Fund Securities Lending and Brokerage (N-CEN)
The per-fund detail inside each N-CEN annual census: securities lending, brokerage commissions, advisers and sub-advisers, custodians, expense waivers, lines of credit and interfund lending - plus, for index funds, tracking difference and tracking error both before and after expenses.
edgar_ncen_series - the dataset name to pass to the Obscura API.
What one row means
One **fund** inside a Form N-CEN annual census - Item C, one block per series. This is where the census stops describing a fund complex and starts describing a FUND: this series' adviser and sub-advisers, its custodian chain, its transfer agent, its average net assets, the commissions it paid, what it earned lending securities, and whether its fees are capped. `edgar_ncen_filings` is the registrant row this hangs off. # THE FAN-OUT IS THE REASON THIS TABLE EXISTS EQ Advisors Trust's 2024 census (`0001752724-25-052137`) is **3.4 MB carrying 110 Item C blocks**; Pacific Select Fund states 122; Securian Funds Trust 11; Boyar Value Fund 1. A registrant-only table would discard 109 funds' worth of net assets and adviser identity from a single filing. # WHAT IS SCALAR AND WHAT IS JSON WAS MEASURED, NOT GUESSED Over the 110 series blocks of `0001752724-25-052137`, the per-series multiplicity of each service-provider list is: | element | count per series | modelled as | |---|---|---| | `investmentAdviser` | **1 on 110/110** | scalar columns | | `transferAgent` | **1 on 110/110** | scalar columns | | `admin` | **1 on 110/110** | scalar column | | `shareholderServicingAgent` | **1 on 110/110** | scalar column | | `subAdviser` | 0-5 | `sub_advisers` JSON | | `pricingService` | 0-5 | `pricing_services` JSON | | `custodian` | **1-50** | `custodians` JSON + count | | `broker` (Item C.16, ten largest by commission) | 0-10 | `top_brokers` JSON | | `principalTransaction` (Item C.17, ten largest) | 0-10 | `top_principal_counterparties` JSON | | `brokerDealer` (Item C.15, ALL used) | **15-115** | count + aggregate ONLY | A single global-equity series names 50 custodians because it holds a sub-custodian in every market it settles in - so "the custodian" is not a scalar fact and modelling it as one would have silently kept the first alphabetically. `brokerDealer` is the one list deliberately NOT warehoused: 115 entries × 110 series in ONE filing, across 26,240 filings, is the shape `tasks::export::copy_csv_bytes` cannot carry (it buffers a whole table as CSV in RAM, ~10-15 GB practical ceiling). Its aggregate survives intact in `aggregate_commission`, which is the Form's own total, not a sum this pipeline computed. # THE TWO AVERAGE-NET-ASSET COLUMNS ARE NOT INTERCHANGEABLE The Form asks for MONTHLY average net assets from most funds and DAILY average net assets from money market funds. Over the 110 EQ Advisors series, 109 state `monthly_avg_net_assets` and exactly 1 states `daily_avg_net_assets`. Some filers state both with `0.00` in the unused one (Boyar), so **a zero here is ambiguous between "not applicable" and "genuinely zero"** and is preserved verbatim rather than being cleaned to NULL - the filer's own zero is the record, and normalising it away would be unrecoverable. See `docs/datasets/edgar_ncen_series.md`.
One row per One row per (N-CEN submission, fund series), keyed by (accession, series_id). The row says 'in this registrant's census for this fiscal year, this series had these advisers, these custodians and this much in average net assets'. The key includes the accession rather than being (series_id, period_end) because an N-CEN/A restates a series' figures under a NEW accession and both statements must survive - collapsing them would destroy the only record that a census was corrected. A series that states no seriesId (measured on some N-2 closed-end registrants, which have exactly one fund and no series identifier) is keyed on the EMPTY STRING sentinel, because a NULL cannot sit in a composite primary key; read it as 'the registrant's only fund, unidentified'..
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_ncen_series: filing_join: available_date is the EDGAR DISSEMINATION day of the parent submission, taken from the bronze object's own day partition - the same value carried on the parent edgar_ncen_filings row, denormalised onto this table so a point-in-time query over fund economics needs no join. Never the header's FILED AS OF DATE. period_end is the census's fiscal period (Item A.2) and precedes availability by the statutory 75-day window; it is what the MEASURES on this row describe and is never an availability anchor.
Refresh cadence
Obscura refreshes edgar_ncen_series 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 - 54 columns
The full public column list for edgar_ncen_series, with the meaning of every field. The same
schema is served unauthenticated at https://api.obscura.trade/v1/catalog/edgar_ncen_series.
| Column | Type | Description |
|---|---|---|
| accession | text · not null | PK part. SEC accession number of the N-CEN submission this series block came from, e.g. `0001752724-25-052137`. The foreign key into `edgar_ncen_filings`. |
| series_id | text · not null | PK part. SEC series identifier for the fund, `S000010926` - the same identifier `edgar_nport_filings`, `edgar_fund_series_classes` and `edgar_fund_risk_return` carry, which is what makes this table joinable to holdings and to a market ticker. The EMPTY STRING (never NULL) where the registrant states none: a NULL cannot participate in a composite primary key, and dropping the row would erase the fund. |
| filename | text · not null | The registrant's SEC EDGAR Archives path for the parent submission. Denormalised from `edgar_ncen_filings` so this table stands alone as the declared `filing_join` via-column. |
| available_date | date | Public availability date: the parent submission's EDGAR DISSEMINATION day, from the bronze object's own day partition. The point-in-time anchor and the only date a backtest may key on. |
| period_end | date | The fiscal period these figures DESCRIBE - the census's `reportEndingPeriod`. Every measure on this row is an annual figure for the year ending on this date, and every one of them is stale by at least the statutory 75-day filing window when it becomes available. NEVER an availability date. |
| cik | bigint | Central Index Key of the REGISTRANT (the trust), denormalised from the parent filing. Note this is the trust's CIK, not the series' - SEC assigns no CIK to a series, which is why `series_id` exists. |
| fund_name | text | The fund's name as the Form states it (`mgmtInvFundName`), e.g. `Allspring Managed Account CoreBuilder Shares Series M`. The filer's spelling, which is not necessarily the prospectus name. |
| fund_lei | text | The SERIES' own Legal Entity Identifier (`mgmtInvLei`), distinct from the registrant's. Funds within one trust each carry their own LEI, so this is the identifier that joins a fund to non-SEC reference data. NULL where the filer states `N/A`. |
| is_first_filing_by_fund | boolean | TRUE when this is the FUND's first appearance on an N-CEN (`isFirstFilingByFund`) - a fund launch, stated by the filer rather than inferred from an absence. |
| is_fund_liquidated | boolean | TRUE when the fund was LIQUIDATED during the reporting period (`isFundLiquidated`). Together with `num_terminated_class` this is the fund-death record, and it is why a naive "the series stopped appearing" heuristic is unnecessary here. |
| num_authorized_class | integer | How many share classes the fund is AUTHORIZED to issue (`numAuthorizedClass`). |
| num_added_class | integer | How many share classes were ADDED during the period (`numAddedClass`). |
| num_terminated_class | integer | How many share classes were TERMINATED during the period (`numTerminatedClass`). Non-zero is a real share-class closure, which usually precedes a fee-structure change or a fund merger. |
| share_classes | jsonb · not null | JSON array of the fund's share classes with outstanding shares as `[{"class_id":"C000030254","class_name":…,"ticker":"BOYAX"}]`. **The ticker is frequently absent and that is information, not a gap** - the 110 EQ Advisors series carry 208 classes and ZERO tickers, because insurance-dedicated classes are not exchange-traded. `edgar_fund_series_classes` is the dedicated crosswalk when a ticker is what you need. |
| fund_types | jsonb · not null | JSON array of the fund-type classifications the filer selected, e.g. `["Money Market Fund"]`, `["Index Fund"]`; `[]` where none applies. The Form states these in THREE places and all three are unioned: a wrapped `<fundTypes><fundType>`, a BARE `<fundType>` directly on the series block, and an `indexFundInfo/@fundType` ATTRIBUTE. Securian's `0001752724-25-052131` uses all three shapes across its own 11 series. The filer's `N/A` placeholder - 8 of Securian's 9 - is normalised AWAY rather than stored, so `fund_types @> '["N/A"]'` cannot match a non-answer as though it were a category. |
| is_index_fund_affiliated | boolean | TRUE when the fund is an INDEX fund whose target index is AFFILIATED with the adviser (`indexFundInfo/isIndexFundAffiliated`) - a self-indexing ETF, where the same firm builds the benchmark and manages the fund tracking it. NULL when the fund is not an index fund at all, which is the majority. |
| is_index_fund_exclusive | boolean | TRUE when the fund has EXCLUSIVE rights to the index it tracks (`indexFundInfo/isIndexFundExclusive`) - i.e. no competitor may launch a fund on the same benchmark. NULL for a non-index fund. |
| index_tracking_diff_before_expense | numeric | **TRACKING DIFFERENCE BEFORE EXPENSES**, in percent: the fund's annual return minus its target index's return, gross of the fund's own fees (`indexFundReturnDiffBeforeExpense`). Negative means the fund trailed its index before it charged anything - i.e. the shortfall is sampling, cash drag or transaction costs rather than fees. NULL for a non-index fund. Together with the after-expense figure this is the only per-fund, per-year tracking record SEC publishes anywhere. |
| index_tracking_diff_after_expense | numeric | **TRACKING DIFFERENCE AFTER EXPENSES**, in percent (`indexFundReturnDiffAfterExpense`) - what the shareholder actually experienced against the benchmark. The gap between this and `index_tracking_diff_before_expense` is the fee drag, measured rather than quoted: Securian's SFT Index 500 Fund reports 0.00 before and −0.35 after. NULL for a non-index fund. |
| index_tracking_stdev_before_expense | numeric | **TRACKING ERROR BEFORE EXPENSES**: the standard deviation of the DAILY return difference against the index, in percent (`indexFundReturnDailyStdevBeforeExpense`). Difference measures the level of the miss; this measures its volatility, and a fund can have near-zero tracking difference with large tracking error. NULL for a non-index fund. |
| index_tracking_stdev_after_expense | numeric | **TRACKING ERROR AFTER EXPENSES**: the same daily standard deviation net of fund expenses (`indexFundReturnDailyStdevAfterExpense`), in percent. NULL for a non-index fund. |
| is_non_diversified | boolean | TRUE when the fund is NON-DIVERSIFIED under Section 5(b)(2) (`isNonDiversifiedCompany`) - i.e. it may concentrate. A structural risk attribute stated annually. |
| is_foreign_subsidiary | boolean | TRUE when the fund invests through a FOREIGN SUBSIDIARY (`isForeignSubsidiary`), typically a Cayman subsidiary used for commodity exposure. The structure that makes a fund's stated holdings incomplete on their own. |
| is_securities_lending_authorized | boolean | TRUE when the fund is AUTHORISED to lend portfolio securities (`isFundSecuritiesLending`). Distinct from `did_lend_securities`, which says whether it actually did - 27 of 110 EQ Advisors series name no securities-lending agent at all. |
| did_lend_securities | boolean | TRUE when the fund ACTUALLY lent securities during the period (`didFundLendSecurities`). The pair with `is_securities_lending_authorized` separates permission from practice; conflating them overstates lending activity across the corpus. |
| avg_securities_on_loan_value | numeric | Average value of portfolio securities ON LOAN over the period (`avgPortfolioSecuritiesValue`), in USD. The denominator for a securities-lending yield; `net_income_securities_lending` is the numerator. |
| net_income_securities_lending | numeric | The fund's NET INCOME from securities lending for the period (`netIncomeSecuritiesLending`), in USD - after the lending agent's split. One of the few places a fund's lending revenue is stated as a number rather than buried in an expense ratio. |
| monthly_avg_net_assets | numeric | The fund's MONTHLY average net assets for the period (`mnthlyAvgNetAssets`), in USD. The size measure for a non-money-market fund - 109 of 110 EQ Advisors series state this one. **A zero is ambiguous**: some filers state `0.00` here when they answered in `daily_avg_net_assets` instead, so read both columns before treating a zero as a size. |
| daily_avg_net_assets | numeric | The fund's DAILY average net assets for the period (`dailyAvgNetAssets`), in USD - what money market funds answer instead of the monthly figure. Exactly 1 of the 110 EQ Advisors series states it. Same zero ambiguity as `monthly_avg_net_assets`, in the other direction. |
| aggregate_commission | numeric | TOTAL brokerage commissions the fund paid during the period (`aggregateCommission`), in USD. **The Form's own total across ALL broker-dealers**, not a sum over the stored `top_brokers` - which is why the unbounded Item C.15 list can be left unwarehoused without losing the aggregate. |
| aggregate_principal_transactions | numeric | TOTAL value of PRINCIPAL transactions (purchases and sales combined) the fund transacted during the period (`principalAggregatePurchase`), in USD. The Form's own total; `top_principal_counterparties` names only the ten largest counterparties behind it. |
| broker_dealer_count | integer · not null | How many broker-dealers the fund used in total (Item C.15, `brokerDealer` entries). **The list itself is deliberately not stored** - measured 15 to 115 entries per series, which across 110 series in one filing and 26,240 filings would exceed the export path's practical table ceiling. This count and `aggregate_commission` preserve what the list aggregates to. |
| adviser_name | text | The fund's investment adviser NAME (Item C.7). A scalar because it measured exactly 1 per series on 110 of 110 EQ Advisors series - a fund has one adviser and delegates to sub-advisers, which are in `sub_advisers`. |
| adviser_file_number | text | The adviser's Investment Advisers Act file number, e.g. `801-19283`. |
| adviser_crd | text | The adviser's CRD number, e.g. `000105255` - the join key into FINRA/IAPD adviser data. Text, not an integer: leading zeros are significant and the Form permits `N/A`, normalised to NULL. |
| adviser_lei | text | The adviser's Legal Entity Identifier. NULL where the filer states `N/A`, which is the majority - advisers state a CRD far more often than an LEI. |
| is_adviser_hired | boolean | TRUE when the adviser was HIRED during the reporting period (`isInvestmentAdviserHired`) - a management change, stated rather than inferred from a name diff between years. |
| sub_advisers | jsonb · not null | JSON array of SUB-advisers as `[{"name":…,"file_number":…,"crd":…,"lei":…,"is_affiliated":…,"is_hired":…}]`; `[]` where none. Measured 0-5 per series (23 of 110 EQ Advisors series have none), which is why this is an array while `adviser_name` is a scalar. The sub-adviser is who actually picks the securities in a manager-of-managers structure. |
| transfer_agent_name | text | The fund's transfer agent NAME (Item C.9) - 1 per series on 110 of 110 measured, hence a scalar. |
| administrator_name | text | The fund's ADMINISTRATOR name (Item C.12) - 1 per series on 110 of 110 measured. |
| shareholder_servicing_agent_name | text | The fund's shareholder servicing agent NAME (Item C.11) - 1 per series on 110 of 110 measured. |
| custodians | jsonb · not null | JSON array of custodians as `[{"name":…,"lei":…,"state":…,"country":…,"is_affiliated":…,"is_sub_custodian":…,"custody_type":…}]`. **Measured 1 to 50 per series** - a global-equity fund names a sub-custodian in every settlement market it touches - which is exactly why this is not a scalar column. `custodian_count` is its length. |
| custodian_count | integer · not null | How many custodians the fund names. Measured 1-50 per series; the long tail is sub-custodian networks and is itself a proxy for how many markets the fund settles in. |
| pricing_services | jsonb · not null | JSON array of pricing/valuation services as `[{"name":…,"lei":…,"state":…,"country":…,"is_affiliated":…}]`; `[]` where none. Measured 0-5 per series. Which vendor marks a fund's book is a real dependency and the Form is the only place it is stated. |
| top_brokers | jsonb · not null | JSON array of the TEN broker-dealers that received the largest brokerage commissions from this fund (Item C.16) as `[{"name":…,"file_number":…,"crd":…,"lei":…,"state":…,"country":…,"gross_commission":…}]`. Bounded at ten by the Form itself - measured 0-10 per series - which is why the whole list is stored while the unbounded Item C.15 list is not. |
| top_principal_counterparties | jsonb · not null | JSON array of the TEN counterparties with the largest PRINCIPAL transactions (Item C.17) as `[{"name":…,"file_number":…,"crd":…,"lei":…,"state":…,"country":…,"total_purchase_sale":…}]`. Bounded at ten by the Form; measured 0-10 per series. This is the dealer relationship that a commission-only view misses entirely, because a principal trade pays no commission. |
| is_expense_limitation_in_place | boolean | TRUE when an EXPENSE LIMITATION (fee cap) was in place during the period (`isExpenseLimitationInPlace`). The stated-rather-than-derived version of "is this fund's headline expense ratio subsidised". |
| is_expense_reduced_or_waived | boolean | TRUE when expenses were actually REDUCED OR WAIVED during the period (`isExpenseReducedOrWaived`). The pair with `is_expense_limitation_in_place` separates a cap that exists from a cap that bound. |
| is_fees_waived_recoupable | boolean | TRUE when waived FEES are RECOUPABLE by the adviser in later periods (`isFeesWaivedRecoupable`) - i.e. today's low expense ratio carries a future claw-back against the fund. A materially different economic position from a permanent waiver, and the Form distinguishes them. |
| has_line_of_credit | boolean | TRUE when the fund had a LINE OF CREDIT during the period (`hasLineOfCredit`). |
| is_interfund_lending | boolean | TRUE when the fund LENT to affiliated funds under an interfund lending order (`isInterfundLending`). |
| is_interfund_borrowing | boolean | TRUE when the fund BORROWED from affiliated funds (`isInterfundBorrowing`). Together with `is_interfund_lending` this is the intra-complex liquidity network, and the two directions are separate columns because a fund can do either, both or neither. |
| is_swing_pricing | boolean | TRUE when the fund applied SWING PRICING during the period (`isSwingPricing`). Very rarely TRUE in US funds and therefore worth finding when it is. |
| relied_on_rules | jsonb · not null | JSON array of the 1940 Act rules the fund states it relied on (`relyOnRuleType`), e.g. `["Rule 32a-4 (17 CFR 270.32a-4)"]` - verbatim rule citations as the filer selected them, not normalised to rule numbers, because the Form's own strings are the controlled vocabulary here. |
Access edgar_ncen_series
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_ncen_series",
symbols=["NVDA", "AAPL"],
start="2024-01-01",
)
Create a free account Browse all 122 datasets
Frequently asked questions
What is in the edgar_ncen_series dataset?
The per-fund detail inside each N-CEN annual census: securities lending, brokerage commissions, advisers and sub-advisers, custodians, expense waivers, lines of credit and interfund lending - plus, for index funds, tracking difference and tracking error both before and after expenses. One **fund** inside a Form N-CEN annual census - Item C, one block per series. This is where the census stops describing a fund complex and starts describing a FUND: this series' adviser and sub-advisers, its custodian chain, its transfer agent, its average net assets, the commissions it paid, what it earned lending securities, and whether its fees are capped. `edgar_ncen_filings` is the registrant row this hangs off. # THE FAN-OUT IS THE REASON THIS TABLE EXISTS EQ Advisors Trust's 2024 census (`0001752724-25-052137`) is **3.4 MB carrying 110 Item C blocks**; Pacific Select Fund states 122; Securian Funds Trust 11; Boyar Value Fund 1. A registrant-only table would discard 109 funds' worth of net assets and adviser identity from a single filing. # WHAT IS SCALAR AND WHAT IS JSON WAS MEASURED, NOT GUESSED Over the 110 series blocks of `0001752724-25-052137`, the per-series multiplicity of each service-provider list is: | element | count per series | modelled as | |---|---|---| | `investmentAdviser` | **1 on 110/110** | scalar columns | | `transferAgent` | **1 on 110/110** | scalar columns | | `admin` | **1 on 110/110** | scalar column | | `shareholderServicingAgent` | **1 on 110/110** | scalar column | | `subAdviser` | 0-5 | `sub_advisers` JSON | | `pricingService` | 0-5 | `pricing_services` JSON | | `custodian` | **1-50** | `custodians` JSON + count | | `broker` (Item C.16, ten largest by commission) | 0-10 | `top_brokers` JSON | | `principalTransaction` (Item C.17, ten largest) | 0-10 | `top_principal_counterparties` JSON | | `brokerDealer` (Item C.15, ALL used) | **15-115** | count + aggregate ONLY | A single global-equity series names 50 custodians because it holds a sub-custodian in every market it settles in - so "the custodian" is not a scalar fact and modelling it as one would have silently kept the first alphabetically. `brokerDealer` is the one list deliberately NOT warehoused: 115 entries × 110 series in ONE filing, across 26,240 filings, is the shape `tasks::export::copy_csv_bytes` cannot carry (it buffers a whole table as CSV in RAM, ~10-15 GB practical ceiling). Its aggregate survives intact in `aggregate_commission`, which is the Form's own total, not a sum this pipeline computed. # THE TWO AVERAGE-NET-ASSET COLUMNS ARE NOT INTERCHANGEABLE The Form asks for MONTHLY average net assets from most funds and DAILY average net assets from money market funds. Over the 110 EQ Advisors series, 109 state `monthly_avg_net_assets` and exactly 1 states `daily_avg_net_assets`. Some filers state both with `0.00` in the unused one (Boyar), so **a zero here is ambiguous between "not applicable" and "genuinely zero"** and is preserved verbatim rather than being cleaned to NULL - the filer's own zero is the record, and normalising it away would be unrecoverable. See `docs/datasets/edgar_ncen_series.md`.
How do I avoid look-ahead bias with edgar_ncen_series?
Filter on edgar_ncen_series.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 submission, taken from the bronze object's own day partition - the same value carried on the parent edgar_ncen_filings row, denormalised onto this table so a point-in-time query over fund economics needs no join. Never the header's FILED AS OF DATE. period_end is the census's fiscal period (Item A.2) and precedes availability by the statutory 75-day window; it is what the MEASURES on this row describe and is never an availability anchor. 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_ncen_series?
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_ncen_series". The column schema is public at https://api.obscura.trade/v1/catalog/edgar_ncen_series.
Can I filter edgar_ncen_series by company or symbol?
Yes. edgar_ncen_series carries series_id, the column the API's symbols filter resolves against.
How often is edgar_ncen_series updated?
Obscura refreshes edgar_ncen_series 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.