shares)
return quarters;
}
- ///
- /// Order fill event handler.
- ///
- /// Order event details
public override void OnOrderEvent(OrderEvent orderEvent)
{
if (orderEvent.Status == OrderStatus.Filled)
diff --git a/SEC13FAlgorithm.py b/SEC13FAlgorithm.py
index f7aae52..0353570 100644
--- a/SEC13FAlgorithm.py
+++ b/SEC13FAlgorithm.py
@@ -18,18 +18,11 @@
class SEC13FAlgorithm(QCAlgorithm):
'''Example algorithm using the SEC Form 13F institutional holdings dataset as a source of alpha.
It follows one manager, Pershing Square, through seven of the names it reports: it holds them
- all when the first quarter arrives, and from then on only those the manager added to.
-
- The dataset publishes what each manager filed and nothing else, so the change this trades on is
- worked out here: a point is every position reported for the security on one filing date, the
- manager's lines are picked out by CIK, and the quarter they describe is period_end.
-
- The 13F symbols returned by add_data are signals, not tradeable securities, so every name is
- added twice: once as the tradeable equity and once as the custom data subscribed on it.'''
+ all when the first quarter arrives, and from then on only those the manager added to. No filing
+ states a change, so the comparison between two reported quarters is worked out here.'''
# Pershing Square Capital Management, and Pershing Square Inc., which has reported the same
- # positions since the June 2026 quarter while the former files only a notice. A manager is
- # followed by CIK, and a change of reporting entity is a change of CIK.
+ # positions since the June 2026 quarter. A change of reporting entity is a change of CIK.
MANAGERS = {1336528, 2026053}
def initialize(self) -> None:
@@ -39,12 +32,8 @@ def initialize(self) -> None:
self.set_end_date(2026, 8, 31)
self.set_cash(100000)
- # The shares the manager reported for each equity, by the quarter they describe.
self._shares_by_equity = {}
-
- # The newest quarter the managers have reported, for any name.
self._latest_period = datetime.min
-
self._rebalance = False
for ticker in ["META", "UBER", "QSR", "MSFT", "BN", "HTZ", "AMZN"]:
@@ -54,17 +43,15 @@ def initialize(self) -> None:
def on_data(self, slice: Slice) -> None:
for data_symbol, point in slice.get(SEC13FHoldings).items():
- # The data symbol carries the equity it was subscribed on as its underlying.
- equity = data_symbol.underlying
-
# One point per filing date, carrying every position every manager reported for the
- # security that day. An amendment would restate lines already counted and an option
- # line states the shares under the contracts, so both are left out of the share count.
+ # security that day. An amendment restates lines already counted and an option line
+ # states the shares under the contracts, so both are left out of the share count.
for holding in point:
if (holding.manager_cik not in self.MANAGERS or holding.form_type != "13F-HR"
or holding.amount_type != "SH" or holding.put_call is not None):
continue
+ equity = data_symbol.underlying
shares = self._shares_by_equity[equity]
shares[holding.period_end] = shares.get(holding.period_end, 0) + (holding.amount or 0)
self._latest_period = max(self._latest_period, holding.period_end)
@@ -81,26 +68,18 @@ def on_data(self, slice: Slice) -> None:
self._rebalance = False
- # With one quarter known, hold what the manager holds. With two, hold what it added to.
selected = []
for equity, shares in self._shares_by_equity.items():
- periods = sorted(shares)
- quarters = [shares[period] for period in periods]
-
- # The manager's trades, which no filing states: the change between two reported quarters.
- if len(quarters) > 1:
- # A quarter the manager opened the position in reports no shares before it.
- change = f" ({quarters[-1] / quarters[-2] - 1:+.1%})" if quarters[-2] > 0 else ""
- self.log(f"{self.time:%Y-%m-%d} {equity.value}: {quarters[-2]:,.0f} -> {quarters[-1]:,.0f} shares"
- f"{change} between {periods[-2]:%Y-%m-%d} and {periods[-1]:%Y-%m-%d}")
+ quarters = [shares[period] for period in sorted(shares)]
# A position sold out of has no line in the new quarter, so its newest period stays
# behind the newest the manager reported anywhere. Taken for the name's own latest
- # quarter, it would go on being compared with the quarter before it and held forever.
- # Not being reported is a report of no shares.
- if periods and periods[-1] < self._latest_period:
+ # quarter, it would be compared with the quarter before it and held forever. Not
+ # being reported is a report of no shares.
+ if shares and max(shares) < self._latest_period:
quarters.append(0)
+ # With one quarter known, hold what the manager holds. With two, hold what it added to.
if quarters and (quarters[0] > 0 if len(quarters) == 1 else quarters[-1] > quarters[-2]):
selected.append(equity)
@@ -108,7 +87,6 @@ def on_data(self, slice: Slice) -> None:
self.liquidate()
return
- self.log(f"{self.time:%Y-%m-%d} holding {', '.join(equity.value for equity in selected)}")
self.set_holdings([PortfolioTarget(equity, 1 / len(selected)) for equity in selected],
liquidate_existing_holdings=True)
diff --git a/SEC13FHolding.cs b/SEC13FHolding.cs
index f9dc156..8fd371d 100644
--- a/SEC13FHolding.cs
+++ b/SEC13FHolding.cs
@@ -89,9 +89,9 @@ public class SEC13FHolding : BaseData
public string FormType { get; set; }
///
- /// For an amendment, whether it restates the whole report or only adds holdings. The
- /// distinction decides whether the amendment replaces the original filing or supplements
- /// it, and the SEC leaves it to the filer to declare. Empty on an original filing.
+ /// For an amendment, whether it restates the whole report or only adds holdings:
+ /// RESTATEMENT replaces the original filing and NEW HOLDINGS supplements it. The SEC
+ /// leaves the distinction to the filer to declare. Empty on an original filing.
///
public string AmendmentType { get; set; }
diff --git a/listing-about-whales.md b/listing-about-whales.md
index c4007b8..f617936 100644
--- a/listing-about-whales.md
+++ b/listing-about-whales.md
@@ -1,53 +1,34 @@
## Introduction
SEC Whales is the ownership side of the SEC's filings: who holds what, reported by the holders
-themselves. The US Securities and Exchange Commission requires a large holder to say so, and the
-filings that carry those disclosures are published through EDGAR as they are made. This product
-collects them, one source at a time, and publishes each filing as it was filed.
-
-It ships today with Form 13F institutional holdings. Every institutional investment manager
-exercising discretion over at least 100 million dollars must file a Form 13F within 45 days of the
-end of a calendar quarter, listing the covered securities it holds, from the second quarter of 2013
-to the present. Further ownership filings are added to the product as they are built.
+themselves. It collects those disclosures one source at a time and publishes each filing as it was
+made. It ships today with Form 13F institutional holdings, which every manager exercising
+discretion over at least 100 million dollars must file within 45 days of the end of a calendar
+quarter, from the second quarter of 2013 to the present.
Nothing is summed, counted or averaged. The holders of a name, the shares institutions hold between
them, quarter over quarter change and concentration are all derivable from a day's records, and no
-filing states any of them, so they are left to the algorithm rather than invented here. Counting
-distinct filer CIKs across the records is a line of code and is what the demonstration algorithms
-do.
-
-The reporting lag is the product, not an inconvenience to be hidden. A 13F position is typically 45
-to 135 days old by the time it reaches the public record, with late amendments arriving years later,
-so a record is stamped with the filing date and carries the quarter it describes in `PeriodEnd`.
-Measured across 11,761 filings in one window, the lag from the reported quarter end to the filing
-date runs minimum 0 days, p10 16, median 42, p90 48, maximum 6,596, with 10.4 percent of filings
-arriving later than the 45 day deadline. Delivering a position on the quarter end it describes would
-inject every day of that gap as look-ahead, so LEAN delivers it when the dataset could first publish
-it.
-
-A record's `Time` is its filing date and its `EndTime` is midnight that night. LEAN emits a point at
-its end time, so a day's filings all reach the algorithm at 00:00 the following day, after EDGAR has
-finished listing that day at about 22:05 ET, so a backtest never reads a filing before it existed.
-A filing whose EDGAR index came days late is added to history under its filing date.
+filing states any of them, so they are left to the algorithm rather than invented here.
+
+The reporting lag is the product, not an inconvenience to be hidden. A position is typically 45 to
+135 days old when it reaches the public record, late amendments arrive years later, and 10.4
+percent of filings miss the deadline, so a record is stamped with the date it was filed and carries
+the quarter it describes in `PeriodEnd`. Delivering it on that quarter end would inject every day
+of the gap as look-ahead.
An algorithm receives one `SEC13FHoldings` point per security per filing date, holding every
-position reported for that security that day. Several managers file on the same day, and a single
+position reported for that security that day. Several managers file on the same day, and one
manager can report the same security on more than one line when the investment discretion differs,
-which the rules allow and Berkshire Hathaway does with Moody's. Those records stay apart, because
-folding them together would state a number no filing contains.
+which Berkshire Hathaway does with Moody's. Those records stay apart.
## About the Provider
The [U.S. Securities and Exchange Commission](https://www.sec.gov) is the federal agency that
regulates the US securities markets. Form 13F is filed through EDGAR, the SEC's electronic filing
-system, and the agency's Division of Economic and Risk Analysis republishes those filings as
-structured, tab separated data sets in three month batches. The history is built from those data
-sets. The daily job reads each day's filings from EDGAR itself, the information tables the data sets
-are extracted from, because a batch arrives up to three months after its first filing: compared line
-by line, 48 of 48 filings carry the same lines in both, and on six sample days EDGAR's daily index
-lists exactly the filings the data set holds. The data is public domain, needs no account and no API
-key, and the only access requirement is the descriptive User-Agent header that SEC policy asks of
-all automated readers.
+system, and the agency republishes those filings as structured, tab separated data sets in three
+month batches. The history is built from those data sets, and the daily job reads each day's
+filings from EDGAR itself, because a batch arrives up to three months after its first filing. The
+data is public domain and needs no account and no API key.
## Getting Started
@@ -67,142 +48,19 @@ The following table describes the dataset properties:
| Property | Value |
| --- | --- |
| Start Date | May 2013 |
-| Asset Coverage | 8,520 US Equities |
+| Asset Coverage\* | 18,877 US Equities |
| Data Density | Sparse |
-| Resolution | Daily\* |
+| Resolution\*\* | Daily |
| Timezone | America/New_York |
-\* Positions are reported quarterly, but the managers of one quarter file across roughly fifty
-different days and several quarters are live at once, so publication is close to continuous rather
-than quarterly. In the week of 3 to 7 August 2026, 1,462 filings carrying positions produced 34,002
-security days across 8,520 securities.
-
-The history begins on 2013-05-20, the first filing date in the SEC structured data set, whose first
-archive covers the second quarter of 2013. Anything earlier exists only as raw filings in the EDGAR
-full index and is not part of this dataset.
+\* The coverage includes all assets since the start date. It increases over time. Positions are
+reported by CUSIP, which is licensed and not published, and resolve to a LEAN `Symbol` for 97.0
+percent of the lines of one recent week and 95.0 percent of the fourth quarter of 2020; the
+crosswalk that reaches the hardest names begins in late 2019, so a security delisted before then
+is the likeliest to be missing.
-Each record in a point carries the following fields, exactly as the manager filed them:
-
-| Property | Meaning |
-| --- | --- |
-| `AccessionNumber` | EDGAR accession number of the submission the line was reported on |
-| `ManagerCik` | Central Index Key of the filing manager, its stable identity across name changes |
-| `ManagerName` | The manager's name as its most recent cover page states it, without commas, null for an unknown CIK |
-| `PeriodEnd` | End of the quarter the position is reported for, the SEC PERIODOFREPORT |
-| `FormType` | 13F-HR for a holdings report, 13F-HR/A for an amendment |
-| `AmendmentType` | On an amendment, whether it restates the whole report or only adds holdings |
-| `AmendmentNumber` | Sequence number of the amendment, null on an original filing |
-| `TitleOfClass` | Class of the security as the manager titled it, such as COM or CL A |
-| `Amount` | Size of the position: a share count when `AmountType` is SH, a principal amount when PRN |
-| `AmountType` | SH for shares, PRN for a principal amount |
-| `ReportedValue` | Market value exactly as the manager stated it, in the unit the filing used |
-| `ValueScale` | The power of ten that turns `ReportedValue` into dollars: 3, 0 or -3 |
-| `MarketValue` | `ReportedValue` in whole dollars, derived from the two above and never stored |
-| `PutCall` | Call or Put when the line is an option on the security, null when it is the security |
-| `InvestmentDiscretion` | SOLE, DFND or OTR, as the filing states it |
-| `OtherManager` | The other managers sharing the position, as the cover page numbers them, separated by semicolons; empty when there are none |
-| `VotingSole` | Shares over which the manager holds sole voting authority |
-| `VotingShared` | Shares over which the manager shares voting authority |
-| `VotingNone` | Shares over which the manager holds no voting authority |
-| `ConfidentialOmitted` | True when the submission withheld other positions under confidential treatment |
-| `DateReported` | For a previously confidential filing, the date it was originally made |
-
-The CUSIP is not published, being licensed. It resolves the security and stops there.
-
-Option and debt lines are published as what they are rather than folded away. One quarter of the
-source carries 61,400 call lines, 57,443 put lines and 15,698 PRN lines, and a share count that
-swallowed any of them would misstate the position, so `AmountType` and `PutCall` say what each line
-is and the algorithm decides what to count.
-
-`ConfidentialOmitted` is a point-in-time feature rather than a data quality flag. A manager can ask
-the SEC to withhold specific positions temporarily, so a filing marked this way is incomplete by
-design and the withheld positions appear in a later filing.
-
-### The reported value is published as filed
-
-`ReportedValue` is the number the manager wrote. The SEC asked for thousands of dollars before 2023
-and whole dollars after, and filers on both sides of that change ignore the instruction: in Apple's
-December 2019 quarter 85 of 5,365 lines were already in dollars, and scaled as thousands they made
-88 percent of the total, an implied 2,577 dollars a share against a 293.65 close.
-
-`ValueScale` is the one reading in a record the SEC does not publish. It is the power of ten that
-turns the reported number into dollars, decided per line from the filing's period and from how the
-implied price compares with the security's close on the quarter's last trading day: 3 where the
-filing states thousands, 0 where it states dollars, and -3 for a line that overstated its value a
-thousandfold, which three SPY lines of the March 2023 quarter did, carrying 16 percent of that
-quarter's total with them.
-
-It is carried beside the reported number rather than multiplied into it, so what the manager filed
-stays readable and this one judgement stays separable from it. `MarketValue` applies it.
-
-### Amendments are published as filed
-
-An amendment is a record like any other, with its `FormType`, `AmendmentType` and `AmendmentNumber`
-saying what it is. It does not replace the filing it restates and nothing is netted against
-anything. Deciding that a restatement supersedes an earlier figure is a judgement about the data,
-and the filer has already declared which kind of amendment it made, so the algorithm can apply it.
-Most amendments restate the whole report, which is what `RESTATEMENT` in `AmendmentType` means;
-`NEW HOLDINGS` adds to the original.
-
-### A manager can change CIK
-
-A manager whose positions are reported on another manager's filing files a 13F-NT, a notice that
-carries no positions and contributes no records. Pershing Square Capital Management (CIK 1336528)
-reported its own positions through the March 2026 quarter and has filed a notice since, while
-Pershing Square Inc. (CIK 2026053) reports them. Followed by the first CIK alone, the fund appears
-to sell everything in one quarter, so follow every CIK the manager has reported under.
-
-### Coverage is partial by design
-
-Holdings are keyed by CUSIP in the source and resolved to a LEAN `Symbol` before publication, so no
-identifier from the source is shipped. The resolution runs in four steps, each picking up what the
-one before it could not reach:
-
-1. The CUSIP itself, looked up in LEAN's security database. The database repeats some identifiers
- across the listings one company has had, such as the old and the new Alcoa, so every row carrying
- the CUSIP is tried and the one trading under its own ticker on the filing date is kept.
-2. The US ISIN, built arithmetically from the same CUSIP. It reaches issuers whose CUSIP column in
- that database is blank.
-3. The ticker the SEC's own Form N-PORT filings report for the CUSIP, taken back to a `Symbol`
- through the map files. This step needs no security database at all and it is what reaches the
- foreign domiciled issuers whose identifier is really a CINS, for which a constructed US ISIN is
- wrong by construction. Alphabet is one of them.
-4. For an option line, the security the option is written on. A manager reporting options names them
- by the option's own CUSIP, which carries the underlying's six character issuer and issue 90 for
- calls or 95 for puts and appears in no security database. The position is still published as the
- option line it is, with its side in `PutCall`; a line that names no side takes the one its CUSIP
- states. Where the issuer has several funds, as iShares does, every fund's options share one CUSIP,
- and each line goes to the one fund whose quarter-end close its implied price matches, or is dropped.
-
-A security's file holds what managers reported under its CUSIP, which is not always the common
-stock: filers put preferred shares, units and convertibles under the common's CUSIP, and
-`TitleOfClass` is the only field that says so. `ValueScale` takes the values 3, 0 and -3, so a
-filing stated in millions is not brought to dollars.
-
-A fund's reported ticker in step 3 is free text, so that step keeps a match only when the reported
-prices do not say otherwise. A group is dropped when three or more of its prices are not the
-security's own quarter-end close, in dollars or in thousands, which keeps Centerra Gold, Enerflex,
-B2Gold and DeFi Technologies, whose Toronto or fund-reported tickers are CG, EFX, BTO and DEFI, off
-Carlyle, Equifax, a John Hancock fund and the Hashdex DEFI ETF. A CUSIP whose issue number carries
-letters, as a company's debt does, is rejected outright when that issuer already has stock in the
-security database: Apple's 3.45% 2045 bond reached AAPL through a fund administrator's reported
-ticker and added the bond's principal amount to Apple's share count as ten thousand shares, and a
-bond near par against a stock near the same number agrees with a price test by coincidence. The
-iBonds ETFs, whose CUSIPs also carry letters, are themselves in the security database and resolve at
-step 1 without ever reaching that rule.
-
-Step 4 is only taken when the issuer has exactly one equity issue in the database. iShares writes
-seventy equity issues under 464287 and SPDR eleven under 81369Y, and an option CUSIP there names one
-of them without saying which; filing the position under the wrong fund would be worse than not
-publishing it. In the week of 3 to 7 August 2026 that step recovered 2,947 of the 6,083 lines
-reported on an option CUSIP and left the ambiguous rest out.
-
-The real limit is step 3's own history: N-PORT begins in late 2019, so a security that stopped
-trading before then is not reachable through it at any depth and can only be resolved if the first
-two steps already found it. Measured over the whole chain, 97.0 percent of the reported lines in the
-week of 3 to 7 August 2026 and 95.0 percent of those in the fourth quarter of 2020 resolved to a
-security and were published. The product therefore covers most of the reported positions but not
-every reported name, and it says so rather than implying full coverage.
+\*\* Positions are reported quarterly, but the managers of one quarter file across roughly fifty
+different days and several quarters are live at once, so publication is close to continuous.
## Example Applications
@@ -216,12 +74,8 @@ Examples include the following strategies:
- Following one manager through `ManagerCik`, reading what a single fund reported quarter after
quarter rather than what the market did in aggregate, which is what the demonstration algorithms
do with Pershing Square.
-- Screening out thinly followed names, requiring a minimum number of reporting managers before a
- security is tradeable.
- Reading the reported put and call lines alongside the share positions to see whether managers are
hedging a name rather than simply owning it.
-- Separating sole from defined discretion with `InvestmentDiscretion`, which is the difference
- between a manager's own book and the assets it merely directs.
## Meta
@@ -229,10 +83,10 @@ Examples include the following strategies:
| --- | --- |
| name | SEC Whales |
| url | sec-whales |
-| vendorName | U.S. Securities and Exchange Commission |
+| vendorName | Securities and Exchange Commission |
| website | https://www.sec.gov |
| history | May 2013 |
-| reach | 8,520 US Equities |
+| reach | 18,877 US Equities |
| shortDescription | Ownership filings from the SEC as their holders filed them, starting with Form 13F institutional holdings |
| priceCTA | Free in Cloud |
| delivery | cloud only |
@@ -244,7 +98,7 @@ Licensing card:
```html
Free access to SEC Whales in QuantConnect Cloud for use in backtesting or live trading.
- - Every position reported on Form 13F since 2013, as each manager filed it, across 8,520 US Equities
+ - Every position reported on Form 13F since 2013, as each manager filed it, across 18,877 US Equities
- Manager, reported quarter, share or principal amount, value, option side, discretion and voting authority per position
- Curated, clean data
diff --git a/listing-documentation-whales.md b/listing-documentation-whales.md
index cefe341..c7d54e6 100644
--- a/listing-documentation-whales.md
+++ b/listing-documentation-whales.md
@@ -8,7 +8,7 @@ can access the data later in your algorithm.
class SEC13FDataAlgorithm(QCAlgorithm):
def initialize(self) -> None:
- # Coverage runs 2013-05-20 to 2026-05-29
+ # Coverage starts 2013-05-20
self.set_start_date(2020, 10, 1)
self.set_end_date(2020, 12, 31)
self.set_cash(100000)
@@ -23,7 +23,7 @@ public class SEC13FDataAlgorithm : QCAlgorithm
public override void Initialize()
{
- // Coverage runs 2013-05-20 to 2026-05-29
+ // Coverage starts 2013-05-20
SetStartDate(2020, 10, 1);
SetEndDate(2020, 12, 31);
SetCash(100000);
@@ -38,14 +38,9 @@ public class SEC13FDataAlgorithm : QCAlgorithm
To get the current Form 13F data, index the current [Slice](https://www.quantconnect.com/docs/v2/writing-algorithms/key-concepts/time-modeling/timeslices)
with the dataset **Symbol**. **Slice** objects deliver unique events to your algorithm as they
-happen, but the **Slice** may not contain data for your dataset at every time step. Positions are
-reported quarterly, but the managers of one quarter file across roughly fifty different days, so
-publication is close to continuous: a widely held name such as AAPL carries filings on 58 of the 66
-weekdays of the fourth quarter of 2020. Check that the **Slice** contains the data you want before
-you index it.
-
-A point is a `SEC13FHoldings` collection holding every position reported for that security on that
-filing date, so iterate it rather than reading a single value off it.
+happen, so check that the **Slice** contains the data you want before you index it. A point is a
+`SEC13FHoldings` collection holding every position reported for that security on that filing date,
+so iterate it rather than reading a single value off it; the collection's own `Value` is zero.
```python
def on_data(self, slice: Slice) -> None:
@@ -67,130 +62,64 @@ public override void OnData(Slice slice)
}
```
-To iterate through all of the dataset objects in the current **Slice**, call the **Get** method. A
-filing deadline puts thousands of securities into the same day, so this is the usual shape when you
-subscribe to more than one name.
-
-```python
-def on_data(self, slice: Slice) -> None:
- for dataset_symbol, holdings in slice.get(SEC13FHoldings).items():
- for holding in holdings:
- self.log(f"{dataset_symbol} {holding.manager_cik}: {holding.amount}")
-```
-```csharp
-public override void OnData(Slice slice)
-{
- foreach (var kvp in slice.Get())
- {
- var datasetSymbol = kvp.Key;
- foreach (SEC13FHolding holding in kvp.Value)
- {
- Log($"{datasetSymbol} {holding.ManagerCik}: {holding.Amount}");
- }
- }
-}
-```
-
-A point's `Time` is the filing date and its `EndTime` is midnight that night. LEAN emits a point at
-its end time, so a day's filings reach your algorithm at 00:00 the following day, after EDGAR has
-finished listing that day at about 22:05 ET, so a backtest never reads a filing before it existed.
-A filing whose EDGAR index came days late is added to history under its filing date.
-
-The filing date is not the quarter the position describes, and the gap between them cannot be
-derived: measured across 11,761 filings in one window it runs minimum 0 days, p10 16, median 42, p90
-48, maximum 6,596, and 10.4 percent of filings arrive later than the 45 day deadline. A point that
-arrives today therefore carries positions from a quarter that ended typically 45 to 135 days ago,
-with late amendments arriving years later, and one day of filings can carry several different
-reported quarters. Read `PeriodEnd` whenever you need the quarter a position describes rather than
-the day it arrived.
+A point's `Time` is the filing date, not the quarter the position describes. Read `PeriodEnd`
+whenever you need the quarter, because one day of filings can carry several different ones and the
+gap between the two dates runs from zero days to several years.
-### Nothing is aggregated
-
-The dataset publishes what each manager filed. It states no holder count, no total shares and no
-total value, because no 13F filing states any of them. Counting is the algorithm's job and it is a
-few lines:
+The dataset states no holder count, no total shares and no total value, because no 13F filing states
+any of them. Counting is the algorithm's job. To iterate through all of the dataset objects in the
+current **Slice**, call the **Get** method, which is the usual shape once you subscribe to more than
+one name:
```python
def on_data(self, slice: Slice) -> None:
for dataset_symbol, holdings in slice.get(SEC13FHoldings).items():
managers = {holding.manager_cik for holding in holdings
if holding.put_call is None and holding.amount_type == "SH"}
- shares = sum(holding.amount or 0 for holding in holdings
- if holding.put_call is None and holding.amount_type == "SH")
- self.log(f"{dataset_symbol}: {len(managers)} managers filed, {shares} shares")
+ self.log(f"{dataset_symbol}: {len(managers)} managers filed")
```
```csharp
public override void OnData(Slice slice)
{
foreach (var kvp in slice.Get())
{
- var shareLines = kvp.Value.Cast()
+ var managers = kvp.Value.Cast()
.Where(holding => !holding.PutCall.HasValue && holding.AmountType == "SH")
- .ToList();
- var managers = shareLines.Select(holding => holding.ManagerCik).Distinct().Count();
- var shares = shareLines.Sum(holding => holding.Amount.GetValueOrDefault());
- Log($"{kvp.Key}: {managers} managers filed, {shares} shares");
+ .Select(holding => holding.ManagerCik).Distinct().Count();
+ Log($"{kvp.Key}: {managers} managers filed");
}
}
```
-Two things to know when you count. A manager files once per quarter on a day of its own choosing,
-so breadth builds up across filing dates rather than appearing on any single one: accumulate across
-points instead of reading one day. And a single manager can report the same security on more than
-one line when the investment discretion differs, which the rules allow, so records outnumber
-managers and counting distinct `ManagerCik` is not the same as counting records.
-
-Filter on `AmountType` and `PutCall` before you add anything up. A PRN line is a principal amount of
-debt, not a share count, and an option line states the shares underlying the contracts rather than a
-holding of the security. Adding either into a share total misstates the position.
-
-### Reading the value
+Filter on `AmountType` and `PutCall` first, as that example does: a PRN line is a principal amount
+of debt and an option line states the shares underlying the contracts, so adding either into a share
+total misstates the position. A manager files once per quarter on a day of its own choosing, so
+breadth builds up across filing dates rather than appearing on any single one, and one manager can
+report the same security on several lines when the discretion differs, so distinct `ManagerCik` is
+not the same as records. One that stops appearing has not necessarily sold: a fund whose positions
+move to another reporting entity files a 13F-NT, which carries none, and the new entity reports
+them under its own CIK.
`ReportedValue` is the number the manager wrote, in whatever unit the filing used, and `ValueScale`
-is the power of ten that turns it into dollars: 3 for a filing stating thousands, 0 for one stating
-dollars, and -3 for a line that overstated its value a thousandfold. `MarketValue` applies the scale
-and is what you want in almost every case.
-
-```python
-value = holding.market_value # dollars
-raw = holding.reported_value # as filed, with holding.value_scale beside it
-```
-```csharp
-var value = holding.MarketValue; // dollars
-var raw = holding.ReportedValue; // as filed, with holding.ValueScale beside it
-```
-
-The collection's own `Value` is zero and carries no meaning: LEAN builds the collection and sets
-only its symbol and timestamps, so there is nothing for a single number to be. Read the records.
-
-### Amendments and confidential filings
-
-`FormType` is 13F-HR for a holdings report and 13F-HR/A for an amendment, with `AmendmentType`
-saying whether the amendment restates the whole report or only adds holdings, and
-`AmendmentNumber` its sequence. An amendment is published beside the filing it restates and
-replaces nothing, so if your strategy wants a restatement to supersede an earlier figure it has to
-apply it. Amendments are rare: 24 of the 1,462 filings carrying positions in the week of 3 August
-2026.
-
-`ConfidentialOmitted` is true when the submission withheld other positions under confidential
-treatment. Such a filing is incomplete by design and the withheld positions surface in a later
-filing, so treat a flagged record as a floor rather than the full picture. `DateReported` carries
-the date a previously confidential filing was originally made, and is null on the roughly 998
-filings in a thousand that were never confidential.
-
-### Empty values
-
-A field the filing left empty is null, and null is different from a reported zero: a manager
-reporting no shared voting authority files a zero, while one that withheld the figure files nothing.
-Guard the arithmetic.
+is the power of ten that turns it into dollars, which `MarketValue` applies for you. A field the
+filing left empty is null, which is not a reported zero, so guard the arithmetic.
```python
+value = holding.market_value # dollars
+raw = holding.reported_value # as filed, with holding.value_scale beside it
shares = holding.amount or 0
```
```csharp
+var value = holding.MarketValue; // dollars
+var raw = holding.ReportedValue; // as filed, with holding.ValueScale beside it
var shares = holding.Amount.GetValueOrDefault();
```
+An amendment, which `FormType` marks 13F-HR/A, is published beside the filing it amends and
+replaces nothing. `AmendmentType` says which kind it is: `RESTATEMENT` replaces the original
+filing, `NEW HOLDINGS` only adds to it, and applying either is the algorithm's job. `ConfidentialOmitted` marks a submission that withheld other positions under confidential
+treatment, which makes that record a floor rather than the full picture.
+
## Historical Data
To get historical Form 13F data, call the **History** method with the
@@ -200,44 +129,28 @@ dataset **Symbol**. If there is no data in the period you request, the history r
# pandas Series, one entry per filing date, each holding the list of that day's positions
history_series = self.history(self._dataset_symbol, timedelta(days=60), Resolution.DAILY)
-# DataFrame, one row per reported position
+# DataFrame, one row per reported position, columns named after the record's fields in lower case
history_df = self.history(SEC13FHoldings, self._dataset_symbol, timedelta(days=60), Resolution.DAILY, flatten=True)
-# Dataset objects, one per filing date
+# SEC13FHoldings objects, one per filing date, each carrying its records
history_bars = self.history[SEC13FHoldings](self._dataset_symbol, timedelta(days=60), Resolution.DAILY)
```
```csharp
var history = History(_datasetSymbol, TimeSpan.FromDays(60), Resolution.Daily);
```
-The three shapes differ more than usual for this dataset, because a point is a collection.
-
-Without `flatten`, Python gives you a **Series** and not a DataFrame: one entry per filing date,
-indexed by symbol and time, each entry holding the list of that day's positions. Reading a column
-off it will not work, because it has none.
-
-With `flatten=True` you get a DataFrame with **one row per reported position**, indexed by time and
-symbol, whose columns are the record's fields in lower case: `accessionnumber`, `managercik`,
-`managername`, `periodend`, `formtype`, `amendmenttype`, `amendmentnumber`, `titleofclass`,
-`amount`, `amounttype`, `reportedvalue`, `valuescale`, `marketvalue`, `putcall`, `investmentdiscretion`, `othermanager`,
-`votingsole`, `votingshared`, `votingnone`, `confidentialomitted`, `datereported`. This is the form
-to use for anything cross-sectional. Sixty days of AAPL history is one Series of 39 entries or a
-DataFrame of 6,333 rows, which is the difference the flag makes.
-
-Note that a reported zero occasionally comes back as `NaN` in the flattened frame rather than as
-`0`, which is LEAN's pandas conversion rather than a gap in the data. Treat the two alike:
-
-```python
-shares = history_df["votingshared"].fillna(0)
-```
-
-The typed history and the C# history give you the `SEC13FHoldings` objects themselves, one per
-filing date, each carrying its records. That is the form that keeps the day's positions grouped.
+The three shapes differ more than usual for this dataset, because a point is a collection. Without
+`flatten`, Python gives you a **Series** and not a DataFrame, so reading a column off it will not
+work. With `flatten=True` you get one row per reported position, which is the form to use for
+anything cross-sectional: sixty days of AAPL history read at the start of the fourth quarter of
+2020 is one Series of 36 entries or a DataFrame of 4,144 rows. A reported zero occasionally comes
+back there as `NaN` rather than as `0`, which is LEAN's pandas conversion rather than a gap in the
+data, so treat the two alike with `history_df["votingshared"].fillna(0)`.
Ask for a time span rather than a bar count. A bar count is read in daily bars and the dataset
-publishes on filing dates only, so what comes back depends on how widely the security is held rather
-than on the count you asked for. AAPL carries filings on 58 of the 66 weekdays of the fourth quarter
-of 2020, while a thinly held name carries them on a handful of days a year.
+publishes on filing dates only, so what comes back depends on how widely the security is held
+rather than on the count you asked for. AAPL carries filings on 58 of the 66 weekdays of the fourth
+quarter of 2020, while a thinly held name carries them on a handful of days a year.
For more information about historical data, see [History Requests](https://www.quantconnect.com/docs/v2/writing-algorithms/historical-data/history-requests).