An FMP Signals Lab Research Methodology
What 13F Drift Measures
A 13F filing is the quarterly disclosure institutional investment managers must submit when they exercise investment discretion over $100 million or more in section 13F securities. It lists reportable long positions in Section 13F securities the manager held at the end of the quarter. The filing is due 45 days after quarter-end, which means that by the time a 13F reaches EDGAR, the positions it describes are at least 45 days old, and sometimes older.
13F drift is the measure of how a filer's positioning is changing, not what it currently is. A new initiation, a 40% trim, a doubled stake, or a complete exit: these are the events that can contain forward-looking positioning information, because they reveal the manager's revealed conviction in a way the static snapshot does not. A manager who has held 2 million shares of a name for eight straight quarters is telling you something very different from a manager who built that same 2 million share position in the most recent quarter, even though both filings read identically on the surface.
The Limitations of Static Ownership Snapshots
This staleness is the first thing most readers of 13F data underweight. The second is the more important one: a 13F is a snapshot, not a trajectory. It tells you what a manager held on a single day. It does not tell you what they were doing in the weeks before that day, what they have done since, or what their conviction looks like relative to the prior quarter. To extract a signal from 13F data, you have to move from snapshots to drift, the quarter-over-quarter change in position, sized correctly, attributed to the right manager, and set in the context of the rest of their book.
A manager who has held a steady position for many quarters and a manager who has just built the same position will look identical in any single filing. Only one of them carries fresh conviction, and the static snapshot cannot tell them apart.
The Data Engineering Challenges Behind Drift Analysis
The institutional case for tracking drift is not new. It is well-documented in the academic literature on copycat strategies and in the operating practices of allocators who maintain manager exposure tables. What is newer, and what this methodology addresses, is doing it at scale, with point-in-time integrity, across the full filer universe, without relying on a separate specialized dataset. The 13F filing record is public. The reason most teams do not extract drift cleanly from it is not a data access problem. It is a data engineering problem: schema normalization, filer attribution, restatement handling, and survivorship are all non-trivial once you scale beyond a handful of managers.
The rest of this methodology walks through how to solve those engineering problems and produce a clean drift score that an institutional research process can rely on.
Why Standard 13F Data Misses the Drift Signal
Most teams that work with 13F data treat it the way EDGAR presents it: as a list of positions held at a point in time. Pull the latest filing for a manager, read the holdings, draw conclusions. This works for descriptive use cases, such as "what does Berkshire own?", and it fails for everything else.
There are four specific failure modes that consistently break the drift signal at scale.
The Snapshot Trap
A single filing tells you what a manager held on the reporting date. It tells you nothing about how they got there. A 2 million share position that has been steady for six quarters looks identical, in any single filing, to a 2 million share position that was just initiated. Without prior filings joined on the same key, you cannot distinguish a held conviction from a fresh thesis, and only one of those carries forward-return information. The fix is conceptually obvious (compare consecutive quarters) and operationally non-trivial, which is why most descriptive 13F coverage stops at the snapshot.
Restatement Opacity
13F filings are amended more often than the casual reader of the data assumes. A manager files, a discrepancy is identified, and an amendment is filed weeks later that supersedes the original. If your data pipeline ingested the original and never refreshed it, your drift calculation is comparing the wrong number against the next quarter's filing. The size of the error is usually small, but it concentrates in exactly the names where the drift is most interesting: large institutional adjustments that triggered the amendment in the first place. Point-in-time integrity is the only defense.
Filer Survivorship and Identity Drift
The 13F filer universe is not stable. Funds launch, close, merge, and rebrand. Managers move between firms and carry their books with them. A drift calculation that compares one quarter to the next needs to know whether a holding moved because the manager changed their mind, or because the holding moved with the manager to a new filing entity. CIK-level attribution is necessary but not sufficient. You also need a view on how to treat fund-of-funds rollups, sub-advised mandates, and CIK consolidations that the SEC introduces over time.
The $100M Floor
The reporting threshold is $100 million in U.S. equities under management. This sounds high, but it captures a long tail of small institutional managers whose drift signal is driven more by cash-flow rebalancing than conviction. A clean drift methodology needs a filter, typically a minimum threshold above $100M, to surface the names where drift is most likely to be informative. The threshold is a judgment call. The need to apply one is not.
These four failure modes are independent. A team can solve one and still be fooled by the other three. The approach that follows treats them as a system: each is addressed explicitly, and the resulting drift score is robust enough to carry the weight an institutional research process will place on it.
What Has to Be Engineered at Scale
The previous section explained why standard 13F reads fail. This section focuses on what has to be engineered to fix them at scale. The work is harder than it looks, and there are four challenges that have to be solved before drift becomes a number a research process can lean on.
Point-in-Time Integrity
A 13F filing has two dates that matter: the report date (the quarter being filed for) and the file date (when it actually arrived at the SEC). The gap is at least 45 days and is sometimes longer when amendments push the effective date further out. A drift calculation done on the latest available data is technically correct but operationally meaningless, because you cannot build a defensible backtest, a research trail, or an audit log without knowing what the data looked like on the date you would have acted on it. Point-in-time reconstruction, the ability to query what the filer universe looked like as of a specific historical date, is the engineering capability that determines whether this methodology is research-grade or directionally interesting. It is rarer across vendors than most readers assume.
Schema Normalization Across Filers
The SEC's 13F-HR form is standardized at the field level, but the values inside are not. Position quantities can be reported as shares or as principal amounts. CUSIPs are sometimes pre-formatted, sometimes raw. Some filers report at the issuer level, some at the security level, some break out class A and class B holdings, some do not. Joining one manager's filing to the next quarter's filing from the same manager, let alone comparing across managers, requires a normalization pass that handles all of these variations consistently. The work is unglamorous, and it is where most in-house pipelines quietly fail.
Manager Attribution Beyond the CIK
A CIK identifies a filer, not a manager. The same investment manager can file under multiple CIKs through sub-advised mandates, separately managed entities, or legal restructuring. A single CIK can also change hands when a fund is sold, wound down, or absorbed into a parent. Drift attributed cleanly to a CIK is technically correct and analytically misleading whenever the CIK identity has drifted underneath it. A robust pipeline needs a view of manager identity that sits one layer above the CIK: a manager key that survives reorganization and that you can defend in a research review.
Universe Construction
Drift is most informative when measured against a stable, meaningful universe of filers. The full 13F universe is too noisy: it includes pension consultants, insurance subsidiaries, family offices, and small RIAs whose filings reflect cash flows more than convictions. The right approach is to filter the universe before computing drift, typically by scale threshold (commonly $1B and above), by filer type, and sometimes by sector concentration of the book. Constructing this universe in a way that is reproducible across quarters is part of the methodology, not a preprocessing afterthought.
How FMP Supports the Drift Workflow
FMP's 13F endpoint family supports the first three challenges by exposing normalized filing data, pre-computed change fields, and historical filing context. The Filings Extract With Analytics By Holder API returns position-level data with the drift fields pre-computed: changeInSharesNumber, changeInOwnership, changeInWeight, and holdingPeriod are exposed as first-class fields rather than as derivations the user has to construct. The Positions Summary API provides historical filer-level aggregates that make point-in-time reconstruction tractable. The Holder Performance Summary API gives the manager-quality context needed for the universe filter. The fourth challenge, universe construction, remains a methodology choice the user has to make, but the inputs to that choice are exposed at the endpoint level.
The result is that the engineering effort shifts from data wrangling to methodology design, which is where the analytical value actually lives.
Framework Design
A drift score is a single number that summarizes how a filer's position in a name has changed quarter-over-quarter, normalized so that scores are comparable across names, filers, and time periods. With the engineering challenges from the previous section handled by FMP's endpoint family, the construction is straightforward. The analytical choices that go into it are not, and they have to be made deliberately rather than absorbed by default.
The framework has four components, each of which is a decision the user has to take an explicit position on.
1. The Base Unit
Drift can be measured in three ways, each capturing something different. Share count is the rawest signal, useful for spotting outright initiations and exits, but it does not distinguish a 100,000-share move in a $50 stock from the same move in a $500 stock. Percentage change in the position captures conviction at the name level but ignores whether the position grew through price appreciation or active buying. Weight change, the position's share of the filer's overall book, is the most analytically meaningful of the three, but it carries a trap that has to be handled explicitly.
The trap: a position's weight can change quarter-over-quarter without any trading at all, purely because the underlying price moved. A name that doubled in price during the quarter will show up as a "weight increase" in a naive calculation, even if the manager held the share count flat. That is the market doing the work, not the manager, and it is the opposite of a drift signal.
The fix is to decompose weight change into its passive component (price movement on the prior-quarter share count) and its active component (the trade-driven residual). Only the active component carries the conviction signal. A robust framework uses share count, percentage change, and active weight change as three separate base units, then combines them with explicit weighting in the composite step.
2. Filer-Level Normalization
A 5% increase in a small-cap holding by a $100B manager is a meaningful conviction signal. The same 5% increase in the same holding by a $300M manager is more likely to reflect client-flow dynamics than active conviction, not because the manager is less skilled, but because at smaller scale the drift signal is mechanically harder to separate from rebalancing. Drift scores have to be normalized against filer size before they can be compared across managers. The simplest defensible approach weights each filer's drift by their scale rank within the universe, so large filers contribute more to a name's aggregate score and small filers contribute less. More sophisticated approaches use the filer's historical track record (available via the Holder Performance Summary API) as the normalization weight, on the logic that drift from a high-Sharpe manager is more informative than drift from an average one.
3. Universe and Time-Window Choices
The drift score for a name is meaningless in isolation. It has to be computed against a defined universe and over a defined window. The universe choice is straightforward in principle (top filers by scale, top quartile by performance, and so on) and prone to bias in practice, because it is where the methodology can be unconsciously tuned to surface predetermined conclusions. The right discipline is to fix the universe up front and document the rule. The time-window choice is less sensitive but still consequential: quarter-over-quarter is the natural cadence, but a clean methodology should also surface trailing-2-quarter and trailing-4-quarter drift, because a single quarter can mislead when a manager is mid-trade.
4. Composite Construction
The final drift score for a name is a composite: a weighted combination of the three base units (share change, percentage change, active weight change), the filer-normalization layer, and the time-window dimension. The exact weighting is the methodology's biggest editorial decision, and there is no objectively correct answer. What matters is that the weights are fixed before the score is computed, that they are stable across quarters so case studies remain comparable, and that they are exposed in the methodology rather than hidden in the code. A reasonable starting weighting, and the one this methodology uses across the case studies, is 30% share change, 30% percentage change, and 40% active weight change, with a filer-scale normalization layer applied on top.
These four components produce a single drift score per name, refreshed each quarter as new 13F filings arrive. The score is a starting point for analysis, not a conclusion. The case studies using this methodology work through specific names where the score surfaced something worth investigating, and they document the cases where the score flagged a name that turned out to be less meaningful after review.
Reproducing the 13F Drift Calculation With FMP
This walkthrough shows how to reproduce the 13F drift calculation using live FMP data. Each stage follows the same pattern: explain the methodology decision, provide runnable Python code, show the output, and interpret what the result means. The code is written for readability and reproducibility rather than production optimization.
The demonstration name is Apple (AAPL), looking at institutional drift between Q4 2025 and Q1 2026, the most recent full filing window at the time of writing, with 13F submissions for the quarter due by May 15, 2026. Apple is used because it has the broadest institutional coverage of any U.S. equity, which makes the universe-construction and drift-computation steps visible at scale. This section shows that the methodology works; the deeper, name-by-name interpretation belongs to the quarterly case studies.
Before You Run This Code
You will need Python 3.9 or later, the requests and pandas libraries, and an FMP API key. Place your key in the API_KEY variable shown in the first code block. One caveat worth noting up front: 13F filings are sometimes amended after their initial submission, so outputs may change if a filing is restated or refreshed between runs. The code below uses the current authoritative version of each filing; a backtest-grade pipeline would query historical filing-date snapshots instead.
Stage 1: Pull the Filer Data
Purpose. The starting point is the Filings Extract With Analytics By Holder API endpoint, which returns position-level data for every institutional filer that reported a holding in the target name for a given quarter. Each row already includes both the current-quarter values and the prior-quarter values (in fields prefixed with last), along with pre-computed change fields. That means one call to the Q1 2026 endpoint gives the quarter-over-quarter comparison built in, with no separate Q4 2025 pull and merge required. The code below requests the first 100 filers; the complete script later in this methodology paginates through the full result set so the analysis is not limited to the first page.
|
import requests import pandas as pd
API_KEY = "YOUR_FMP_API_KEY" BASE = "https://financialmodelingprep.com/stable" SYMBOL = "AAPL" YEAR = 2026 QUARTER = 1
url = f"{BASE}/institutional-ownership/extract-analytics/holder" params = { "symbol": SYMBOL, "year": YEAR, "quarter": QUARTER, "page": 0, "limit": 100, "apikey": API_KEY, } response = requests.get(url, params=params) response.raise_for_status() df = pd.DataFrame(response.json())
print(df[["investorName", "sharesNumber", "weight", "marketValue", "isNew", "isSoldOut"]].head(10)) |
Output: Stage 1, top filers reporting AAPL for Q1 2026.
Interpretation. Rows 1 and 4 have blank investorName fields and isNew: True. These are filers whose submissions reported AAPL for the first time in Q1 2026 (or whose CIK appeared in the SEC's records as new for this quarter). They have no Q4 2025 baseline to compare against, since lastSharesNumber and lastMarketValue are zero, which means the drift calculation cannot meaningfully be computed for them. This is the filer survivorship problem surfacing in the data, and the framework's response is to exclude these rows from the composite, which Stage 4 does.
Berkshire Hathaway stands out for a different reason: a 21.99% portfolio weight in a single name. That is by far the largest single-name concentration in the top filer universe, and a useful reminder that drift signals at the filer level have to be read alongside the filer's broader portfolio construction.
Stage 2: Build the Universe Filter
Purpose. The Framework Design section specified a scale threshold as the universe filter. For this demonstration, we use a $100M target-position-size threshold as a practical proxy for filer scale. A production version should use total filer book size from the Holder Performance Summary API rather than the size of a single position.
|
POSITION_SIZE_THRESHOLD = 100_000_000 universe = df[df["marketValue"] >= POSITION_SIZE_THRESHOLD].copy() print(f"Filers in universe: {len(universe)}") |
Output: Stage 2, filers clearing the target-position-size threshold.
Interpretation. 99 of the 100 filers returned cleared the $100M threshold. AAPL is unusual in that almost every institutional filer that reports it does so with a substantial position. For smaller names, the same threshold would likely remove a more meaningful share of the long tail.
Stage 3: Compute the Active/Passive Decomposition
Purpose. This is the core methodological step, and it is where the framework becomes meaningfully different from a basic quarter-over-quarter holdings comparison. Total weight change can mislead, because a position's weight can move purely on price even when the manager did not trade. The decomposition splits weight change into a price-driven (passive) component and a trade-driven (active) component. We compute the prior quarter's implied price from the last-quarter fields the endpoint already returns, then apply the price ratio to estimate what the prior weight would have become with no trading. Only the active residual carries the drift signal.
|
universe["prior_quarter_price"] = ( universe["lastMarketValue"] / universe["lastSharesNumber"].replace(0, pd.NA) ) universe["price_ratio"] = universe["quarterEndPrice"] / universe["prior_quarter_price"] universe["passive_weight_estimate"] = universe["lastWeight"] * universe["price_ratio"] universe["weight_change_total"] = universe["weight"] - universe["lastWeight"] universe["weight_change_passive"] = universe["passive_weight_estimate"] - universe["lastWeight"] universe["weight_change_active"] = universe["weight_change_total"] - universe["weight_change_passive"] |
Output: Stage 3, active/passive weight decomposition (first 10 rows).
Interpretation. The decomposition's value is visible immediately. BlackRock's total weight in AAPL declined by 0.23 points quarter-over-quarter, which on a surface read looks like a small trim. The active component, however, is positive (+0.12): BlackRock was a marginal buyer, and the apparent weight decline was driven by the rest of its book outperforming AAPL during the quarter. Berkshire shows the same pattern more sharply: a 0.62 point decline in total weight, but a +0.89 active component. Berkshire's AAPL position became a smaller share of the book purely because of relative price moves elsewhere, while the share count was held steady enough that the active signal is strongly positive.
The opposite case also appears in the data, though not in the first 10 rows shown here: filers whose total weight rose entirely on AAPL price appreciation, with negative or flat active components. In a naive single-number drift method, those filers would have read as buyers. The decomposition prevents that false reading.
Stage 4: Compute the Composite Drift Score
Purpose. The final step combines the three base units, share change, percentage change, and active weight change, using the 30/30/40 weighting set out in the Framework Design section. Each component is z-scored within the universe so the units are comparable before combination. The composite is then multiplied by the filer's position-size rank to apply the scale-normalization layer. Before z-scoring, we drop the rows where the active component could not be computed, the new-filer cases identified in Stage 1, because drift can only be measured against a prior quarter that exists. The block below also includes the sort and display logic that produces the ranked tables shown beneath it, so the exact output can be reproduced.
|
universe = universe.dropna(subset=["weight_change_active"]).copy() universe["share_change"] = universe["changeInSharesNumber"] universe["pct_change"] = universe["changeInSharesNumberPercentage"]
def zscore(series): series = series.astype(float) if series.std() == 0 or pd.isna(series.std()): return series * 0 return (series - series.mean()) / series.std()
universe["share_change_z"] = zscore(universe["share_change"]) universe["pct_change_z"] = zscore(universe["pct_change"]) universe["weight_change_active_z"] = zscore(universe["weight_change_active"])
# 30/30/40 weighting from the Framework Design section universe["drift_composite"] = ( 0.30 * universe["share_change_z"] + 0.30 * universe["pct_change_z"] + 0.40 * universe["weight_change_active_z"] )
# position_size_rank is a demonstration proxy for filer scale, # not true filer AUM universe["position_size_rank"] = universe["marketValue"].rank(pct=True) universe["drift_composite_normalized"] = ( universe["drift_composite"] * universe["position_size_rank"] )
ranked = universe.sort_values("drift_composite_normalized", ascending=False)
display_cols = [ "investorName", "share_change", "pct_change", "weight_change_active", "drift_composite_normalized", ] print("Top 10 active accumulators:") print(ranked[display_cols].head(10).to_string()) reducers = universe.sort_values("drift_composite_normalized", ascending=True) print("Top 10 active reducers:") print(reducers[display_cols].head(10).to_string()) |
Output: Stage 4, top 10 active accumulators of AAPL in Q1 2026.
Output: Stage 4, top 10 active reducers of AAPL in Q1 2026.
Interpretation. The output is the drift composite per filer for AAPL between Q4 2025 and Q1 2026, sortable in either direction. A few observations show what the score surfaces, without drawing conclusions that belong in a full case study.
The conviction signal is clearest at the top. Amundi, the European asset manager, was the cleanest active accumulator of AAPL in the quarter, with a 23% share-count increase paired with the largest active weight contribution in the top 10. Capital International Investors, on the reducer side, cut its position by nearly half (a 49.5% share-count decline) and carries the most negative drift composite. These are the names the score is designed to surface, and they emerge without manual filtering.
A geographic pattern appears in the data. Several of the largest active accumulators are non-US institutions, while the largest reducers skew toward US-headquartered managers. This is the kind of cross-cut a static snapshot would not reveal, and it is a good example of something a quarterly case study can investigate further rather than resolve here.
One row shows where the methodology breaks down. H&H International Investment registers an active weight change of -10.23, two orders of magnitude larger than any other filer (the next largest is -0.70). This is not a meaningful drift signal; it almost certainly reflects a structural shift in the filer's broader book that distorted the passive-weight estimate in a single quarter. A robust production deployment would winsorize outliers above a defined threshold or fall back to a simpler weight-change metric for filers with severely changed books. We flag it here as an honest example of the methodology's limits, and return to it in the limitations section that closes this methodology.
What This Pipeline Does and Does Not Cover
The pipeline above produces a clean drift composite for a single name, for a single quarter-over-quarter window, against a defined universe. That is the unit of analysis the case studies operate on. A full research deployment extends it in three directions: across many names (running the pipeline over an index universe), across multiple windows (trailing-2 and trailing-4 quarters), and with a full filer-scale normalization rather than the single-name position-size proxy used here. None of those extensions changes the core methodology; they extend its reach.
The next section walks through the engineering choices required to scale this pattern from a single-name demonstration to a standing institutional research artifact.
Complete Reproducible Python Script
The staged walkthrough above explains each methodology decision separately. For reproducibility, the full script below combines those stages into one runnable workflow. Add your FMP API key, adjust the symbol, year, and quarter as needed, and run the script to generate the drift composite and the ranked accumulator and reducer tables. This version paginates through the full result set rather than stopping at the first 100 filers, so it supports analysis at scale.
Full Runnable Script
|
import requests import pandas as pd
# ============================================================ # CONFIG # ============================================================ API_KEY = "YOUR_FMP_API_KEY" BASE = "https://financialmodelingprep.com/stable" SYMBOL = "AAPL" YEAR = 2026 QUARTER = 1
# ============================================================ # STAGE 1 - Pull filer data (paginated to capture all filers, # not only the first page of results) # ============================================================ def pull_all_holders(symbol, year, quarter): frames, page = [], 0 while True: url = f"{BASE}/institutional-ownership/extract-analytics/holder" params = {"symbol": symbol, "year": year, "quarter": quarter, "page": page, "limit": 100, "apikey": API_KEY} resp = requests.get(url, params=params) resp.raise_for_status() batch = resp.json() if not batch: break frames.append(pd.DataFrame(batch)) page += 1 return pd.concat(frames, ignore_index=True) if frames else pd.DataFrame()
df = pull_all_holders(SYMBOL, YEAR, QUARTER) print(f"Total filers returned: {len(df)}")
# ============================================================ # STAGE 2 - Universe filter # $100M target-position-size threshold: a demonstration proxy # for filer scale, not true filer AUM. # ============================================================ POSITION_SIZE_THRESHOLD = 100_000_000 universe = df[df["marketValue"] >= POSITION_SIZE_THRESHOLD].copy() print(f"Filers in universe: {len(universe)}")
# ============================================================ # STAGE 3 - Active/passive weight decomposition # ============================================================ universe["prior_quarter_price"] = ( universe["lastMarketValue"] / universe["lastSharesNumber"].replace(0, pd.NA) ) universe["price_ratio"] = universe["quarterEndPrice"] / universe["prior_quarter_price"] universe["passive_weight_estimate"] = universe["lastWeight"] * universe["price_ratio"] universe["weight_change_total"] = universe["weight"] - universe["lastWeight"] universe["weight_change_passive"] = universe["passive_weight_estimate"] - universe["lastWeight"] universe["weight_change_active"] = universe["weight_change_total"] - universe["weight_change_passive"]
# ============================================================ # STAGE 4 - Composite drift score (30/30/40 weighting from # the Framework Design section: share / percentage / active weight) # ============================================================ universe = universe.dropna(subset=["weight_change_active"]).copy() universe["share_change"] = universe["changeInSharesNumber"] universe["pct_change"] = universe["changeInSharesNumberPercentage"]
def zscore(series): series = series.astype(float) if series.std() == 0 or pd.isna(series.std()): return series * 0 return (series - series.mean()) / series.std()
universe["share_change_z"] = zscore(universe["share_change"]) universe["pct_change_z"] = zscore(universe["pct_change"]) universe["weight_change_active_z"] = zscore(universe["weight_change_active"])
universe["drift_composite"] = ( 0.30 * universe["share_change_z"] + 0.30 * universe["pct_change_z"] + 0.40 * universe["weight_change_active_z"] )
# position_size_rank: rank by position value as a demonstration # proxy for filer scale (not true filer AUM) universe["position_size_rank"] = universe["marketValue"].rank(pct=True) universe["drift_composite_normalized"] = ( universe["drift_composite"] * universe["position_size_rank"] )
ranked = universe.sort_values("drift_composite_normalized", ascending=False)
display_cols = [ "investorName", "share_change", "pct_change", "weight_change_active", "drift_composite_normalized", ]
print("Top 10 active accumulators:") print(ranked[display_cols].head(10).to_string())
reducers = universe.sort_values("drift_composite_normalized", ascending=True) print("Top 10 active reducers:") print(reducers[display_cols].head(10).to_string())
|
The script is written for clarity rather than production deployment. A production version should add caching, rate-limit handling, logging, and a filer-level master table for full universe-scale analysis.
Note on reproducing the staged outputs: the screenshots shown earlier in this walkthrough were generated from the first-page demonstration run, which reads the top 100 filers by position size. Run against the full filer universe, this script surfaces a larger and noisier set of filers, including small or newly initiated positions whose percentage-change values can be extreme, so the composite scores and rankings will differ from the staged figures above. The quarterly case studies apply additional universe filters, such as a filer-level book-size threshold from the Holder Performance Summary API, before interpreting any result.
Scaling the Drift Workflow Beyond a Single-Name Demonstration
The complete script handles reproduction for one name in one quarter. This section explains what changes when the workflow becomes institutional: the methodology stays the same, but the orchestration around it grows in three directions.
Scaling Across Names
The most direct extension runs the same pipeline over a defined index universe, such as the S&P 500 or the Russell 1000, instead of a single ticker. This is a structural change, not a logical one. The endpoint takes a symbol parameter, so the natural pattern iterates the call across the universe and accumulates results into a single table. The constraint here is orchestration: a 500-name pull, even at a generous call rate, takes time and should be parallelized carefully. A robust deployment uses a request pool with backoff, caches responses by (symbol, year, quarter) so reruns are inexpensive, and stores the result in a database the rest of the research stack can query. The methodology layer does not change as names are added; scaling across symbols increases orchestration and rate-management complexity, not analytical complexity.
Scaling Across Windows
The Framework Design section noted that single-quarter drift is noisy, since a manager mid-trade looks like a buyer one quarter and a holder the next. The clean version surfaces drift over multiple windows at once: a trailing-1-quarter composite (the version computed above), a trailing-2-quarter composite, and a trailing-4-quarter composite. Each window has a different signal-to-noise profile. Trailing-1 captures the most recent positioning intent but has the highest false-positive rate. Trailing-4 is the most stable but lags structural shifts. The right operational pattern computes all three and treats them as a vector: filers whose drift is consistent across windows carry more weight than filers whose drift is large in a single window. Producing the trailing windows requires pulling multiple quarters per symbol, which increases the total API call volume but does not change the methodology.
Scaling Across the Filer Universe
The single-name proxy used in the walkthrough, filtering on the filer's AAPL position size, is fine for a demonstration and inadequate for production research. The rigorous version uses each filer's total book size as the scale input, which requires a separate Holder Performance Summary API call per filer. At universe scale, this is a one-time enrichment that is well-suited to quarterly caching, since filer scale moves quarterly rather than daily. A clean architecture builds a filer master table once per quarter, joins it to the per-symbol drift output, and uses the joined view for all downstream analysis. The same master table supports manager-quality filtering, survivorship handling (tracking which CIKs appear and disappear quarter to quarter), and the universe-construction decisions the methodology depends on. Production research should use this filer master table rather than the demonstration's single-name position-size proxy.
These three extensions turn a script into a research artifact: a quarterly-refreshed table, indexed by symbol and filer, with the active/passive decomposition, the composite score, the scale-normalized variant, and the trailing-window vector pre-computed. A portfolio manager queries it for names, a risk team queries it for filer-level exposure, and a research process queries it for screens. The same data and the same methodology power all three uses, which is what separates a research artifact from a one-off analysis.
How FMP Supports Production-Scale Drift Analysis
The engineering above is manageable rather than prohibitive because each scaling requirement maps to a specific endpoint. The Filings Extract With Analytics By Holder API supplies the pre-computed comparison fields that make scaling across names a matter of iteration rather than re-derivation. The Positions Summary API and the historical filing windows behind it support scaling across time. The Holder Performance Summary API supplies the filer-level scale and quality measures that the universe layer needs. With data acquisition and normalization handled, the engineering concentrates on orchestration (rate management, caching, scheduling), which keeps the methodology's complexity in the analytics rather than in the plumbing.
Where the Methodology Breaks Down
Any methodology that reduces complex reporting data into a single score has failure modes. Naming those constraints is part of making the framework usable in a real research process. The drift composite has four specific points where the signal degrades, and any deployment should treat them as known constraints rather than rare edge cases.
Filer Survivorship by Deletion
The code walkthrough dropped rows where the active weight could not be computed, the filers reporting AAPL in Q1 2026 with no Q4 2025 baseline. That choice is defensible: drift requires two points, and a filer that appeared for the first time has only one. But the deletion is not free. A filer that liquidated its entire AAPL position by closing the fund disappears from the data in the same way, since isSoldOut rows are also dropped, and the methodology never sees that high-conviction sell signal. One workaround is to monitor isSoldOut and isNew as separate parallel signals rather than treating them as missing data. The case studies using this methodology demonstrate that pattern on quarters where it matters.
Filer Identity Drift
The methodology attributes drift to a CIK, but a CIK identifies a filer, not a manager. The same investment manager can file under multiple CIKs, and a single CIK can change hands when a fund is sold or absorbed into a parent. A drift signal attributed cleanly to a CIK is technically correct and analytically misleading whenever the CIK identity has drifted underneath it. The Q1 2026 data shows a version of this to the naked eye: two of the top 100 filers in AAPL, both flagged as new entrants, reported positions worth over $80 billion each and arrived with blank investorName fields. These are unlikely to represent true first-time AAPL buyers and may reflect CIK consolidations, rebranded filing entities, or other identifier changes. A robust pipeline maintains a manager-key layer above the CIK and uses external identity-resolution data to handle these cases. We flag it because a production deployment will encounter it within the first quarter of operation.
Outlier Sensitivity in the Passive-Weight Estimate
The active/passive decomposition uses the prior quarter's implied price (derived from the last-quarter market value and share count) as the input to the passive-weight calculation. For filers whose books changed dramatically between quarters, through major rebalances, large client redemptions, or fund liquidations, the implied price can be distorted and the passive-weight estimate becomes unreliable. The Q1 2026 output shows one example clearly: H&H International Investment registered an active weight change of -10.23, two orders of magnitude larger than any other filer. That number is not a meaningful drift signal; it is the math being pushed past its useful range by a structural shift in the filer's book. The standard safeguards are to winsorize outliers above a defined threshold (typically capping at the 99th percentile of historical drift magnitudes) or to fall back to a simpler share-count-based score for filers whose passive-weight estimate is implausible. These are methodological safeguards that reduce the impact of the problem, not a definitive fix, and explicit awareness of the limitation matters as much as the safeguard itself.
Single-Quarter Signal Noise
A 13F drift signal reads cleanest when a manager has finished a decision and noisiest when a manager is mid-decision. A filer two quarters into building a position looks like a buyer in the trailing-1-quarter window and a holder in the trailing-2. The trailing-window vector described in the scaling section is the structural response: look at trailing-1, trailing-2, and trailing-4 composites together rather than relying on the single-quarter score. The case studies can treat the trailing-1 score as the attention signal and multi-window persistence as the stronger conviction signal for deeper review.
These four limitations are properties of 13F data and the methodology applied to it, not bugs in the implementation. They define what the drift composite can and cannot claim. The case studies using this methodology should document both outcomes: quarters where the framework surfaced a useful signal, and quarters where the signal proved less durable after further review.
13 Drift FMP Case Studies Using This Methodology
This methodology is refreshed each quarter as new 13F filings arrive at the SEC. Each refresh surfaces names where the composite score flagged something worth investigating, and the case studies using this methodology walk through those names one by one, with the methodology applied as-is and the conclusions documented honestly, including the cases where a flagged name proved less meaningful after review.
New case studies are published shortly after each quarter's 13F filing window closes. Older case studies remain accessible and are not retroactively edited when the methodology is refined: the methodology page above is the single source of truth, and a case study's conclusions stand on the version of the methodology in effect at the time it was published.


