Fastest SEC Filings Websocket vs Normal Websocket
Total Difference (all time): Calculating…
Total Difference (averaged by day): Calculating…
Choose a statistic to view its charts.
When a SEC filings is accepted by the SEC, a Filing Metadata page is immediately created. Then, seconds, minutes, hours, or days later the filing is emitted on the RSS feed. Fastest SEC Filings Websocket uses a combination of RSS improvements and predictive algorithms to detect filings faster. Below are graphs comparing it to the normal websocket. The normal websocket should be as fast or faster than existing commercial options. The underlying data is from SEC Filings Metadata. More details on design: How to Make a Fast SEC Filing Detection System.
Note: This currently is broken, as it relies on the SEC submissions endpoint for acceptancedatetime, which broke near October. Working on a fix.
Total Difference (all time): Calculating…
Total Difference (averaged by day): Calculating…
Total Difference (all time): Calculating…
Total Difference (averaged by day): Calculating…
Fastest SEC Filings prediction algorithm 'index_url' vs Websocket's 'anticipate'
Total Difference (all time): Calculating…
Total Difference (averaged by day): Calculating…
Source: Datamule public statistics data.
Use datamule-hub to download the source data for the last 30 complete Eastern days.
from datetime import datetime, time, timedelta
from zoneinfo import ZoneInfo
from datamulehub import databases
eastern = ZoneInfo("America/New_York")
end_date = datetime.now(eastern).date()
start_date = end_date - timedelta(days=30)
start_seconds = int(datetime.combine(start_date, time(), eastern).timestamp())
end_seconds = int(datetime.combine(end_date, time(), eastern).timestamp())
databases.query(
f"""
SELECT accession, detected_time, detection_method
FROM fastest_sec_filings_metadata
WHERE detected_time >= {start_seconds * 1000}
AND detected_time < {end_seconds * 1000}
""",
output_dir="fastest",
)
databases.query(
f"""
SELECT accession, source, detected_time
FROM monitor_dumps
WHERE detected_time >= {start_seconds}
AND detected_time < {end_seconds}
AND lower(source) IN ('rss', 'efts', 'anticipate')
""",
output_dir="websocket",
)
databases.query(
f"""
SELECT accessionnumber, acceptancedatetime, filingdate
FROM submissions_metadata
WHERE filingdate >= '{start_date}'
AND filingdate < '{end_date}'
""",
output_dir="linked_filings",
)
Compare acceptance timestamps extracted from filing metadata from the SEC versus the SEC's reporting of said metadata via endpoints. Filing metadata timestamps in Eastern Time (adjusted for EST and EDT).
Source: Datamule acceptance datetime comparison data. Updated by the nightly statistics workflow.
Generate monthly comparison counts with datamule-hub.
from datamulehub import databases
databases.query(
"""
SELECT
submission_type,
year(filing_date) AS year,
month(filing_date) AS month,
COUNT_IF(diff_seconds = -18000) AS num_minus_5,
COUNT_IF(diff_seconds = -14400) AS num_minus_4,
COUNT_IF(diff_seconds = 0) AS num_exact,
COUNT_IF(diff_seconds = 14400) AS num_plus_4,
COUNT_IF(diff_seconds = 18000) AS num_plus_5,
COUNT_IF(diff_seconds NOT IN (-18000, -14400, 0, 14400, 18000)) AS num_other,
COUNT_IF(diff_seconds IS NOT NULL) AS total_compared,
COUNT_IF(diff_seconds IS NULL) AS num_excluded
FROM (
SELECT
s.submission_type,
s.filing_date,
date_diff('second', f.acceptance_time, s.acceptance_time) AS diff_seconds
FROM (
SELECT
accession,
CASE WHEN COUNT(DISTINCT submission_type) = 1
THEN min(submission_type) ELSE 'Unknown' END AS submission_type,
min(filing_date) AS filing_date,
CASE WHEN COUNT(DISTINCT submission_type) = 1
AND COUNT(DISTINCT filing_date) = 1
AND COUNT(DISTINCT acceptance_time) = 1
THEN min(acceptance_time) END AS acceptance_time
FROM (
SELECT
TRY_CAST(replace(trim(CAST(accessionnumber AS varchar)), '-', '') AS bigint) AS accession,
COALESCE(NULLIF(trim("form"), ''), 'Unknown') AS submission_type,
TRY_CAST(filingdate AS date) AS filing_date,
TRY(from_iso8601_timestamp(acceptancedatetime)) AS acceptance_time
FROM submissions_metadata
) submission_rows
WHERE accession > 0 AND accession < 1000000000000000000
GROUP BY accession
) s
LEFT JOIN (
SELECT
accession,
CASE WHEN COUNT(DISTINCT acceptance_time) = 1
THEN min(acceptance_time) END AS acceptance_time
FROM (
SELECT
TRY_CAST(replace(trim(CAST(accessionnumber AS varchar)), '-', '') AS bigint) AS accession,
TRY(with_timezone(
date_parse(acceptance_datetime, '%Y%m%d%H%i%s'), 'America/New_York'
)) AS acceptance_time
FROM filing_metadata_filings
) header_rows
WHERE accession > 0 AND accession < 1000000000000000000
GROUP BY accession
) f ON s.accession = f.accession
WHERE s.filing_date >= DATE '2025-01-01'
) comparisons
GROUP BY submission_type, year(filing_date), month(filing_date)
ORDER BY 2, 3, 1
""",
output_dir="acceptance_datetime_comparison",
)
Loading filing types…
Source: Datamule filing types data.
Use datamule-hub to download the filing type counts.
from datamulehub import databases
databases.query(
"""
SELECT
submissiontype,
CAST(date_trunc('week', CAST(filingdate AS DATE)) AS DATE) AS week_start,
CAST(date_trunc('month', CAST(filingdate AS DATE)) AS DATE) AS month_start,
year(CAST(filingdate AS DATE)) AS calendar_year,
COUNT(*) AS count,
SUM(CASE WHEN containsxbrl = 1 THEN 1 ELSE 0 END) AS xbrl_count
FROM sec_submission_details_table
WHERE submissiontype IS NOT NULL AND filingdate IS NOT NULL
GROUP BY 1, 2, 3, 4
ORDER BY calendar_year, month_start, week_start, submissiontype
""",
output_dir="filing_types",
)
Loading signatures…
Source: Datamule signature data.
Use datamule-hub to download the last five years of SEC signature records.
from datamulehub import databases
databases.query(
"""
SELECT accessionnumber, filingdate, name
FROM (
SELECT accessionnumber, filingdate, signature AS name
FROM "schedule_13d_signature_person"
WHERE signature IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "345_owner_signature"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturereportingpersonname
FROM "schedule_13g_signature_information"
WHERE signaturereportingpersonname IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, personsignature
FROM "c_signature_person"
WHERE personsignature IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, personsignature
FROM "c_tr_signature_person"
WHERE personsignature IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "13fhr"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "d"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "25_nse"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "ncr"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signername
FROM "ma"
WHERE signername IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signername
FROM "ma_w"
WHERE signername IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signername
FROM "sbse_c"
WHERE signername IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signpersonname
FROM "x_17a_5"
WHERE signpersonname IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, nameofsigningofficer
FROM "n_mfp12"
WHERE nameofsigningofficer IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, nameofsigningofficer
FROM "n_mfp3"
WHERE nameofsigningofficer IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, registrantsignedname
FROM "n_cen"
WHERE registrantsignedname IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signername
FROM "nport_p"
WHERE signername IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "ta_1"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "ta_2"
WHERE signaturename IS NOT NULL
UNION ALL
SELECT accessionnumber, filingdate, signaturename
FROM "ta_w"
WHERE signaturename IS NOT NULL
) signers
WHERE filingdate >= CAST(date_add('year', -5, current_date) AS VARCHAR)
""",
output_dir="all_signers",
)
Loading commissions…
Amounts in USD on a logarithmic scale. Color identifies fee type; point shape identifies submission type.
Source: Datamule commissions data.
Use datamule-hub to download the source records.
from datamulehub import databases
databases.query(
"""
SELECT
accessionnumber,
filingdate,
'd' AS submission_type,
CAST(findersfeedollaramount AS varchar) AS findersfee,
CAST(salescommissionsdollaramount AS varchar) AS commissions
FROM "d"
WHERE findersfeedollaramount IS NOT NULL
OR salescommissionsdollaramount IS NOT NULL
UNION ALL
SELECT
accessionnumber,
filingdate,
'1-A',
CAST(finderfeesfee AS varchar),
CAST(salescommissionsserviceproviderfees AS varchar)
FROM "1a"
WHERE finderfeesfee IS NOT NULL
OR salescommissionsserviceproviderfees IS NOT NULL
UNION ALL
SELECT
accessionnumber,
filingdate,
'1-K',
CAST(findersfees AS varchar),
CAST(salescommissionsfee AS varchar)
FROM "1k_summary_info"
WHERE findersfees IS NOT NULL
OR salescommissionsfee IS NOT NULL
UNION ALL
SELECT
accessionnumber,
filingdate,
'1-Z',
CAST(findersfees AS varchar),
CAST(salescommissionsfee AS varchar)
FROM "1_z"
WHERE findersfees IS NOT NULL
OR salescommissionsfee IS NOT NULL
""",
output_dir="finders_and_commissions",
)
Loading mergers…
Source: Datamule merger data. Dates shown are filing dates.
Use datamule-hub to download series-level merger filings.
from datamulehub import databases
databases.query(
"""
SELECT
accessionnumber,
filingdate,
acquiringcik,
acquiringentityname,
acquiringseriesid,
acquiringseriesname,
acquiringclasscontractid,
acquiringclasscontractname,
targetcik,
targetentityname,
targetseriesid,
targetseriesname,
targetclasscontractid,
targetclasscontractname
FROM "effect_merger"
WHERE acquiringseriesid IS NOT NULL
AND acquiringseriesname IS NOT NULL
AND targetseriesid IS NOT NULL
AND targetseriesname IS NOT NULL
""",
output_dir="effect_merger",
)
The first ten digits of an accession identify the submitting account's CIK. This can be a filing agent or an entity filing for itself. Each filing is counted once for its submitting CIK, with its calendar year taken from the filing date.
Loading filer agents…
Choose a submitting CIK to view its filings.
Names are current names from listed and unlisted filer metadata. “Filed for” includes companies and individuals associated with a filing. A filing can have several associated CIKs, so table counts may add up to more than the agent's total filings.
| Filed for | CIK | Number of filings |
|---|
Sources: SEC Filings Metadata, Filer Metadata, and Filer Agent Data.
Use datamule-hub to download
annual agent totals and counts by associated CIK. Rows with a null cik contain
the agent total. Join the other rows to listed and unlisted filer metadata on cik for names.
from datamulehub import databases
databases.query(
"""
SELECT
calendar_year,
agent_cik,
CASE WHEN grouping(cik) = 1 THEN CAST(NULL AS bigint) ELSE cik END AS cik,
count(DISTINCT accession) AS filing_count
FROM (
SELECT
d.accession,
year(d.filing_date) AS calendar_year,
CAST(substr(lpad(CAST(d.accession AS varchar), 18, '0'), 1, 10) AS bigint) AS agent_cik,
TRY_CAST(regexp_replace(CAST(c.cik AS varchar), '[^0-9]', '') AS bigint) AS cik
FROM sec_accession_cik_table c
JOIN (
SELECT
TRY_CAST(replace(CAST(accessionnumber AS varchar), '-', '') AS bigint) AS accession,
min(TRY_CAST(filingdate AS date)) AS filing_date
FROM sec_submission_details_table
GROUP BY 1
) d
ON TRY_CAST(replace(CAST(c.accessionnumber AS varchar), '-', '') AS bigint) = d.accession
WHERE d.filing_date <= current_date
AND d.accession > 0 AND d.accession < 1000000000000000000
) filings
WHERE agent_cik > 0
GROUP BY GROUPING SETS (
(calendar_year, agent_cik),
(calendar_year, agent_cik, cik)
)
HAVING grouping(cik) = 1 OR (cik > 0 AND cik < 10000000000)
""",
output_dir="filer_agents",
)
Compare the first nonzero digits of numeric XBRL values in one filing with Benford's Law.
Sources: XBRL Benford data and filer names.
Generate the first nonzero digit counts by accession, taxonomy, and submission type with datamule-hub.
from datamulehub import databases
databases.query(
"""
SELECT acc AS accessionnumber, c.cik, d.submissiontype, taxonomy, digit, n
FROM (
SELECT accessionnumber AS acc, taxonomy,
CAST(regexp_extract(value, '[1-9]') AS integer) AS digit,
count(*) AS n
FROM simple_xbrl
WHERE regexp_like(value, '^-?[0-9]+([.][0-9]+)?$')
AND regexp_like(value, '[1-9]')
GROUP BY 1, 2, 3
)
JOIN sec_accession_cik_table c ON acc = c.accessionnumber
JOIN sec_submission_details_table d ON acc = d.accessionnumber
ORDER BY 1, 2, 3, 4, 5
""",
output_dir="benford_by_accession_cik_taxonomy_type",
)