Skip to content

feat(sec): capture the Form 144 filer CIK and 10b5-1 plan adoption date - #4411

Merged
daniel3303 merged 2 commits into
mainfrom
feat/form144-filer-cik
Aug 19, 2026
Merged

feat(sec): capture the Form 144 filer CIK and 10b5-1 plan adoption date#4411
daniel3303 merged 2 commits into
mainfrom
feat/form144-filer-cik

Conversation

@daniel3303

Copy link
Copy Markdown
Owner

Problem

A Form 144 announces that an affiliate intends to sell. Whether the sale actually happened is only knowable by joining the notice to that person's Form 4 executions, and nothing in the codebase could do that.

The parser stored the seller as free text and dropped the identity entirely, with the model comment asserting that "Form 144 XML carries the seller's name but not a CIK". That is not correct. The CIK is in the submission header, and it is the same CIK the person's Forms 3/4/5 are filed under.

Falling back to name matching resolves about 55% of the corpus, and the unresolved remainder is not missing at random, so any analysis built on it is biased before it starts.

What changed

Form144Filing gains two nullable columns:

  • FilerCik, from headerData/filerInfo/filer/filerCredentials/cik. Stored verbatim, because EDGAR zero-pads to ten characters on both Form 4 and Form 144, and InsiderOwner.OwnerCik keeps the Form 4 value in exactly that shape. The two therefore compare without normalisation. A test pins the padding: trimming it would make every join miss silently rather than fail loudly.
  • PlanAdoptionDate, from noticeSignature/planAdoptionDates. The 10b5-1 flag exists only on InsiderTransaction, so an executed notice can borrow it from the matching transaction while a never-executed notice has nothing to borrow from. The notice's own plan date is the only pre-arranged-versus-discretionary signal available for that cohort.

Form144FilerCikBackfillWorker fills history by re-reading each notice's XML. Selection is driven by FilerCik IS NULL, so the run drains, terminates and resumes. Documents EDGAR will not serve are parked after three attempts so one bad filing cannot stall the backlog, and a batch that makes no progress at all ends the cycle rather than looping on the same rows. It runs in the worker process to share the single SEC rate limiter, on a longer stagger than the insider reprocess so the two do not split the request budget.

Verification

The identity claim was checked against EDGAR directly before writing any code:

  • Every Form 144 is indexed twice in the quarterly form.idx, once under the issuer CIK and once under the natural person's. Across 17 quarters, 122,720 of 122,723 distinct accessions carry exactly two rows.
  • On a 20-notice sample the CIK recovered from that index matched filerCredentials/cik inside the notice XML in 20 of 20 cases, across three different filing agents.

Tests: 10 new, covering agent-filed and self-filed shapes, the zero-padding invariant, missing header data, joint filings with several <filer> elements, plan-date capture including the several-dates and none cases, agreement between the processor and the backfill parse paths, and malformed XML. All 57 Form 144 tests pass.

Migration

AddForm144FilerCikAndPlanAdoptionDate is additive only, two nullable columns and one index, so it is invisible to running code and safe under a migrate-before-rollout deploy.

Not in this PR

FilerCik is deliberately not exposed on any DTO, MCP tool or portal surface. It is an internal identity key, and putting it on the public wire is a separate decision.

A Form 144 says an affiliate intends to sell. Whether they actually sold is
only knowable by joining the notice to that person's Form 4 executions, and
nothing could do that: the parser stored the seller as free text and dropped
the identity, so matching fell back to names and resolved barely half the
corpus.

EDGAR does carry the identity, in headerData/filerInfo/filer/filerCredentials,
and it is the same CIK the person's Forms 3/4/5 are filed under. Storing it
verbatim makes the join exact against InsiderOwner.OwnerCik, which keeps the
Form 4 value in the same zero-padded shape. A test pins that padding, because
trimming it would make every join miss silently rather than fail loudly.

Also captures the Rule 10b5-1 plan adoption date from noticeSignature. The
10b5-1 flag exists only on InsiderTransaction, so an executed notice can borrow
it from the matching transaction while a never-executed notice cannot. The
notice's own plan date is the only pre-arranged-versus-discretionary signal
available for the cohort where it matters most.

Both columns are nullable and backfilled by Form144FilerCikBackfillWorker,
which re-reads each notice's XML from EDGAR. Selection is driven by FilerCik
being null, so the run drains, terminates and resumes; documents EDGAR will not
serve are parked after three attempts so one bad filing cannot stall the
backlog. It shares the worker process's SEC rate limiter and starts on a longer
stagger than the insider reprocess so the two do not split the request budget.

Migration is additive only: two nullable columns and one index.
Comment on lines +113 to +121
catch (Exception exception)
{
_logger.LogWarning(
exception,
"Failed to fetch Form 144 {AccessionNumber}",
filing.AccessionNumber
);
continue;
}
@daniel3303
daniel3303 merged commit 113386a into main Aug 19, 2026
8 of 9 checks passed
@daniel3303
daniel3303 deleted the feat/form144-filer-cik branch August 21, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants