Money Market Fund Holdings
Every security a money-market fund held, month by month, from Form N-MFP: issuer, CUSIP/ISIN/LEI, value, weight, maturity, yield, and whether the position counts as daily-liquid, weekly-liquid or illiquid - plus repo collateral and NRSRO ratings where reported. Coverage begins 2016-05, matching edgar_mmf_filings.
edgar_mmf_holdings - the dataset name to pass to the Obscura API.
What one row means
One **security held by a money-market fund**, as reported on one monthly SEC Form N-MFP: the issuer, its identifiers, the three maturity dates Rule 2a-7 cares about, the value including and excluding sponsor support, the percent of the fund's net assets, and the daily/weekly/illiquid classifications that decide whether the fund is compliant. # This is NOT `edgar_nport_holdings`, and the overlap measurement is why Both describe "a fund's position on a date", so the union was the obvious design. Read against real documents it collapses. Of `edgar_nport_holdings`' 24 data columns, an N-MFP position can fill **9 at best** - issuer name, title, CUSIP, ISIN, LEI, value, percent of net assets, a maturity date and a category - and two of those are semantic stretches: N-PORT's `asset_cat` is a short code vocabulary (`EC`, `DBT`) while N-MFP's `investmentCategory` is a Rule 2a-7 sentence (`"U.S. Treasury Repurchase Agreement, if collateralized only by U.S. Treasuries"`), and N-PORT reports ONE `maturityDt` while N-MFP reports THREE distinct maturities whose divergence is the entire point of the form. The other **15 N-PORT columns would be NULL on every N-MFP row** - `ticker`, `balance`, `units`, `cur_cd`, `payoff_profile`, `issuer_cat`, `inv_country`, `fair_val_level`, `is_restricted_sec`, the three securities-lending flags, `coupon_kind`, `annualized_rt`, `is_default` - because a money fund reports value rather than share count and does not report any of them. Running the other way, **~16 fields here have no N-PORT home at all**, four of which are nested one-to-many lists (repo collateral, NRSRO ratings, demand-feature providers, guarantors) that a flat holdings row cannot hold. A union table would be roughly 62 % NULL from both directions and would still lose the nested lists. # FOUR GENERATIONS, AND THE RENAMES ARE SILENT SEC rewrote this form's XML three times, and every rewrite renamed concepts rather than only adding them. Measured field-presence over 28 filings / 2,809 positions across the four vocabularies: | concept | `N-MFP` | `N-MFP1` | `N-MFP2` | `N-MFP3` | |---|---|---|---|---| | issuer | `InvestmentIssuer` | `nameOfIssuer` | `nameOfIssuer` | `nameOfIssuer` | | value | `InvestmentOwnedAtFairValue` | `includingValueOfAnySponsorSupport` | same | same | | pct of NAV | `InvestmentOwnedPercentOfNetAssets` | `percentageOfMoneyMarketFundNetAssets` | same | same | | illiquid | `isThisIlliquidSecurity` | `illiquidSecurityFlag` | same | same | | eligibility | `rating` (category) | `securityRated` (category) | `securityEligibilityFlag` (Y/N) | same | | NRSRO block | `designatedNRSROList` | `designatedNrsro` | `NRSRO` | `assigningNRSRORating` | A parser written to one spelling yields a column that is NULL for an entire era - silently, because NULL is legal. That is the `edgar_abs_assets` defect class, and the defence here is the same: an explicit per-generation alias table plus a parse-time check that every position carries a value and a percent. **The generation is read from the DOCUMENT, never from the date.** The four vocabularies overlap inside single quarters at both transitions - 2016 QTR2 carries 546 `N-MFP` and 1,039 `N-MFP1`, 2016 QTR4 carries 484 `N-MFP1` and 935 `N-MFP2` - and amendments cross eras freely. See `docs/datasets/edgar_mmf_holdings.md`.
One row per One SECURITY POSITION in one SERIES in one REPORT MONTH, keyed by (filename, seq). seq is the 0-based index of the position in the submission's schedule, assigned by the parser via .enumerate() and NOT sourced from the filing - N-MFP gives a position no identifier of its own, and CUSIP is absent on up to 8 % of rows so it cannot serve as the key. NOT one security: the same CUSIP reappears every month for as long as the fund holds it, which is what makes this a monthly panel. The analytic key is (series_id, cusip, occurred_on) and it is indexed, but it is not the identity..
Point-in-time availability
Every Obscura dataset carries available_date: the calendar day the publisher made the row
available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the
same thing on every dataset in the catalog.
For edgar_mmf_holdings: filing_join: available_date is the EDGAR DISSEMINATION day of the parent N-MFP submission, taken from the bronze object's own day partition (edgar_docs::available_date), never from FILED AS OF DATE. WHY: original N-MFP filings were embargoed 60 days under the original Rule 30b1-7 and are absent from every daily index probed between 2011-07-12 and 2016-04-06, while the quarterly full index attributes thousands of them to those dates; the embargo lifted with N-MFP1, whose originals appear same-day from 2016-05-06. occurred_on is the report month end the position is held AS OF, and is always earlier than availability. Evidence class B; see docs/availability/edgar_mmf_holdings.md.
Refresh cadence
Obscura refreshes edgar_mmf_holdings every 6 hours - the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.
Schema - 39 columns
The full public column list for edgar_mmf_holdings, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/edgar_mmf_holdings.
| Column | Type | Description |
|---|---|---|
| filename | text · not null | PK part 1. EDGAR archive path of the N-MFP submission this position came from (`edgar/data/{cik}/{accession}.txt`); the join back to `edgar_mmf_filings` and to `edgar_filings`. |
| seq | integer · not null | PK part 2. 0-based index of this position within the submission's schedule of portfolio securities, assigned by the parser via `.enumerate()`. It is NOT a source field - N-MFP gives a position no identifier, and CUSIP is missing on up to 8 % of rows. |
| reg_cik | bigint | Registrant (filer) CIK, denormalized from the parent filing for joins. |
| series_id | text | SEC series identifier of the holding money-market fund (`S000011990`), denormalized from the parent filing. The fund side of every join. |
| available_date | date | The parent filing's EDGAR dissemination date - when the public could first see this position. Denormalized so a point-in-time query needs no join. The point-in-time anchor. |
| occurred_on | date | The report month end this position is held AS OF (`reportDate`). The real-world event date; always earlier than `available_date`, and up to 60 days earlier on the pre-2016-05 embargoed generation. |
| generation | text · not null | Which N-MFP XML vocabulary this row was parsed from - `N-MFP`, `N-MFP1`, `N-MFP2` or `N-MFP3`. Denormalized from the parent filing because it is what says which source tag fed each column below, and which columns are legitimately NULL for the era rather than missing. |
| issuer_name | text | Issuer name (`nameOfIssuer`, gen-1 `InvestmentIssuer`), e.g. `Australia & New Zealand Banking Group Ltd.`. |
| title | text | Security title as filed (`titleOfIssuer`, gen-1 `InvestmentTitle`), normally the issuer plus the coupon, e.g. `Australia & New Zealand Banking Group Ltd. 3.100000%`. |
| cusip | text | Security CUSIP (`CUSIPMember`). Present on 92-100 % of positions depending on generation; indexed. |
| isin | text | Security ISIN (`ISINId`). Introduced with N-MFP1; absent on gen-1, and present on ~73 % of modern positions. |
| lei | text | Issuer Legal Entity Identifier (`LEIID`). Introduced with N-MFP1 at ~25 % coverage, reaching 100 % on N-MFP3. |
| other_unique_id | text | A filer-supplied identifier used when the security has no CUSIP, ISIN or LEI (`otherUniqueId`, e.g. `05599Y002_3.00_0102`). Free-form and NOT resolvable to a security master - stored so the position is still traceable, never as a substitute identifier. |
| issuer_cik | bigint | SEC CIK of the ISSUER where the filer supplied one (`cik` inside the position block, gen-1 `EntityCentralIndexKey`). Present on 4-40 % of positions; the cleanest join from a money-fund position to an EDGAR filer. |
| investment_category | text | Rule 2a-7 investment category exactly as filed - `U.S. Treasury Debt`, `Variable Rate Demand Note`, `U.S. Treasury Repurchase Agreement, if collateralized only by U.S. Treasuries`, … From `investmentCategory` (gen 2-4) or gen-1 `InvestmentTypeDomain`. Stored verbatim and never normalised: the category vocabulary itself changed with the 2014 reform, and `generation` is what says which vocabulary the string belongs to. |
| maturity_date_wam | date | Maturity date used for the WEIGHTED-AVERAGE MATURITY calculation (`investmentMaturityDateWAM`, gen-1 `InvestmentMaturityDate`). For a floating-rate note this is the next RATE RESET, not the final maturity - which is why it is a different column from the two below. |
| maturity_date_wal | date | Maturity date used for the WEIGHTED-AVERAGE LIFE calculation (`investmentMaturityDateWAL`). Ignores interest-rate resets but honours demand features. NULL on gen-1, which does not report a WAL date. |
| final_legal_maturity_date | date | The security's FINAL LEGAL maturity date (`finalLegalInvestmentMaturityDate`) - when principal is contractually due, ignoring every reset and demand feature. The spread between this and `maturity_date_wam` is the fund's hidden duration. |
| yield_pct | double precision | The security's annualised yield at the reporting date (`yieldOfTheSecurityAsOfReportingDate`) as a decimal fraction exactly as filed (`0.0314` = 3.14 %), never rescaled. Not reported on gen-1. |
| coupon | double precision | The security's stated coupon rate as filed. Introduced with N-MFP3 (present on ~97 % of its positions); NULL on all earlier generations, where the coupon is only recoverable by parsing it out of `title`. |
| principal_amount | double precision | Principal amount held, to the nearest cent (`InvestmentOwnedBalancePrincipalAmount`). **Gen-1 only** - N-MFP1 onwards stopped reporting a position's principal or share count entirely and report only value, which is the single largest information loss across the four generations. |
| amortized_cost | double precision | Amortized cost of this position (gen-1 `AvailableForSaleSecuritiesAmortizedCost`). Gen-1 only; later generations report amortized cost only at the series level. |
| value_including_sponsor_support | double precision | Value of the position INCLUDING any sponsor/capital support (`includingValueOfAnySponsorSupport`, gen-1 `InvestmentOwnedAtFairValue`). The headline value. |
| value_excluding_sponsor_support | double precision | Value of the position EXCLUDING any sponsor/capital support (`excludingValueOfAnySponsorSupport`, gen-1 `valueOfSecurityExcludingValueOfCapitalSupportAgreement`). Where this is below the including-support value, the sponsor is propping up the position - the fact the pair of columns exists to expose. |
| pct_of_net_assets | double precision | Position as a fraction of the fund's net assets (`percentageOfMoneyMarketFundNetAssets`, gen-1 `InvestmentOwnedPercentOfNetAssets`) exactly as filed (`0.0883` = 8.83 %), never rescaled. |
| eligibility_category | text | Rule 2a-7 eligibility CATEGORY as a string - `First Tier Security` / `Second Tier Security`. From gen-1 `rating` and N-MFP1 `securityRated` ONLY. The tier vocabulary was abolished by the 2014 reform, which replaced it with the boolean below; the two are a genuine regime break and are never collapsed into one column. |
| is_eligible_security | boolean | Whether the security is an eligible security under Rule 2a-7 (`securityEligibilityFlag`, Y/N). N-MFP2 and N-MFP3 only - it is the post-2014-reform replacement for `eligibility_category`, not a synonym. |
| has_demand_feature | boolean | Whether the security carries a demand feature (`securityDemandFeatureFlag`, gen-1 `doesSecurityHaveDemandFeature`) - a put that shortens its effective maturity. |
| has_guarantee | boolean | Whether the security carries a guarantee (`securityGuaranteeFlag`, gen-1 `doesSecurityHaveGuarantee`). |
| has_enhancements | boolean | Whether the fund relies on credit enhancements for this security (`securityEnhancementsFlag`). |
| is_daily_liquid | boolean | Whether the position counts toward the fund's DAILY liquid assets (`dailyLiquidAssetSecurityFlag`). Not reported on gen-1. Present on ~69 % of N-MFP2 positions - a genuinely optional tag there, not a parse gap. |
| is_weekly_liquid | boolean | Whether the position counts toward the fund's WEEKLY liquid assets (`weeklyLiquidAssetSecurityFlag`). The 30 % Rule 2a-7 constraint is computed over these. |
| is_illiquid | boolean | Whether the fund classifies the position as illiquid (`illiquidSecurityFlag`, gen-1 `isThisIlliquidSecurity`). |
| is_level3 | boolean | Whether the position is carried at ASC 820 fair-value level 3 (`securityCategorizedAtLevel3Flag`) - unobservable inputs, i.e. a mark nobody can verify. Not reported on gen-1. Note this is a BOOLEAN, unlike N-PORT's three-valued `fair_val_level`, which is why the two datasets do not share a column. |
| treats_as_acquisition_of_underlying | boolean | Whether the fund treats this as an acquisition of the securities underlying a repo (`fundAcqstnUndrlyngSecurityFlag`, gen-1 `isFundTreatingAsAcquisitionUnderlyingSecurities`) - i.e. looks through the repo to its collateral for diversification purposes. |
| is_repurchase_agreement | boolean · not null | Whether this position is a repurchase agreement at all - true when the source carried a `repurchaseAgreement` block (gen-1 `repurchaseAgreementList`). Stored as its own flag so a repo can be counted without inspecting the JSON below. |
| is_open_repurchase_agreement | boolean | Whether the repo is an OPEN (no stated maturity) repo (`repurchaseAgreementOpenFlag`). NULL when the position is not a repo. |
| repo_collateral | jsonb · not null | The repo's collateral, one JSON object per `collateralIssuers` block: issuer name, maturity date, coupon, principal amount, value and the collateral category. A nested one-to-many with no flat home - and the only place the fund's ACTUAL credit exposure is stated, since the repo counterparty is what `issuer_name` names. Empty array when not a repo. |
| nrsro_ratings | jsonb · not null | NRSRO credit ratings assigned to the security, one JSON object per rating block: the rating agency and the rating. A nested one-to-many. The source element is renamed in EVERY generation (`designatedNRSROList` → `designatedNrsro` → `NRSRO` → `assigningNRSRORating`), which is exactly the silent-rename trap this table's alias map exists to defeat. Empty array when unrated. |
Access edgar_mmf_holdings
Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.
import obscura
client = obscura.Client("obs_live_…")
df = client.query(
dataset="edgar_mmf_holdings",
symbols=["NVDA", "AAPL"],
start="2024-01-01",
)
Create a free account Browse all 122 datasets
Frequently asked questions
What is in the edgar_mmf_holdings dataset?
Every security a money-market fund held, month by month, from Form N-MFP: issuer, CUSIP/ISIN/LEI, value, weight, maturity, yield, and whether the position counts as daily-liquid, weekly-liquid or illiquid - plus repo collateral and NRSRO ratings where reported. Coverage begins 2016-05, matching edgar_mmf_filings. One **security held by a money-market fund**, as reported on one monthly SEC Form N-MFP: the issuer, its identifiers, the three maturity dates Rule 2a-7 cares about, the value including and excluding sponsor support, the percent of the fund's net assets, and the daily/weekly/illiquid classifications that decide whether the fund is compliant. # This is NOT `edgar_nport_holdings`, and the overlap measurement is why Both describe "a fund's position on a date", so the union was the obvious design. Read against real documents it collapses. Of `edgar_nport_holdings`' 24 data columns, an N-MFP position can fill **9 at best** - issuer name, title, CUSIP, ISIN, LEI, value, percent of net assets, a maturity date and a category - and two of those are semantic stretches: N-PORT's `asset_cat` is a short code vocabulary (`EC`, `DBT`) while N-MFP's `investmentCategory` is a Rule 2a-7 sentence (`"U.S. Treasury Repurchase Agreement, if collateralized only by U.S. Treasuries"`), and N-PORT reports ONE `maturityDt` while N-MFP reports THREE distinct maturities whose divergence is the entire point of the form. The other **15 N-PORT columns would be NULL on every N-MFP row** - `ticker`, `balance`, `units`, `cur_cd`, `payoff_profile`, `issuer_cat`, `inv_country`, `fair_val_level`, `is_restricted_sec`, the three securities-lending flags, `coupon_kind`, `annualized_rt`, `is_default` - because a money fund reports value rather than share count and does not report any of them. Running the other way, **~16 fields here have no N-PORT home at all**, four of which are nested one-to-many lists (repo collateral, NRSRO ratings, demand-feature providers, guarantors) that a flat holdings row cannot hold. A union table would be roughly 62 % NULL from both directions and would still lose the nested lists. # FOUR GENERATIONS, AND THE RENAMES ARE SILENT SEC rewrote this form's XML three times, and every rewrite renamed concepts rather than only adding them. Measured field-presence over 28 filings / 2,809 positions across the four vocabularies: | concept | `N-MFP` | `N-MFP1` | `N-MFP2` | `N-MFP3` | |---|---|---|---|---| | issuer | `InvestmentIssuer` | `nameOfIssuer` | `nameOfIssuer` | `nameOfIssuer` | | value | `InvestmentOwnedAtFairValue` | `includingValueOfAnySponsorSupport` | same | same | | pct of NAV | `InvestmentOwnedPercentOfNetAssets` | `percentageOfMoneyMarketFundNetAssets` | same | same | | illiquid | `isThisIlliquidSecurity` | `illiquidSecurityFlag` | same | same | | eligibility | `rating` (category) | `securityRated` (category) | `securityEligibilityFlag` (Y/N) | same | | NRSRO block | `designatedNRSROList` | `designatedNrsro` | `NRSRO` | `assigningNRSRORating` | A parser written to one spelling yields a column that is NULL for an entire era - silently, because NULL is legal. That is the `edgar_abs_assets` defect class, and the defence here is the same: an explicit per-generation alias table plus a parse-time check that every position carries a value and a percent. **The generation is read from the DOCUMENT, never from the date.** The four vocabularies overlap inside single quarters at both transitions - 2016 QTR2 carries 546 `N-MFP` and 1,039 `N-MFP1`, 2016 QTR4 carries 484 `N-MFP1` and 935 `N-MFP2` - and amendments cross eras freely. See `docs/datasets/edgar_mmf_holdings.md`.
How do I avoid look-ahead bias with edgar_mmf_holdings?
Filter on edgar_mmf_holdings.available_date, the day the publisher made the row public. For this dataset that date is derived as follows - filing_join: available_date is the EDGAR DISSEMINATION day of the parent N-MFP submission, taken from the bronze object's own day partition (edgar_docs::available_date), never from FILED AS OF DATE. WHY: original N-MFP filings were embargoed 60 days under the original Rule 30b1-7 and are absent from every daily index probed between 2011-07-12 and 2016-04-06, while the quarterly full index attributes thousands of them to those dates; the embargo lifted with N-MFP1, whose originals appear same-day from 2016-05-06. occurred_on is the report month end the position is held AS OF, and is always earlier than availability. Evidence class B; see docs/availability/edgar_mmf_holdings.md. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.
In what formats can I get edgar_mmf_holdings?
As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="edgar_mmf_holdings". The column schema is public at https://api.obscura.trade/v1/catalog/edgar_mmf_holdings.
Can I filter edgar_mmf_holdings by company or symbol?
Yes. edgar_mmf_holdings carries series_id, the column the API's symbols filter resolves against.
How often is edgar_mmf_holdings updated?
Obscura refreshes edgar_mmf_holdings on a every 6 hours schedule - that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.