diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3558fe4..cc3963d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,12 +37,21 @@ jobs: path: Lean - name: Move Lean - run: mv Lean ../Lean + # The runners are self-hosted and ../Lean sits outside the checkout, so it survives + # between runs. Left in place, mv moves into it instead of renaming, and the step + # fails on whichever runner has already built this repo once. + run: | + rm -rf ../Lean + mv Lean ../Lean - name: Run build and tests run: | # BuildDataSource dotnet build ./QuantConnect.DataSource.csproj /p:Configuration=Release /v:quiet /p:WarningLevel=1 + # BuildDataProcessing + # Built here too: without it a syntax error anywhere in the downloader lands on master + # with a green check, since nothing else in this workflow compiles that project. + dotnet build ./DataProcessing/DataProcessing.csproj /p:Configuration=Release /v:quiet /p:WarningLevel=1 # BuildTests dotnet build ./tests/Tests.csproj /p:Configuration=Release /v:quiet /p:WarningLevel=1 # Run Tests diff --git a/DataProcessing/DataProcessing.csproj b/DataProcessing/DataProcessing.csproj index 697ba6a..92ba250 100644 --- a/DataProcessing/DataProcessing.csproj +++ b/DataProcessing/DataProcessing.csproj @@ -16,4 +16,9 @@ + + diff --git a/DataProcessing/Program.cs b/DataProcessing/Program.cs index 6367eff..8143af8 100644 --- a/DataProcessing/Program.cs +++ b/DataProcessing/Program.cs @@ -1,4 +1,4 @@ -/* +/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * @@ -15,25 +15,77 @@ using QuantConnect.Configuration; using QuantConnect.Logging; +using QuantConnect.Util; using System; using System.Diagnostics; -using System.Globalization; -using System.IO; namespace QuantConnect.DataProcessing { /// - /// Console program to convert from raw SEC data to a formatted form usable by LEAN + /// Console program to convert from raw SEC data to a formatted form usable by LEAN. + /// + /// The repository ships two unrelated SEC datasets and the "dataset-name" config key selects + /// which one a run processes: + /// + /// - "reports" (the default): the 10-K, 10-Q and 8-K filings, downloaded with + /// and converted with . + /// - "13f" (): Form 13F institutional holdings, every position + /// as its manager filed it, from the SEC's structured data sets and EDGAR's daily indexes. + /// + /// The default keeps a job that sets no dataset-name doing exactly what it did before the key + /// existed. /// public class Program { - public static void Main() + /// + /// The "dataset-name" config value that selects the shipped SEC reports dataset, and the + /// value assumed when the key is not set. + /// + private const string ReportsDatasetName = "reports"; + + /// Config key that asks the 13F run to rebuild the whole history instead of one date. + internal const string RebuildHistoryKey = "sec-13f-rebuild-history"; + + /// + /// Entrypoint of the program. The exit code is returned rather than handed to + /// from inside the work: that call does not unwind the stack, + /// so every finally block written for the failure paths would be skipped on all of them. + /// + /// Zero on success, one on any failure + public static int Main() + { + var dataset = Config.Get("dataset-name", ReportsDatasetName).Trim().ToLowerInvariant(); + + switch (dataset) + { + case ReportsDatasetName: + return ProcessReports(); + + case SEC13FDownloader.DatasetName: + return Process13F(); + + default: + Log.Error($"DataProcessing.Main(): Unknown dataset-name '{dataset}'. Valid options: " + + $"{ReportsDatasetName}, {SEC13FDownloader.DatasetName}"); + return 1; + } + } + + /// + /// Downloads and converts the SEC reports dataset for the deployment date. + /// + /// Zero on success, one on any failure + private static int ProcessReports() { - var processingDateValue = Environment.GetEnvironmentVariable("QC_DATAFLEET_DEPLOYMENT_DATE"); - var processingDate = DateTime.ParseExact(processingDateValue, "yyyyMMdd", CultureInfo.InvariantCulture); - var temporaryFolder = Config.Get("temp-output-directory", "/temp-output-directory"); - var rawDataDirectory = Config.Get("raw-data-folder", "/raw"); - var secDataDirectory = Path.Combine(rawDataDirectory, "alternative", "sec"); + // The reports dataset has no full rebuild, so it always needs a date. + if (!SECProcessingContext.TryCreate(null, out var context)) + { + return 1; + } + + var processingDate = context.DeploymentDate.Value; + var temporaryFolder = context.OutputRoot; + var secDataDirectory = context.RawDirectory; Log.Trace($"DataProcessing.Main(): Processing {processingDate:yyyy-MM-dd}"); var timer = Stopwatch.StartNew(); @@ -62,10 +114,58 @@ public static void Main() catch (Exception e) { Log.Error(e, $"DataProcessing.Main(): {processingDate} Exception while processing SEC data"); - Environment.Exit(1); + return 1; } - Environment.Exit(0); + return 0; } + + /// + /// Downloads and converts the Form 13F institutional holdings dataset for the deployment date, + /// folding it into the published history, or rebuilds the whole history when asked to. + /// + /// Zero on success, one on any failure + private static int Process13F() + { + if (!SECProcessingContext.TryCreate(RebuildHistoryKey, out var context)) + { + return 1; + } + + Log.Trace($"DataProcessing.Process13F(): writing {SEC13FDownloader.DatasetName} to {context.OutputDirectory}" + + (context.DeploymentDate == null ? " for the full history" : $" for {context.DeploymentDate:yyyy-MM-dd}")); + + var timer = Stopwatch.StartNew(); + SEC13FDownloader downloader; + try + { + downloader = new SEC13FDownloader(context.OutputDirectory, context.ProcessedDirectory, + context.DeploymentDate, context.RawDirectory); + } + catch (Exception err) + { + Log.Error(err, $"DataProcessing.Process13F(): The {SEC13FDownloader.DatasetName} downloader failed to be constructed"); + return 1; + } + + try + { + downloader.Run(); + + timer.Stop(); + Log.Trace($"DataProcessing.Process13F(): Conversion finished in time {timer.Elapsed}"); + return 0; + } + catch (Exception err) + { + Log.Error(err, $"DataProcessing.Process13F(): The {SEC13FDownloader.DatasetName} downloader exited unexpectedly"); + return 1; + } + finally + { + downloader.DisposeSafely(); + } + } + } } diff --git a/DataProcessing/SEC13FClosePrices.cs b/DataProcessing/SEC13FClosePrices.cs new file mode 100644 index 0000000..b2c09d3 --- /dev/null +++ b/DataProcessing/SEC13FClosePrices.cs @@ -0,0 +1,94 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +namespace QuantConnect.DataProcessing +{ + /// + /// Raw closing prices from LEAN's coarse universe files, one per trading day, which is what a + /// reported VALUE over SSHPRNAMT is checked against. The close of the quarter's last trading day + /// is known before any filing for that quarter can be made, so the check needs no other filing + /// and gives the same answer on the day a filing is read as in a rebuild years later. + /// + internal sealed class SEC13FClosePrices + { + /// Days walked back from a quarter end for its last trading day: a long weekend and a holiday. + private const int DaysToLookBack = 7; + + private readonly string _coarseDirectory; + private readonly Dictionary> _closesByDay = new(); + + public SEC13FClosePrices(string coarseDirectory) + { + _coarseDirectory = coarseDirectory; + } + + /// Whether the coarse files are there at all, so the caller can say so once. + public bool Available => Directory.Exists(_coarseDirectory); + + /// + /// The raw close of a security on the last trading day on or before the quarter end, or + /// null. Never a day after the filing date: a period typed years ahead would otherwise read + /// a price nobody had when the filing was made. + /// + public decimal? Close(SecurityIdentifier security, DateTime periodEnd, DateTime filingDate) + { + var last = periodEnd.Date < filingDate.Date ? periodEnd.Date : filingDate.Date; + for (var back = 0; back < DaysToLookBack; back++) + { + var closes = ClosesOn(last.AddDays(-back)); + if (closes != null) + { + // The last trading day decides: a security missing from it did not trade then. + return closes.TryGetValue(security.ToString(), out var close) && close > 0m ? close : null; + } + } + + return null; + } + + /// The closes of one trading day, keyed by security identifier, or null when the day has no file. + private Dictionary ClosesOn(DateTime day) + { + if (_closesByDay.TryGetValue(day, out var closes)) + { + return closes; + } + + var path = Path.Combine(_coarseDirectory, $"{day.ToString(DateFormat.EightCharacter, CultureInfo.InvariantCulture)}.csv"); + if (File.Exists(path)) + { + closes = new Dictionary(StringComparer.Ordinal); + + // sid, ticker, close, volume, dollar volume, has fundamentals, price factor, split factor + foreach (var line in File.ReadLines(path)) + { + var fields = line.Split(','); + if (fields.Length > 2 && decimal.TryParse(fields[2], NumberStyles.Any, CultureInfo.InvariantCulture, out var close)) + { + closes[fields[0]] = close; + } + } + } + + _closesByDay[day] = closes; + return closes; + } + } +} diff --git a/DataProcessing/SEC13FDownloader.cs b/DataProcessing/SEC13FDownloader.cs new file mode 100644 index 0000000..717fbee --- /dev/null +++ b/DataProcessing/SEC13FDownloader.cs @@ -0,0 +1,2578 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using QuantConnect.Configuration; +using QuantConnect.Data.Auxiliary; +using QuantConnect.DataSource; +using QuantConnect.Interfaces; +using QuantConnect.Lean.Engine.DataFeeds; +using QuantConnect.Logging; +using QuantConnect.Securities; +using QuantConnect.Util; + +[assembly: InternalsVisibleTo("Tests")] + +namespace QuantConnect.DataProcessing +{ + /// + /// Converts Form 13F filings into LEAN's per-security zips, one entry per filing + /// date. Without QC_DATAFLEET_DEPLOYMENT_DATE it rebuilds the whole history, from the SEC's + /// structured data sets through the last window published and from EDGAR's daily indexes after + /// it; with it, it reads that day from EDGAR, with any recent day whose index came late, and + /// folds them into the published history. + /// + /// Besides the data folders it reads these config keys: sec-user-agent-company-name and + /// sec-user-agent-company-email, which the SEC asks automated readers for; sec-13f-rebuild-history, + /// which a run without a deployment date needs; and, for checks only, sec-13f-edgar-from, + /// sec-13f-edgar-until and sec-13f-rebuild-from, which move the rebuild's EDGAR days and skip the + /// older data sets. From the data folder it reads the map files and security-database.csv, which + /// resolve CUSIPs and tickers, and the coarse universe files, whose quarter-end closes decide + /// VALUE's unit and vet the crosswalk. + /// + public class SEC13FDownloader : IDisposable + { + /// + /// The "dataset-name" config value that selects this downloader. Spelled the same as the + /// data class's ReportFolder, which stays the single source of truth for the output path. + /// + public const string DatasetName = "13f"; + + /// Vendor name (matches the alternative/<vendor> data path). + public static string VendorName => "sec"; + + private const string DataSetsPageUrl = "https://www.sec.gov/data-research/sec-markets-data/form-13f-data-sets"; + private const string SecBaseUrl = "https://www.sec.gov"; + + + // A 13F-NT is a notice that the manager's holdings are reported on someone else's filing. It + // carries no INFOTABLE at all, so it is not a zero position and must not become one. + private const string NoticeSubmissionTypePrefix = "13F-NT"; + + // EDGAR lists a day's filings in its daily index at about 22:05 ET (02:02 to 02:07 UTC the + // next morning over six business days measured in September 2026). The daily job reads that + // index at 01:00 ET the next day with a one hour timeout (schedule "0 1 * * 2-6", Date + // Offset 1), which is why a point ends at midnight after its FILING_DATE and no earlier. + + /// Config key that starts the rebuild's EDGAR days on this yyyyMMdd instead of after the last data set. + internal const string EdgarFromKey = "sec-13f-edgar-from"; + + /// Config key that ends the rebuild's EDGAR days on this yyyyMMdd instead of yesterday. + internal const string EdgarUntilKey = "sec-13f-edgar-until"; + + /// + /// Config key that makes the rebuild skip the data sets ending before this yyyyMMdd. For checks + /// only: the rules do not depend on the old years, and cutting them takes a rebuild from ten + /// minutes to one or two. A published history always starts in 2013. + /// + internal const string RebuildFromKey = "sec-13f-rebuild-from"; + + /// The EDGAR days already folded into the published history, one yyyyMMdd per line. + private const string EdgarStateFileName = "edgar-days.txt"; + + /// + /// Where the filing managers' names live, once each, so that the reported positions can + /// carry nothing but the CIK. + /// + private const string ManagerNamesFileName = "managers.csv"; + + /// How far back a daily run looks for a day whose index EDGAR published late. + private const int EdgarLookbackDays = 10; + + /// + /// How many holdings filings one daily run fetches before leaving the rest of a gap to the + /// runs that follow. Each filing is its own round trip, so this and not a count of days is + /// what a run's hour buys: over the June to August 2026 quarter a day carries 65 filings at + /// the median and 312 at the ninetieth percentile, and the 45 day deadline of 2026-08-14 + /// carries 1,835, the heaviest of the quarter. The budget is that heaviest day, so catching + /// up never asks a run for more work than an ordinary run already does every quarter. + /// + internal const int MaxFilingsPerRun = 2000; + + private const string PeriodFormat = "yyyyMMdd"; + + /// Where the manager's CIK sits in a published row, which is read back by column. + private const int ManagerCikColumn = 2; + + /// Where the accession sits in a published row, which names the filing its line came from. + private const int AccessionColumn = 1; + + // N-PORT quarters folded into the crosswalk: a year reaches every security still trading. + private const int NPortQuartersToFold = 4; + + // The SEC rule: VALUE in thousands for filings before this date, whole dollars from it. + // DetectValueUnits checks each filing against it. + private static readonly DateTime ValueInWholeDollarsFrom = new(2023, 1, 1); + + // How far, in log10, a crosswalk group's median price may sit from the close times a power + // of a thousand and still be that security: about three times either way. Farther is another + // security, as DeFi Technologies at $2 reaching the $129 Hashdex DEFI ETF through its ticker. + private const double PriceMatchTolerance = 0.5; + + private static readonly string[] SecDateFormats = { "dd-MMM-yyyy", "d-MMM-yyyy", "yyyy-MM-dd", "MM/dd/yyyy" }; + + private static readonly Regex ArchiveLinkRegex = new( + @"href=""(?[^""]*?/(?[^""/]+_form13f\.zip))""", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex QuarterArchiveRegex = new( + @"^(?\d{4})q(?[1-4])_form13f\.zip$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex WindowArchiveRegex = new( + @"^(?\d{2}[a-z]{3}\d{4})-(?\d{2}[a-z]{3}\d{4})_form13f\.zip$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private readonly string _destinationDirectory; + private readonly string _processedDataDirectory; + private readonly string _archiveCacheDirectory; + private readonly DateTime? _deploymentDate; + + private readonly SECEdgarClient _edgar = new(); + + private readonly IMapFileProvider _mapFileProvider; + + // The security database's rows by CUSIP body and by ISIN, every row kept, since the database + // repeats identifiers across the listings one company has had. See TradingDefinition. + private readonly Dictionary> _definitionsByCusip; + private readonly Dictionary> _definitionsByIsin; + + /// The equity issues of each six character issuer, for resolving an option CUSIP. + private readonly Dictionary> _equityIssuesByIssuer; + + /// + /// CUSIP and filing date to security, misses included, since each miss scans the whole + /// security database. The date is part of the key because resolution is point in time; the + /// cache is cleared per archive so it stays the size of one window. + /// + private readonly Dictionary<(string Cusip, DateTime Date), SecurityIdentifier> _resolvedCusips = new(); + + /// CUSIPs already tallied in the resolution summary, so its totals stay per CUSIP. + private readonly HashSet _countedCusips = new(StringComparer.Ordinal); + + /// Map file per security, so the point-in-time ticker costs one lookup per date. + private readonly Dictionary _mapFiles = new(); + + /// Accessions already folded in, so a submission can never be counted twice. + private readonly HashSet _processedAccessions = new(StringComparer.Ordinal); + + /// + /// Every filing manager's name by CIK, as the most recent cover page stated it. Published + /// once in managers.csv rather than on each of the reported positions. + /// + private readonly Dictionary _managerNames = new(); + + /// + /// Rows waiting to be staged, flushed per archive. Keyed by security, because several CUSIPs + /// can reach one security and their lines all belong in its file. Each row keeps the ticker + /// of its filing date, which is the file it lands in. + /// + private readonly Dictionary> + _pendingSecurityRows = new(StringComparer.Ordinal); + + /// + /// Where rows wait, one file per security, until the finalize pass. Outside the output + /// folder because everything there is published. + /// + private readonly string _stagingDirectory = + Path.Combine(Path.GetTempPath(), "sec-13f-staging", Guid.NewGuid().ToString("N")); + + /// Every security with rows staged, which is every security this run touched. + private readonly HashSet _stagedSecurities = new(StringComparer.Ordinal); + + /// Ticker and date to the security the map files say owns that ticker then, per archive. + private readonly Dictionary<(string Ticker, DateTime Date), string> _tickerOwners = new(); + + private long _conflictingTickers; + private decimal _conflictingValue; + private readonly List _conflictSample = new(); + + /// + /// CUSIP to the ticker the SEC's N-PORT filings report for it. Built on first use rather than + /// at construction: the archives may all resolve through the security database, and building + /// it downloads a few hundred megabytes per quarter folded in. Settable for tests. + /// + internal Dictionary TickerCrosswalk + { + // Read from the published copy and written with the output, since the raw folder is not + // restored between runs and the next run would otherwise rebuild it from 1.8 GB of N-PORT. + get => _tickerCrosswalk ??= SEC13FTickerCrosswalk.Load(_processedDataDirectory, _destinationDirectory, + NPortQuartersToFold, _edgar.UrlExists, (url, name) => _edgar.DownloadFile(url, name, _archiveCacheDirectory)); + set => _tickerCrosswalk = value; + } + + private Dictionary _tickerCrosswalk; + + /// CUSIP to the security its crosswalk ticker named when the funds reported it. + private readonly Dictionary _crosswalkSecurities = new(StringComparer.Ordinal); + + private long _resolvedByCusip; + private long _resolvedByIsin; + private long _resolvedByTicker; + private long _resolvedByOptionUnderlying; + private long _unresolvedGroups; + private long _malformedCusips; + private decimal _resolvedValue; + private decimal _unresolvedValue; + private readonly HashSet _unresolvedCusips = new(StringComparer.Ordinal); + + /// + /// CUSIPs and filing dates resolved through the crosswalk rather than the security database, + /// cleared with the resolution cache. Their groups are checked against the close, since a fund + /// administrator's ticker can name another company. + /// + private readonly HashSet<(string Cusip, DateTime Date)> _crosswalkResolutions = new(); + + /// CUSIPs and filing dates resolved through the security an option is written on. + private readonly HashSet<(string Cusip, DateTime Date)> _optionResolutions = new(); + + private long _optionSidesInferred; + private long _optionLinesByPrice; + private long _optionLinesWithoutOneMatch; + + private long _debtCusipsRejected; + private long _mismatchedGroups; + private decimal _mismatchedValue; + + /// The quarter-end closes that decide VALUE's unit and vet crosswalk resolutions. + private readonly SEC13FClosePrices _closePrices; + + /// EDGAR days already folded into the history, published so a daily run never reads one twice. + private readonly SortedSet _edgarDays = new(); + + /// The last day folded in before this run, which dates what the published files state. + private DateTime _foldedThrough; + + private readonly DateTime? _edgarFrom; + private readonly DateTime? _edgarUntil; + + /// + /// Creates a new instance writing to , merging with any + /// previously processed data found in . A null + /// rebuilds the history from the data sets and EDGAR; a date + /// reads that day's filings from EDGAR, with any recent day it has not read yet. Downloads are + /// kept under so the next run finds them. + /// + public SEC13FDownloader(string destinationDirectory, string processedDataDirectory, DateTime? deploymentDate, + string rawDataDirectory = null) + { + // The folder comes from the data type rather than a literal, so the writer and the + // reader cannot drift apart. + _destinationDirectory = Path.Combine(destinationDirectory, SEC13FHolding.ReportFolder); + _processedDataDirectory = Path.Combine(processedDataDirectory, SEC13FHolding.ReportFolder); + _deploymentDate = deploymentDate; + + // Downloads land in the raw folder, which the job archives after every run but does not + // restore before the next, so it only saves work within a run. + _archiveCacheDirectory = rawDataDirectory == null + ? Path.Combine(Path.GetTempPath(), "sec-13f-archives") + : Path.Combine(rawDataDirectory, SEC13FHolding.ReportFolder, "archives"); + Directory.CreateDirectory(_archiveCacheDirectory); + + // Zip, not disk: the security master ships map_files_.zip, and the disk provider + // only sees loose csv files, so it would resolve almost nothing without failing. + _mapFileProvider = new LocalZipMapFileProvider(); + _mapFileProvider.Initialize(new DefaultDataProvider()); + + // The security database is in place wherever the job runs, as the map files are. Its rows are + // read here rather than through LEAN's resolver, which keeps only the first row of an + // identifier the database repeats; a data folder without it, as in the unit tests, reads as empty. + var securityDatabasePath = Path.Combine( + Globals.GetDataFolderPath("symbol-properties"), "security-database.csv"); + SecurityDefinition.TryRead(new DefaultDataProvider(), securityDatabasePath, out var definitions); + definitions ??= new List(); + + _definitionsByCusip = IndexDefinitions(definitions, definition => DatabaseCusip(definition.CUSIP)); + _definitionsByIsin = IndexDefinitions(definitions, definition => definition.ISIN); + + // The equity issues each issuer has, which is what an option CUSIP is resolved through. + _equityIssuesByIssuer = _definitionsByCusip.Keys + .Where(cusip => cusip.Length >= IssuerLength + 2 && + char.IsDigit(cusip[IssuerLength]) && char.IsDigit(cusip[IssuerLength + 1])) + .GroupBy(cusip => cusip.Substring(0, IssuerLength), StringComparer.OrdinalIgnoreCase) + .ToDictionary(group => group.Key, + group => group.Distinct(StringComparer.OrdinalIgnoreCase).ToList(), + StringComparer.OrdinalIgnoreCase); + + _closePrices = new SEC13FClosePrices(Path.Combine(Globals.DataFolder, "equity", "usa", "fundamental", "coarse")); + if (!_closePrices.Available) + { + Log.Error("SEC13FDownloader(): the coarse universe files are missing from the data folder. Without the " + + "quarter-end closes VALUE takes the SEC unit rule of each filing's date, and a crosswalk match whose " + + "CUSIP carries letters in its issue number, which only a price can confirm, is dropped."); + } + + _edgarFrom = ParseOptionalDate(Config.Get(EdgarFromKey), EdgarFromKey); + _edgarUntil = ParseOptionalDate(Config.Get(EdgarUntilKey), EdgarUntilKey); + } + + /// A yyyyMMdd config value, or null when the key is not set. + private static DateTime? ParseOptionalDate(string value, string key) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + if (DateTime.TryParseExact(value.Trim(), DateFormat.EightCharacter, CultureInfo.InvariantCulture, + DateTimeStyles.None, out var date)) + { + return date; + } + + throw new ArgumentException($"SEC13FDownloader(): {key} '{value}' is not yyyyMMdd"); + } + + /// + /// Runs the download/convert. Failures throw rather than return false, so the caller logs + /// the actual reason: a guard stopping the run and a network outage are not the same thing. + /// + public void Run() + { + RequireEmptyDestination(); + RequirePublishedHistoryForIncrementalRun(); + ReadEdgarState(); + + if (_deploymentDate == null) + { + // The data sets as far as they reach, then EDGAR day by day: the filings the daily + // job reads, with the stamps it gives them. + var archives = GetArchives(); + var edgarFrom = _edgarFrom ?? archives[archives.Count - 1].End.AddDays(1); + var rebuildFrom = ParseOptionalDate(Config.Get(RebuildFromKey), RebuildFromKey); + var selected = ArchivesBefore(archives, edgarFrom) + .Where(archive => rebuildFrom == null || archive.End >= rebuildFrom) + .ToList(); + _edgarFirstDay = edgarFrom; + Log.Trace($"SEC13FDownloader.Run(): {archives.Count} archives published, processing {selected.Count} " + + $"through {selected.LastOrDefault()?.End:yyyy-MM-dd}, then EDGAR from {edgarFrom:yyyy-MM-dd}"); + + foreach (var archive in selected) + { + ProcessArchive(archive); + FlushPendingRows(); + } + + ProcessEdgarDays(EdgarDaysToRead(edgarFrom, _edgarUntil ?? YesterdayInNewYork())); + } + else + { + // The deployment date, and any recent day whose index EDGAR published late. The + // window also reaches back to the day after the last one folded in, or the days a + // run missed while EDGAR blocked the job fall out of the lookback and no later run + // ever reads them, with every run since reporting success. + ProcessEdgarDays(EdgarDaysToRead(FirstDayToCatchUp(_deploymentDate.Value), _deploymentDate.Value)); + } + + FinalizeSecurityFiles(); + WriteEdgarState(); + LogResolutionSummary(); + } + + /// + /// Reads each day's filings from EDGAR and folds them in like an archive. A weekday without an + /// index is a holiday or a late index; it is not recorded, so a later run can still read it. + /// + /// A daily run stops once it has fetched a heaviest day's worth of filings, and the days left + /// are read by the runs that follow. Without that, a gap wider than the run's hour fails every + /// run, and since nothing is published until the last day of it is read, the gap grows by a + /// day each time and the data set never moves again. + /// + private void ProcessEdgarDays(List days) + { + var read = 0; + var fetched = 0; + foreach (var day in days) + { + var built = SEC13FEdgarDay.Build(day, _archiveCacheDirectory, _edgar.ListDirectory, + url => _edgar.GetText(url), FilingBudget(read, fetched)); + if (built.OverBudget) + { + break; + } + + if (built.Path == null) + { + Log.Trace($"SEC13FDownloader.ProcessEdgarDays(): EDGAR lists no index for {day:yyyy-MM-dd}"); + continue; + } + + ProcessArchive(new Archive(Path.GetFileName(built.Path), SECEdgarIndex.IndexUrl(day), day, day, IsDaily: true)); + FlushPendingRows(); + _edgarDays.Add(day); + fetched += built.Filings; + read++; + } + + Log.Trace($"SEC13FDownloader.ProcessEdgarDays(): read {read} of {days.Count} EDGAR days"); + } + + /// + /// The data sets whose window ends before EDGAR takes over. A window that straddles the switch + /// would have its filings read from both sources or from neither, so it stops the run. + /// + internal static List ArchivesBefore(List archives, DateTime edgarFrom) + { + var straddling = archives.FirstOrDefault(archive => archive.Start < edgarFrom && archive.End >= edgarFrom); + if (straddling != null) + { + throw new InvalidOperationException( + $"SEC13FDownloader.ArchivesBefore(): {straddling.Name} covers {edgarFrom:yyyy-MM-dd}. EDGAR has to " + + "take over the day after a data set ends, or that window would be read twice or not at all."); + } + + return archives.Where(archive => archive.End < edgarFrom).ToList(); + } + + /// + /// Where an incremental run starts reading: the lookback, or further back when the last day + /// folded in is older than that, so a gap left by an outage is caught up rather than skipped. + /// + internal DateTime FirstDayToCatchUp(DateTime deploymentDate) + { + var lookback = deploymentDate.AddDays(-EdgarLookbackDays); + if (_edgarDays.Count == 0) + { + return lookback; + } + + var afterTheLast = _edgarDays.Max.AddDays(1); + return afterTheLast < lookback ? afterTheLast : lookback; + } + + /// + /// How many filings a run may still fetch, having read days and + /// fetched filings. The rebuild reads its whole window by design, + /// and a daily run gives its first day the budget whole: a day is the unit of work and cannot + /// be read in half, and its heaviest is what an ordinary run already does every quarter. + /// + internal int FilingBudget(int read, int fetched) + { + return _deploymentDate == null || read == 0 ? int.MaxValue : MaxFilingsPerRun - fetched; + } + + /// + /// The weekdays from one date to another that no earlier run has folded in, never before the + /// day EDGAR took over from the data sets. These are the days a run may read; how many of them + /// it does read is decided as it goes, by what each one costs. + /// + internal List EdgarDaysToRead(DateTime from, DateTime until) + { + var days = new List(); + var first = from.Date < _edgarFirstDay ? _edgarFirstDay : from.Date; + for (var day = first; day <= until.Date; day = day.AddDays(1)) + { + if (day.DayOfWeek != DayOfWeek.Saturday && day.DayOfWeek != DayOfWeek.Sunday && !_edgarDays.Contains(day)) + { + days.Add(day); + } + } + + return days; + } + + /// The last complete EDGAR day when the rebuild runs, in the SEC's time zone. + private static DateTime YesterdayInNewYork() + { + return DateTime.UtcNow.ConvertFromUtc(TimeZones.NewYork).Date.AddDays(-1); + } + + /// + /// Stops a run into a destination that already holds files. The job hands it over empty, and + /// whatever an earlier run left there would be read back and published again. + /// + internal void RequireEmptyDestination() + { + if (Directory.Exists(_destinationDirectory) && Directory.EnumerateFileSystemEntries(_destinationDirectory).Any()) + { + throw new InvalidOperationException( + $"SEC13FDownloader.Run(): {_destinationDirectory} is not empty. The destination has to start empty, " + + "or the files an earlier run left there would be published again."); + } + } + + /// + /// Stops an incremental run that cannot see the published history: merging into nothing would + /// republish thirteen years as one three month window and still report success. + /// + internal void RequirePublishedHistoryForIncrementalRun() + { + if (_deploymentDate == null) + { + return; + } + + var published = Directory.Exists(_processedDataDirectory) + ? Directory.EnumerateFiles(_processedDataDirectory, "*.zip").Count() + : 0; + + if (published == 0) + { + throw new InvalidOperationException( + $"SEC13FDownloader.Run(): the incremental run for {_deploymentDate:yyyy-MM-dd} found no published " + + $"history under {_processedDataDirectory}. Publishing this window on its own would truncate the " + + "dataset to it, so the run stops instead of reporting success."); + } + + Log.Trace($"SEC13FDownloader.Run(): {published} published securities to merge into, " + + $"read from {_processedDataDirectory}"); + + RequireEdgarStateForIncrementalRun(); + } + + /// + /// Stops an incremental run that cannot tell which EDGAR days are already published: reading + /// one again would add its filings a second time under a later stamp. + /// + internal void RequireEdgarStateForIncrementalRun() + { + var path = Path.Combine(_processedDataDirectory, EdgarStateFileName); + if (File.Exists(path)) + { + return; + } + + throw new InvalidOperationException( + $"SEC13FDownloader.Run(): the incremental run for {_deploymentDate:yyyy-MM-dd} found no EDGAR state at " + + $"{path}, so it cannot tell a day already published from a new one. Republish the dataset with a full " + + "history run to restore it."); + } + + /// + /// One archive: the zip name, its absolute URL, and the filing-receipt window it covers. A + /// daily one is a day read from EDGAR, which may hold no holdings filing at all. + /// + internal sealed record Archive(string Name, string Url, DateTime Start, DateTime End, bool IsDaily = false); + + /// + /// Scrapes the data sets page for every published archive, oldest first, so a new window is + /// picked up without a code change. + /// + private List GetArchives() + { + var html = _edgar.GetText(DataSetsPageUrl); + var archives = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (Match match in ArchiveLinkRegex.Matches(html)) + { + var name = match.Groups["name"].Value; + if (archives.ContainsKey(name)) + { + continue; + } + + var href = WebUtility.HtmlDecode(match.Groups["href"].Value); + var url = href.StartsWith("http", StringComparison.OrdinalIgnoreCase) + ? href + : SecBaseUrl + (href.StartsWith("/", StringComparison.Ordinal) ? href : "/" + href); + + var (start, end) = ParseArchiveWindow(name); + archives[name] = new Archive(name, url, start, end); + } + + if (archives.Count == 0) + { + throw new InvalidOperationException( + $"SEC13FDownloader.GetArchives(): no _form13f.zip links found on {DataSetsPageUrl}. " + + "The page layout changed, or the request was blocked."); + } + + return archives.Values.OrderBy(x => x.End).ThenBy(x => x.Name, StringComparer.Ordinal).ToList(); + } + + /// + /// Reads the filing-receipt window out of an archive name. Two shapes are published: + /// "2013q2_form13f.zip" for the quarterly archives and "01mar2026-31may2026_form13f.zip" for + /// the rolling windows that replaced them. + /// + private static (DateTime start, DateTime end) ParseArchiveWindow(string name) + { + var quarter = QuarterArchiveRegex.Match(name); + if (quarter.Success) + { + var year = int.Parse(quarter.Groups["year"].Value, CultureInfo.InvariantCulture); + var number = int.Parse(quarter.Groups["quarter"].Value, CultureInfo.InvariantCulture); + var start = new DateTime(year, number * 3 - 2, 1); + return (start, start.AddMonths(3).AddDays(-1)); + } + + var window = WindowArchiveRegex.Match(name); + if (window.Success) + { + return (ParseWindowDate(window.Groups["start"].Value), ParseWindowDate(window.Groups["end"].Value)); + } + + throw new FormatException( + $"SEC13FDownloader.ParseArchiveWindow(): '{name}' matches neither the quarterly nor the " + + "rolling-window naming. A third naming scheme appeared and the selection would be wrong."); + } + + /// Parses a "01mar2026" style date out of a rolling-window archive name. + private static DateTime ParseWindowDate(string value) + { + return DateTime.ParseExact(value, "ddMMMyyyy", CultureInfo.InvariantCulture); + } + + /// + /// The first day EDGAR is responsible for: the day after the last data set the rebuild read. + /// Earlier days came from the data sets and are not in the list, so a daily run must never + /// read them from EDGAR; it did once, and counted their filings twice. + /// + private DateTime _edgarFirstDay; + + private const string EdgarFirstDayPrefix = "#from "; + + /// Loads the EDGAR days an earlier run published. A full run reads them all again and starts empty. + internal void ReadEdgarState() + { + if (_deploymentDate == null) + { + return; + } + + var path = Path.Combine(_processedDataDirectory, EdgarStateFileName); + var header = false; + foreach (var line in File.ReadLines(path)) + { + if (line.StartsWith(EdgarFirstDayPrefix, StringComparison.Ordinal)) + { + _edgarFirstDay = DateTime.ParseExact(line.Substring(EdgarFirstDayPrefix.Length).Trim(), + DateFormat.EightCharacter, CultureInfo.InvariantCulture); + header = true; + } + else if (!string.IsNullOrWhiteSpace(line)) + { + _edgarDays.Add(DateTime.ParseExact(line.Trim(), DateFormat.EightCharacter, CultureInfo.InvariantCulture)); + } + } + + if (!header) + { + throw new InvalidDataException($"SEC13FDownloader.ReadEdgarState(): {path} does not say which day EDGAR " + + "took over from the data sets, so a daily run could read a day twice. Republish with a full history run."); + } + + // What the published files state, they state as of this day. A full run leaves it at + // MinValue, since it publishes everything itself. + _foldedThrough = _edgarDays.Max; + + Log.Trace($"SEC13FDownloader.ReadEdgarState(): EDGAR from {_edgarFirstDay:yyyy-MM-dd}, {_edgarDays.Count} " + + $"days already published, the last {_foldedThrough:yyyy-MM-dd}"); + } + + /// Publishes the day EDGAR took over and the days folded in so far, oldest first. + internal void WriteEdgarState() + { + Directory.CreateDirectory(_destinationDirectory); + SEC13FFiles.WriteThenMove(Path.Combine(_destinationDirectory, EdgarStateFileName), stream => + { + using var writer = new StreamWriter(stream, leaveOpen: true); + writer.Write(EdgarFirstDayPrefix + _edgarFirstDay.ToString(DateFormat.EightCharacter, CultureInfo.InvariantCulture)); + writer.Write('\n'); + foreach (var day in _edgarDays) + { + writer.Write(day.ToString(DateFormat.EightCharacter, CultureInfo.InvariantCulture)); + writer.Write('\n'); + } + }); + } + + /// + /// Reads one archive end to end: the submissions that carry holdings, the confidential + /// treatment flags, and then the positions themselves, streamed rather than loaded. + /// + internal void ProcessArchive(Archive archive) + { + // One window's worth of resolutions is all that is worth holding: the key carries the + // filing date, so entries from a window already read can never be hit again. + _resolvedCusips.Clear(); + _crosswalkResolutions.Clear(); + _optionResolutions.Clear(); + _tickerOwners.Clear(); + + var path = DownloadArchive(archive); + + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + using var zip = new ZipArchive(stream, ZipArchiveMode.Read); + + var submissions = ReadSubmissions(zip, archive); + if (submissions.Count == 0 && !archive.IsDaily) + { + // The windows do not overlap, so an archive with no holdings means the layout moved, + // and carrying on would publish a history with this window missing. A single EDGAR + // day can legitimately carry none. + throw new InvalidDataException( + $"SEC13FDownloader.ProcessArchive(): {archive.Name} yielded no holdings submissions. " + + "An archive that contributes nothing means the upstream layout moved."); + } + + ApplyConfidentialFlags(zip, archive, submissions); + ReadCoverPages(zip, archive, submissions); + + var holdings = ReadInfoTable(zip, archive, submissions); + Log.Trace($"SEC13FDownloader.ProcessArchive(): {archive.Name}: {submissions.Count} submissions, " + + $"{holdings.Count} (security, period, release) groups"); + + EmitHoldings(holdings); + } + + internal string DownloadArchive(Archive archive) + { + return _edgar.DownloadFile(archive.Url, archive.Name, _archiveCacheDirectory); + } + + /// One filing that carries holdings, keyed in the caller by accession number. + internal sealed class Submission + { + public string Accession { get; init; } + public int Cik { get; init; } + public DateTime FilingDate { get; init; } + public DateTime Period { get; init; } + public bool ConfidentialOmitted { get; set; } + + /// The submission type as filed, 13F-HR or 13F-HR/A. + public string FormType { get; init; } + + /// + /// Whether an amendment restates the whole report or only adds holdings, as the filer + /// declared it on the cover page. Empty on an original filing. + /// + public string AmendmentType { get; set; } = string.Empty; + + /// The amendment's sequence number, or null on an original filing. + public int? AmendmentNumber { get; set; } + + /// The filing manager's name as the cover page states it. + public string ManagerName { get; set; } = string.Empty; + + /// + /// The date a previously confidential filing was originally made, which the cover page + /// carries as DATEREPORTED on about two filings in a thousand. + /// + public DateTime? DateReported { get; set; } + } + + /// + /// Reads SUBMISSION.tsv, keeping only the filings that carry positions and that no earlier + /// archive already contributed. + /// + private Dictionary ReadSubmissions(ZipArchive zip, Archive archive) + { + var submissions = new Dictionary(StringComparer.Ordinal); + var notices = 0; + + foreach (var (columns, fields) in ReadTable(zip, archive, "SUBMISSION.tsv", + "ACCESSION_NUMBER", "FILING_DATE", "SUBMISSIONTYPE", "CIK", "PERIODOFREPORT")) + { + var submissionType = fields[columns["SUBMISSIONTYPE"]]; + if (submissionType.StartsWith(NoticeSubmissionTypePrefix, StringComparison.OrdinalIgnoreCase)) + { + notices++; + continue; + } + + var accession = fields[columns["ACCESSION_NUMBER"]]; + if (!_processedAccessions.Add(accession)) + { + continue; + } + + submissions[accession] = new Submission + { + Accession = accession, + Cik = int.Parse(fields[columns["CIK"]], NumberStyles.Integer, CultureInfo.InvariantCulture), + // A daily index can name a filing whose own date is an earlier day: one 13F-HR + // across the 2026 Q2 and Q3 indexes, filed 2026-04-27 and listed on 04-28. The + // row carries the day it was listed, which is the first day the job could have + // it, so nothing reads in a backtest before it was public. The cost is that a + // rebuild reads that accession from the data sets, which state 04-27, and + // republishes the row a day earlier. + FilingDate = archive.IsDaily + ? archive.End + : ParseSecDate(fields[columns["FILING_DATE"]], archive, "FILING_DATE"), + Period = ParseSecDate(fields[columns["PERIODOFREPORT"]], archive, "PERIODOFREPORT"), + FormType = submissionType.Trim() + }; + } + + Log.Trace($"SEC13FDownloader.ReadSubmissions(): {archive.Name}: {submissions.Count} holdings filings, " + + $"{notices} notices skipped"); + return submissions; + } + + /// + /// Marks the filings that withheld positions under confidential treatment (SUMMARYPAGE + /// ISCONFIDENTIALOMITTED). COVERPAGE CONFDENIEDEXPIRED is not read: it means the opposite, + /// positions withheld before and disclosed in this filing. + /// + private void ApplyConfidentialFlags(ZipArchive zip, Archive archive, Dictionary submissions) + { + var flagged = 0; + + foreach (var (columns, fields) in ReadTable( + zip, archive, "SUMMARYPAGE.tsv", "ACCESSION_NUMBER", "ISCONFIDENTIALOMITTED")) + { + if (!IsYes(fields[columns["ISCONFIDENTIALOMITTED"]]) || + !submissions.TryGetValue(fields[columns["ACCESSION_NUMBER"]], out var submission) || + submission.ConfidentialOmitted) + { + continue; + } + + submission.ConfidentialOmitted = true; + flagged++; + } + + Log.Trace($"SEC13FDownloader.ApplyConfidentialFlags(): {archive.Name}: {flagged} filings flagged"); + } + + /// + /// Reads COVERPAGE.tsv for what the filer declared about the filing itself: the manager's + /// name, whether an amendment restates or supplements, and the date a filing withheld under + /// confidential treatment was originally made. + /// + /// The names are collected rather than written onto every line. A name repeated on each of + /// the hundred and twenty three million reported positions would be the largest column in + /// the dataset and would restate a fund's whole history the day it renames itself, so the + /// lines carry the CIK and the names are published once, in managers.csv. + /// + private void ReadCoverPages(ZipArchive zip, Archive archive, Dictionary submissions) + { + var amendments = 0; + var reported = 0; + + foreach (var (columns, fields) in ReadTable(zip, archive, "COVERPAGE.tsv", + "ACCESSION_NUMBER", "AMENDMENTNO", "AMENDMENTTYPE", "DATEREPORTED", "FILINGMANAGER_NAME")) + { + if (!submissions.TryGetValue(fields[columns["ACCESSION_NUMBER"]], out var submission)) + { + // A notice, or a filing an earlier archive already contributed. + continue; + } + + // No field of managers.csv may carry a comma: the file is split on every one. + var name = Sanitize(fields[columns["FILINGMANAGER_NAME"]]); + if (name.Length > 0) + { + submission.ManagerName = name; + + // The name of the latest filing wins, rather than of the last row read: neither + // a cover page table nor the order a run reads days in is sorted by filing date, + // so a day whose index came late would otherwise roll the name back. + if (!_managerNames.TryGetValue(submission.Cik, out var known) || known.Filed <= submission.FilingDate) + { + _managerNames[submission.Cik] = (submission.FilingDate, name); + } + } + + var amendmentType = Sanitize(fields[columns["AMENDMENTTYPE"]]); + if (amendmentType.Length > 0) + { + submission.AmendmentType = amendmentType; + amendments++; + } + + var amendmentNumber = fields[columns["AMENDMENTNO"]].Trim(); + if (amendmentNumber.Length > 0 && + int.TryParse(amendmentNumber, NumberStyles.Integer, CultureInfo.InvariantCulture, out var number)) + { + submission.AmendmentNumber = number; + } + + // DATEREPORTED is the date a confidential filing was originally made, not a + // timestamp for the positions: it is filled on about two filings in a thousand and + // is left null on the rest rather than falling back to the filing date. + var dateReported = fields[columns["DATEREPORTED"]].Trim().Split(' ')[0]; + if (dateReported.Length > 0 && + DateTime.TryParseExact(dateReported, SecDateFormats, CultureInfo.InvariantCulture, + DateTimeStyles.None, out var parsed)) + { + submission.DateReported = parsed; + reported++; + } + } + + Log.Trace($"SEC13FDownloader.ReadCoverPages(): {archive.Name}: {amendments} amendments declared, " + + $"{reported} filings carrying a confidential report date, {_managerNames.Count} managers known"); + } + + /// + /// Makes a free text field safe to write into a comma separated line that LEAN splits on + /// every comma. The alternative, quoting the field, would need every reader of the dataset + /// to parse quotes, which the engine's own CSV path does not. + /// + private static string Sanitize(string value) + { + // The runs of whitespace the replacements leave are collapsed, or a name written + // "Pershing Square Capital Management, L.P." would be published with two spaces. + return Whitespace.Replace(value.Replace(',', ' ').Replace('"', ' '), " ").Trim(); + } + + private static readonly Regex Whitespace = new(@"\s+", RegexOptions.Compiled); + + private static readonly Regex OtherManagerSeparator = new(@"[,;\s]+", RegexOptions.Compiled); + + /// + /// The other managers' sequence numbers joined by semicolons. Filers separate them with + /// commas, spaces or both, and write NONE or 0 when there are none, which comes out empty. + /// + internal static string FormatOtherManagers(string value) + { + return string.Join(';', OtherManagerSeparator.Split(value.Replace('"', ' ').Trim()) + .Where(token => token.Length > 0 && token != "0" && + !token.Equals("NONE", StringComparison.OrdinalIgnoreCase) && + !token.Equals("N/A", StringComparison.OrdinalIgnoreCase))); + } + + /// + /// One line of one filing's information table, carried through to publication unchanged. + /// Nothing is added to anything: two managers reporting the same security on the same day, + /// or one manager reporting it twice, give two of these and stay apart. + /// + internal sealed class PositionLine + { + /// The filing this line was reported on, which carries the manager and the dates. + public Submission Submission { get; init; } + + public string TitleOfClass { get; init; } + public decimal? Amount { get; init; } + public string AmountType { get; init; } + + /// VALUE as the manager stated it, in whatever unit the filing used. + public decimal? ReportedValue { get; init; } + + /// The power of ten that turns into whole dollars. + public int ValueScale { get; init; } + + public string PutCall { get; set; } + public string InvestmentDiscretion { get; init; } + public string OtherManager { get; init; } + public decimal? VotingSole { get; init; } + public decimal? VotingShared { get; init; } + public decimal? VotingNone { get; init; } + + /// The line's market value in dollars, which the coverage summary totals. + public decimal DollarValue => (ReportedValue ?? 0m) * SEC13FHolding.PowerOfTen(ValueScale); + } + + /// + /// The reported lines sharing one (CUSIP, period, filing date). They are grouped only so + /// that the identity of the security is resolved once for all of them, and so that the + /// crosswalk check has every implied price of the group to judge by; the lines themselves + /// are published one by one. + /// + internal sealed class Holding + { + /// Every line of the group, in the order the information table listed them. + public List Lines { get; } = new(); + + /// Every reported value of the group, for the coverage summary. + public decimal ReportedValue => Lines.Sum(line => line.DollarValue); + + /// + /// The implied price, VALUE over SSHPRNAMT as reported, of every share line in the group, + /// which is what a crosswalk resolution is checked against the close with. + /// + public List SharePrices { get; } = new(); + } + + /// + /// What the lines of one group share: the CUSIP the manager named, the quarter it reported + /// and the day the filing reached EDGAR. + /// + internal readonly record struct HoldingKey(string Cusip, DateTime Period, DateTime FilingDate); + + /// + /// Streams INFOTABLE.tsv and collects every reported line into its group. Nothing is summed + /// and no line is dropped: a line is published as the manager filed it, including the option + /// and debt lines the aggregated model used to fold away, each with its own amount type. + /// + internal Dictionary ReadInfoTable( + ZipArchive zip, Archive archive, Dictionary submissions) + { + var holdings = new Dictionary(); + var units = DetectValueUnits(zip, archive, submissions); + var lines = 0L; + + foreach (var (columns, fields) in ReadTable(zip, archive, "INFOTABLE.tsv", + "ACCESSION_NUMBER", "CUSIP", "TITLEOFCLASS", "VALUE", "SSHPRNAMT", "SSHPRNAMTTYPE", + "PUTCALL", "INVESTMENTDISCRETION", "OTHERMANAGER", + "VOTING_AUTH_SOLE", "VOTING_AUTH_SHARED", "VOTING_AUTH_NONE")) + { + var accession = fields[columns["ACCESSION_NUMBER"]]; + if (!submissions.TryGetValue(accession, out var submission)) + { + // A notice, or a filing an earlier archive already contributed. + continue; + } + + var cusip = fields[columns["CUSIP"]].Trim().ToUpperInvariant(); + if (cusip.Length == 0) + { + continue; + } + + var key = new HoldingKey(cusip, submission.Period, submission.FilingDate); + if (!holdings.TryGetValue(key, out var group)) + { + holdings[key] = group = new Holding(); + } + + var shareType = fields[columns["SSHPRNAMTTYPE"]].Trim(); + var putCall = fields[columns["PUTCALL"]].Trim(); + var amount = ParseOptionalDecimal(fields[columns["SSHPRNAMT"]]); + var value = ParseOptionalDecimal(fields[columns["VALUE"]]); + + // Only a plain share line says anything about the unit the filing reports values in, + // and only those prices are worth comparing with a close, so the crosswalk check is + // given those and no others. + var isShareLine = putCall.Length == 0 && shareType.Equals("SH", StringComparison.OrdinalIgnoreCase); + if (isShareLine && value > 0m && amount > 0m) + { + group.SharePrices.Add((double)(value.Value / amount.Value)); + } + + // The unit is recorded beside the value the manager reported rather than multiplied + // into it. It runs both ways: a filing still in thousands after 2023 is scaled up, and + // a line that overstated its value a thousandfold is brought back down. Only a share + // line has a price to check against the close; an option or a bond at par would land + // on a step by chance, so those keep their filing's unit. + var scale = ScaleOf(isShareLine + ? units.Factor(accession, cusip, submission, value ?? 0m, amount ?? 0m) + : units.Filing(accession)); + + group.Lines.Add(new PositionLine + { + Submission = submission, + TitleOfClass = Sanitize(fields[columns["TITLEOFCLASS"]]), + Amount = amount, + AmountType = Sanitize(shareType), + ReportedValue = value, + ValueScale = scale, + PutCall = Sanitize(putCall), + InvestmentDiscretion = Sanitize(fields[columns["INVESTMENTDISCRETION"]]), + OtherManager = FormatOtherManagers(fields[columns["OTHERMANAGER"]]), + VotingSole = ParseOptionalDecimal(fields[columns["VOTING_AUTH_SOLE"]]), + VotingShared = ParseOptionalDecimal(fields[columns["VOTING_AUTH_SHARED"]]), + VotingNone = ParseOptionalDecimal(fields[columns["VOTING_AUTH_NONE"]]) + }); + + lines++; + } + + Log.Trace($"SEC13FDownloader.ReadInfoTable(): {archive.Name}: {lines} reported positions, " + + $"{units.CorrectedLines} share lines in a different unit than the rest of their filing"); + return holdings; + } + + /// + /// The factors that turn VALUE into whole dollars. Filers do not all follow the 2023 unit + /// change (in Apple's December 2019 quarter 85 lines in dollars made 88 percent of the scaled + /// total), and some mix units within one filing, so each implied price is compared with the + /// security's close on the quarter's last trading day: per line where the close is known, and + /// per filing, from its own lines, where it is not. Nothing here reads another filing, so a + /// filing comes out the same read alone on its day as inside a three month window. The + /// median of every filer in the window it replaces corrected a filing with others made weeks + /// after it. + /// + internal ValueUnits DetectValueUnits(ZipArchive zip, Archive archive, Dictionary submissions) + { + var offsetsByFiling = new Dictionary>(StringComparer.Ordinal); + var pricedByFiling = new Dictionary(StringComparer.Ordinal); + + foreach (var (columns, fields) in ReadTable(zip, archive, "INFOTABLE.tsv", + "ACCESSION_NUMBER", "CUSIP", "VALUE", "SSHPRNAMT", "SSHPRNAMTTYPE", "PUTCALL")) + { + var accession = fields[columns["ACCESSION_NUMBER"]]; + if (!submissions.TryGetValue(accession, out var submission) + || !fields[columns["SSHPRNAMTTYPE"]].Trim().Equals("SH", StringComparison.OrdinalIgnoreCase) + || fields[columns["PUTCALL"]].Trim().Length > 0) + { + continue; + } + + var offset = MarketOffset(fields[columns["CUSIP"]].Trim().ToUpperInvariant(), submission, + ParseDecimal(fields[columns["VALUE"]]), ParseDecimal(fields[columns["SSHPRNAMT"]])); + if (offset == null) + { + continue; + } + + pricedByFiling[accession] = pricedByFiling.GetValueOrDefault(accession) + 1; + + // A price on no unit step says nothing about the unit the filing reports in. + if (UnitStep(offset.Value) == null) + { + continue; + } + + if (!offsetsByFiling.TryGetValue(accession, out var offsets)) + { + offsetsByFiling[accession] = offsets = new List(); + } + + offsets.Add(offset.Value); + } + + var units = new Dictionary(StringComparer.Ordinal); + var againstTheRule = 0; + foreach (var (accession, submission) in submissions) + { + var thousandsRule = submission.FilingDate < ValueInWholeDollarsFrom; + units[accession] = ValueMultiplier( + offsetsByFiling.TryGetValue(accession, out var offsets) ? offsets : new List(), + pricedByFiling.GetValueOrDefault(accession), thousandsRule); + if (units[accession] != (thousandsRule ? 1000m : 1m)) + { + againstTheRule++; + } + } + + // Filings with closes to compare against whose lines mostly sit on no unit step. + var broken = new HashSet( + pricedByFiling + .Where(pair => !IsUnitEvidence(offsetsByFiling.GetValueOrDefault(pair.Key)?.Count ?? 0, pair.Value)) + .Select(pair => pair.Key), + StringComparer.Ordinal); + + Log.Trace($"SEC13FDownloader.DetectValueUnits(): {archive.Name}: {againstTheRule} of " + + $"{submissions.Count} filings report VALUE in the other unit, {broken.Count} show no unit at all"); + + return new ValueUnits(units, broken, MarketOffset); + } + + /// + /// log10 of a share line's implied price over its security's quarter-end close, or null when + /// the line has no price, the CUSIP resolves to nothing, or the close is not known. Near zero + /// the line is in whole dollars, near -3 in thousands. + /// + internal double? MarketOffset(string cusip, Submission submission, decimal value, decimal amount) + { + if (value <= 0m || amount <= 0m) + { + return null; + } + + var security = ResolveSecurity(cusip, submission.FilingDate); + var close = security == null ? null : _closePrices.Close(security, submission.Period, submission.FilingDate); + return close == null ? null : Math.Log10((double)(value / amount / close.Value)); + } + + /// + /// The whole-dollar factor of each share line: its own, measured against its security's close + /// when that is known, or else the one decided for its whole filing. + /// + internal sealed class ValueUnits + { + private readonly Dictionary _filings; + private readonly HashSet _broken; + private readonly Func _marketOffset; + + /// Share lines whose factor differed from their filing's. + public long CorrectedLines { get; private set; } + + public ValueUnits(Dictionary filings, HashSet broken, + Func marketOffset) + { + _filings = filings; + _broken = broken; + _marketOffset = marketOffset; + } + + /// The factor decided for a whole filing, which a line with no price of its own takes. + public decimal Filing(string accession) => _filings[accession]; + + /// The factor for one share line of VALUE over shares. + public decimal Factor(string accession, string cusip, Submission submission, decimal value, decimal amount) + { + // A filing whose lines mostly sit on no unit step keeps its unscaled factor on every + // line: the few that land on a step do so by chance. + var filing = _filings[accession]; + if (_broken.Contains(accession)) + { + return filing; + } + + var offset = _marketOffset(cusip, submission, value, amount); + if (offset == null) + { + return filing; + } + + // The thousandfold step the line's price sits on: one below is VALUE in thousands, one + // above is VALUE typed a thousand times too large. That second case is a VALUE slip + // rather than a share count one: of the 4,740 such lines of the March 2023 quarter + // whose filer reported the same security the quarter before, 4,578 held the same + // shares then and 162 a thousand times more. A price on no step, a millionfold one + // included, cannot be checked, so it takes the smaller of its filing's unit and the rule + // of its filing date. Either one alone inflates an era: the rule scaled the odd lines of + // filers in whole dollars before 2023 ($730 billion in the September 2022 quarter), the + // filing's unit those of filers still in thousands in early 2023 ($879 billion in + // December 2022). Scaling by the nearest step put Seagate at $413 billion. + var step = UnitStep(offset.Value); + if (step == null) + { + return Math.Min(filing, submission.FilingDate < ValueInWholeDollarsFrom ? 1000m : 1m); + } + + var factor = step == 0 ? 1m : step < 0 ? 1000m : 0.001m; + if (factor != filing) + { + CorrectedLines++; + } + + return factor; + } + } + + /// + /// Whether a filing's VALUE is multiplied by a thousand. The offsets are log10 of those of its + /// implied prices that sit on a unit step of the security's close, out of + /// lines with a close: near zero the filing reports whole dollars, + /// near -3 thousands. They say so only when they are most of its priced lines. A filing whose + /// lines mostly sit on no step is broken rather than in another unit, and the few that land on + /// one do so by chance: a manager that typed its dollar values as share counts, a price of $1 + /// on every line, looks like thousands against any close near $1,000. Such a filing is never + /// scaled up: before 2023 the rule multiplied it by a thousand, and one manager whose share + /// counts carried VALUE in thousands, $1,000 a share on every line, put Alphabet's September + /// 2020 quarter 9.5 percent above its close. With nothing to compare against, the rule stands. + /// + internal static decimal ValueMultiplier(List offsets, int priced, bool thousandsRule) + { + if (!IsUnitEvidence(offsets.Count, priced)) + { + return priced == 0 && thousandsRule ? 1000m : 1m; + } + + return Median(offsets) < -1.5 ? 1000m : 1m; + } + + /// Whether a filing's lines on a unit step are most of its priced lines. + private static bool IsUnitEvidence(int onAStep, int priced) + { + return priced > 0 && onAStep * 2 > priced; + } + + private static double Median(List values) + { + var sorted = values.OrderBy(value => value).ToList(); + var middle = sorted.Count / 2; + return sorted.Count % 2 == 1 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2; + } + + /// + /// Resolves each group to a security and queues its lines for that security's file. Groups + /// that resolve to nothing, mostly foreign issuers, are dropped. + /// + private void EmitHoldings(Dictionary holdings) + { + foreach (var day in holdings.GroupBy(entry => entry.Key.FilingDate).OrderBy(group => group.Key)) + { + foreach (var (key, holding) in day) + { + var security = ResolveSecurity(key.Cusip, key.FilingDate); + if (security == null && OptionUnderlyings(key.Cusip).Count > 1) + { + EmitOptionLinesByPrice(key, holding); + continue; + } + + var ticker = security == null ? null : ResolveTicker(security, key.FilingDate); + if (string.IsNullOrWhiteSpace(ticker) || !IsFileNameSafe(ticker)) + { + _unresolvedGroups++; + _unresolvedValue += holding.ReportedValue; + continue; + } + + // Whoever reads the ticker's file takes its rows to belong to the security that + // owns the ticker that day; a group of another security is dropped, not mixed in. + var owner = TickerOwner(ticker, key.FilingDate); + if (owner != security.ToString()) + { + _conflictingTickers++; + _conflictingValue += holding.ReportedValue; + if (_conflictSample.Count < 10) + { + _conflictSample.Add($"{key.Cusip} {key.FilingDate:yyyy-MM-dd} {ticker} is {security}, owner {owner ?? "none"}"); + } + + _unresolvedGroups++; + _unresolvedValue += holding.ReportedValue; + continue; + } + + // A crosswalk ticker is a fund administrator's free text, and a wrong one names + // another company: DeFi Technologies at $2 reached the $129 Hashdex DEFI ETF and + // gave it 111 holders. A group whose prices say so is dropped. + if (_crosswalkResolutions.Contains((key.Cusip, key.FilingDate)) && + !KeepsCrosswalkGroup(key.Cusip, security, key.Period, key.FilingDate, holding.SharePrices)) + { + if (IsEquityIssue(key.Cusip)) + { + _mismatchedGroups++; + } + else + { + _debtCusipsRejected++; + } + + _mismatchedValue += holding.ReportedValue; + _unresolvedGroups++; + _unresolvedValue += holding.ReportedValue; + continue; + } + + _resolvedValue += holding.ReportedValue; + + // Every line of the group, as filed. An amendment is published beside the + // original it restates rather than replacing it: deciding that a restatement + // supersedes a number is a judgement about the data, and the filer already + // declared which kind it is in AmendmentType for whoever wants to apply it. + var throughOption = _optionResolutions.Contains((key.Cusip, key.FilingDate)); + foreach (var line in holding.Lines) + { + if (throughOption) + { + InferOptionSide(key.Cusip, line); + } + + Queue(security.ToString(), ticker.ToLowerInvariant(), new HoldingsRow + { + Time = key.FilingDate, + Line = line + }); + } + } + } + } + + /// + /// Gives a line under an option CUSIP the side it left empty, which would otherwise read as + /// shares of the underlying. The CUSIP states it: issue 90 is a call and 95 a put, and where + /// the line does name a side the two agree on 24,890 of 24,915 lines of the June to August + /// 2026 data set. + /// + private void InferOptionSide(string cusip, PositionLine line) + { + if (FormatPutCall(line.PutCall).Length > 0) + { + return; + } + + line.PutCall = NormalizeCusip(cusip).Substring(IssuerLength, 2) == OptionIssues[0] ? "Call" : "Put"; + _optionSidesInferred++; + } + + /// + /// How far an option line's implied price may sit from a fund's close and still name it. Tight, + /// unlike the unit check, because it has to tell the funds of one family apart, and it can be: + /// filers value the line at that close. On the March 2026 quarter half a percent gave 1,383 + /// of 2,114 lines one fund and 63 several, where three percent gave 958 and 659. + /// + private const double OptionPriceTolerance = 0.005; + + /// + /// Publishes the lines of an option CUSIP whose issuer has several equity issues, as the fund + /// families do: every iShares fund's calls share one CUSIP, so each line is resolved on its + /// own. An option line reports the value and the number of the underlying shares, so its + /// implied price is the underlying's, and the line goes to the one issue whose quarter-end + /// close it matches, in dollars or in thousands. No match, or more than one, drops the line. + /// The close names the fund and nothing else. An option line has no price of its own, so it + /// keeps its filing's unit, like every other option line and like the bond at par beside it. + /// + private void EmitOptionLinesByPrice(HoldingKey key, Holding holding) + { + var candidates = OptionUnderlyings(key.Cusip) + .Select(issue => TradingDefinition(_definitionsByCusip, issue, + BuildUnitedStatesIsin(issue + ComputeCusipCheckDigit(issue)), key.FilingDate)) + .Where(security => security != null) + .Distinct() + .Select(security => (Security: security, Close: _closePrices.Close(security, key.Period, key.FilingDate))) + .Where(candidate => candidate.Close != null) + .ToList(); + + foreach (var line in holding.Lines) + { + var matches = line.Amount > 0m && line.ReportedValue > 0m + ? candidates.Where(candidate => MatchesClose(line.ReportedValue.Value / line.Amount.Value, candidate.Close.Value)).ToList() + : []; + + var ticker = matches.Count == 1 ? ResolveTicker(matches[0].Security, key.FilingDate) : null; + if (string.IsNullOrWhiteSpace(ticker) || !IsFileNameSafe(ticker)) + { + _optionLinesWithoutOneMatch++; + _unresolvedValue += line.DollarValue; + continue; + } + + InferOptionSide(key.Cusip, line); + _optionLinesByPrice++; + _resolvedValue += line.DollarValue; + Queue(matches[0].Security.ToString(), ticker.ToLowerInvariant(), new HoldingsRow { Time = key.FilingDate, Line = line }); + } + } + + /// Whether a price is a close, stated in dollars or in thousands, within OptionPriceTolerance. + internal static bool MatchesClose(decimal price, decimal close) + { + var ratio = price / close; + return Math.Abs((double)ratio - 1) <= OptionPriceTolerance || + Math.Abs((double)ratio * 1000 - 1) <= OptionPriceTolerance; + } + + /// + /// The security trading under a ticker on a date per the map files, or null. It is how the + /// LEAN reads a ticker file, so writing applies the same test. + /// + private string TickerOwner(string ticker, DateTime date) + { + var key = (ticker.ToUpperInvariant(), date.Date); + if (!_tickerOwners.TryGetValue(key, out var owner)) + { + var mapFile = _mapFileProvider + .Get(new AuxiliaryDataKey(Market.USA, SecurityType.Equity)) + .ResolveMapFile(key.Item1, key.Item2); + + _tickerOwners[key] = owner = mapFile.Any() + ? SecurityIdentifier.GenerateEquity(mapFile.FirstDate, mapFile.FirstTicker, Market.USA).ToString() + : null; + } + + return owner; + } + + /// + /// Resolves a CUSIP to a security, point in time: by CUSIP, then by the US ISIN built from + /// it, then through the N-PORT ticker crosswalk. + /// + internal SecurityIdentifier ResolveSecurity(string rawCusip, DateTime tradingDate) + { + var key = (rawCusip, tradingDate); + if (_resolvedCusips.TryGetValue(key, out var cached)) + { + return cached; + } + + // The summary counts CUSIPs, not lookups, and one CUSIP is now resolved once per filing + // date rather than once per run. + var firstSighting = _countedCusips.Add(rawCusip); + + var cusip = NormalizeCusip(rawCusip); + if (cusip == null) + { + if (firstSighting) + { + _malformedCusips++; + } + + _resolvedCusips[key] = null; + return null; + } + + // Step 1: LEAN stores the CUSIP without its check digit, so the ninth character comes off. + var isin = BuildUnitedStatesIsin(cusip); + var security = TradingDefinition(_definitionsByCusip, cusip.Substring(0, 8), isin, tradingDate); + if (security != null) + { + if (firstSighting) + { + _resolvedByCusip++; + } + } + else + { + // Step 2: the US ISIN reaches issuers whose CUSIP is blank in the security database. + security = TradingDefinition(_definitionsByIsin, isin, isin, tradingDate); + if (security != null && firstSighting) + { + _resolvedByIsin++; + } + } + + // Step 3: the N-PORT crosswalk needs no security database, and reaches Alphabet and the + // CINS foreign issuers the constructed ISIN cannot represent. + if (security == null) + { + var identifier = ResolveThroughTicker(cusip); + if (identifier != null) + { + if (firstSighting) + { + _resolvedByTicker++; + } + + _resolvedCusips[key] = identifier; + _crosswalkResolutions.Add(key); + return identifier; + } + + // Step 4: an option's own CUSIP is in no database, so it is resolved through the + // security it is written on. The position is still published as the option line it + // is, with its PutCall side, rather than as a holding of the underlying. + var underlying = UnderlyingOfOptionCusip(cusip); + if (underlying != null) + { + security = TradingDefinition(_definitionsByCusip, underlying, + BuildUnitedStatesIsin(underlying + ComputeCusipCheckDigit(underlying)), tradingDate); + if (security != null) + { + if (firstSighting) + { + _resolvedByOptionUnderlying++; + } + + _resolvedCusips[key] = security; + _optionResolutions.Add(key); + return security; + } + } + + // Several possible underlyings: its lines are resolved one by one, by price. + if (OptionUnderlyings(cusip).Count <= 1) + { + _unresolvedCusips.Add(cusip); + } + + _resolvedCusips[key] = null; + return null; + } + + _resolvedCusips[key] = security; + return security; + } + + /// + /// The security, among the database rows carrying an identifier, that trades under its own + /// ticker on the date, or null. The database repeats identifiers across the listings one + /// company has had, and LEAN's resolver takes the first row, mostly the one that no longer + /// trades: Alcoa's CUSIP sits on the old Alcoa, now Howmet, and on the Alcoa spun off in 2016, + /// and TG Therapeutics' on the listing it had as Atlantic Technology Ventures. Run on the real + /// database that dropped Alcoa, Howmet, Vertiv and TG Therapeutics, as groups whose ticker + /// another security owned. A row carrying the ISIN the CUSIP builds is tried first, since the + /// old Alcoa row carries Howmet's. + /// + private SecurityIdentifier TradingDefinition(Dictionary> rowsByIdentifier, + string identifier, string isin, DateTime tradingDate) + { + if (!rowsByIdentifier.TryGetValue(identifier, out var rows)) + { + return null; + } + + int Priority(SecurityDefinition row) => row.ISIN == null ? 1 + : string.Equals(row.ISIN, isin, StringComparison.OrdinalIgnoreCase) ? 0 : 2; + + foreach (var row in rows.OrderBy(Priority)) + { + var ticker = ResolveTicker(row.SecurityIdentifier, tradingDate); + if (ticker != null && TickerOwner(ticker, tradingDate) == row.SecurityIdentifier.ToString()) + { + return row.SecurityIdentifier; + } + } + + return null; + } + + /// + /// The security database rows by one identifier, in file order. A CUSIP is keyed by its eight + /// character body, the form the database mostly stores; the few rows written with their check + /// digit are keyed only when that digit holds. + /// + private static Dictionary> IndexDefinitions( + IEnumerable definitions, Func identifier) + { + var index = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var definition in definitions) + { + var key = identifier(definition); + if (string.IsNullOrWhiteSpace(key)) + { + continue; + } + + if (!index.TryGetValue(key, out var rows)) + { + index[key] = rows = new List(); + } + + rows.Add(definition); + } + + return index; + } + + /// The eight character body of a database CUSIP, or null when it is neither that nor a checked nine. + private static string DatabaseCusip(string cusip) + { + if (cusip == null) + { + return null; + } + + return cusip.Length switch + { + 8 => cusip, + 9 when ComputeCusipCheckDigit(cusip.Substring(0, 8)) == cusip[8] => cusip.Substring(0, 8), + _ => null + }; + } + + /// + /// Brings a reported CUSIP to nine characters, or null when it cannot be one. A short value + /// has lost either its leading zero ("37833100", Apple) or its check digit ("46428722", an + /// iShares fund). Padding the second with a zero would invent another security, so the + /// check digit decides which repair applies. + /// + internal static string NormalizeCusip(string cusip) + { + // Anything else cannot be put in an ISIN, and one such line would stop the run there. + if (!cusip.All(char.IsAsciiLetterOrDigit)) + { + return null; + } + + if (cusip.Length == 9) + { + return cusip; + } + + if (cusip.Length is < 6 or > 9) + { + return null; + } + + var padded = cusip.PadLeft(9, '0'); + if (ComputeCusipCheckDigit(padded.Substring(0, 8)) == padded[8]) + { + return padded; + } + + if (cusip.Length != 8) + { + return null; + } + + var checkDigit = ComputeCusipCheckDigit(cusip); + return checkDigit.HasValue ? cusip + checkDigit.Value : null; + } + + /// + /// The CUSIP check digit: a modulus ten sum over the eight character body where every + /// second character counts double, letters standing for their position in the alphabet + /// plus nine. + /// + internal static char? ComputeCusipCheckDigit(string body) + { + var sum = 0; + for (var i = 0; i < body.Length; i++) + { + var character = body[i]; + int value; + if (char.IsDigit(character)) + { + value = character - '0'; + } + else if (char.IsLetter(character)) + { + value = char.ToUpperInvariant(character) - 'A' + 10; + } + else + { + value = character switch { '*' => 36, '@' => 37, '#' => 38, _ => -1 }; + if (value < 0) + { + // Not a character a CUSIP can hold, so there is no check digit. + return null; + } + } + + if (i % 2 == 1) + { + value *= 2; + } + + sum += value / 10 + value % 10; + } + + return (char)('0' + (10 - sum % 10) % 10); + } + + /// + /// Priced share lines a crosswalk group needs before its prices can overrule the resolution. One + /// or two lines are usually one manager's slip, a stale price or a split, not another company: + /// on the December 2025 quarter, dropping on any disagreement removed 14,696 holders across + /// 3,249 tickers besides the misattributed ones, and requiring three lines halves that while + /// still removing 565 of the 637 misattributed holders. + /// + private const int PricedLinesToOverrule = 3; + + /// + /// Whether a group reached through the crosswalk stays. A CUSIP whose issue number carries + /// letters is debt as a rule, and N-PORT tags a fund's bonds with the issuer's ticker, so + /// Etsy's convertible notes reached Etsy's stock: 171 million of the 298 million shares its + /// December 2022 quarter published. Such a group stays only when its prices are the security's + /// own, which is how the iShares iBonds ETFs, whose CUSIPs carry letters too, keep their + /// holders. Any other group is dropped only when enough of its prices say it is another + /// company, as Centerra Gold and Enerflex reached Carlyle and Equifax through their Toronto + /// tickers CG and EFX. A group without prices, a day of option positions only, or a security + /// the close file does not carry, cannot be checked and stays. + /// + internal bool KeepsCrosswalkGroup(string cusip, SecurityIdentifier security, DateTime period, DateTime filingDate, + List prices) + { + // A CUSIP whose issue carries letters, whose issuer already has stock in the security + // database, is that company's debt: the stock is the row the database holds, and this is + // something else the same company issued. Apple's 3.45% 2045 bond, 037833BA7, reached + // AAPL here through a fund administrator's N-PORT ticker and added a constant ten + // thousand shares to it, the principal amount on a line the filer had typed SH. + // + // Letting the prices decide was the mistake: a bond near par and a stock near the same + // number agree by coincidence. The iBonds ETFs, whose CUSIPs also carry letters, are + // themselves in the database and resolve before the crosswalk is ever consulted, so this + // does not reach them. + if (!IsEquityIssue(cusip) && IssuerHasStock(cusip)) + { + return false; + } + + var matches = PricesMatchClose(security, period, filingDate, prices); + if (!IsEquityIssue(cusip)) + { + return matches == true; + } + + return !(matches == false && prices.Count >= PricedLinesToOverrule); + } + + /// Whether the CUSIP's six character issuer has a stock of its own in the database. + internal bool IssuerHasStock(string cusip) + { + var nine = NormalizeCusip(cusip); + return nine != null && _equityIssuesByIssuer.ContainsKey(nine.Substring(0, IssuerLength)); + } + + /// + /// Whether a group's prices are the security's: at least half of them sit on a unit step of the + /// quarter-end close. Counted line by line rather than through a median, because a busy day + /// mixes managers in dollars and in thousands: Avanos on 5 February 2026 had four lines at + /// $0.0112 and four at $11.23, and their median sat half way, on neither. Null when the group + /// has no priced share line or the security has no close. + /// + private bool? PricesMatchClose(SecurityIdentifier security, DateTime period, DateTime filingDate, List prices) + { + var close = _closePrices.Close(security, period, filingDate); + if (close == null || prices.Count == 0) + { + return null; + } + + var onAStep = prices.Count(price => UnitStep(Math.Log10(price / (double)close.Value)) != null); + return onAStep * 2 >= prices.Count; + } + + /// + /// The thousandfold step a line's price sits on against the close, given as log10 of their + /// ratio: 0 for whole dollars, -1 for thousands, 1 for a VALUE typed a thousand times too + /// large. Null when it sits on none, within PriceMatchTolerance, which is a price that says + /// nothing about the unit: another security, a bond at par against a stock, or a slip. + /// Scaling those by the nearest step put Seagate's December 2025 quarter at $413 billion. + /// + internal static int? UnitStep(double offset) + { + var steps = (int)Math.Round(offset / 3); + return Math.Abs(steps) <= 1 && Math.Abs(offset - 3 * steps) <= PriceMatchTolerance ? steps : null; + } + + /// + /// Whether a CUSIP's issue number is an equity one: two digits, where debt uses letters. The + /// CUSIP is brought to nine characters first, since filers drop leading zeros and check digits. + /// + internal static bool IsEquityIssue(string cusip) + { + var nine = NormalizeCusip(cusip); + return nine != null && char.IsDigit(nine[6]) && char.IsDigit(nine[7]); + } + + /// + /// Whether a CUSIP is a CINS, the form foreign issuers carry: it opens with the letter of the + /// issuer's country or region, where a US or Canadian one opens with a digit. Many trade in + /// the US all the same, as Accenture and Medtronic do, so it sorts the summary and filters nothing. + /// + internal static bool IsCins(string cusip) => cusip.Length > 0 && char.IsAsciiLetter(cusip[0]); + + /// How many characters of a CUSIP name the issuer, before the issue and the check digit. + private const int IssuerLength = 6; + + /// The issue numbers an option carries: 90 for calls and 95 for puts. + private static readonly string[] OptionIssues = { "90", "95" }; + + /// Whether a CUSIP names an option on a security rather than the security itself. + internal static bool IsOptionIssue(string cusip) + { + var nine = NormalizeCusip(cusip); + return nine != null && OptionIssues.Contains(nine.Substring(IssuerLength, 2)); + } + + /// + /// The security an option CUSIP is written on, as an eight character CUSIP, or null. + /// + /// A manager reporting options names them by the option's own CUSIP, which shares the six + /// character issuer of the underlying and carries issue 90 for calls or 95 for puts. That + /// CUSIP is in no security database, so the line used to resolve to nothing and the position + /// was dropped: in the week of 3 August 2026 that lost 6,083 reported option lines. + /// + /// The issuer alone is not always enough. iShares writes seventy equity issues under + /// 464287 and SPDR eleven under 81369Y, and an option CUSIP there names one of them without + /// saying which. Guessing would file a position under the wrong fund, so the underlying is + /// only returned when the issuer has exactly one equity issue and the answer is not a guess. + /// + internal string UnderlyingOfOptionCusip(string cusip) + { + var nine = NormalizeCusip(cusip); + if (nine == null || !IsOptionIssue(nine)) + { + return null; + } + + var issues = OptionUnderlyings(nine); + return issues.Count == 1 ? issues[0] : null; + } + + /// The equity issues, as eight character CUSIPs, an option CUSIP can be written on. + internal List OptionUnderlyings(string cusip) + { + var nine = NormalizeCusip(cusip); + return nine != null && IsOptionIssue(nine) && + _equityIssuesByIssuer.TryGetValue(nine.Substring(0, IssuerLength), out var issues) + ? issues + : []; + } + + /// + /// Resolves a CUSIP through the N-PORT ticker crosswalk. The ticker is resolved on the day + /// the funds reported it, since a ticker names a security only on a date: at a 2021 filing + /// META named a Roundhill ETF, not Facebook. The map file is checked first because + /// GenerateEquity never returns null for an unknown ticker. Whether the group it resolves is + /// kept is KeepsCrosswalkGroup's call, on that day's prices. + /// + internal SecurityIdentifier ResolveThroughTicker(string cusip) + { + if (!TickerCrosswalk.TryGetValue(cusip, out var entry)) + { + return null; + } + + if (!_crosswalkSecurities.TryGetValue(cusip, out var security)) + { + var mapFile = _mapFileProvider + .Get(new AuxiliaryDataKey(Market.USA, SecurityType.Equity)) + .ResolveMapFile(entry.Ticker, entry.Observed); + + // The map file has to carry the ticker on that very date. Barrick reached the + // crosswalk as ABX, its Toronto ticker, when ABX named no US security, and the + // resolver still returned the company that took ABX months later. + var tradedUnderIt = mapFile.Any() && string.Equals( + mapFile.GetMappedSymbol(entry.Observed, null), entry.Ticker, StringComparison.OrdinalIgnoreCase); + + _crosswalkSecurities[cusip] = security = tradedUnderIt + ? SecurityIdentifier.GenerateEquity(mapFile.FirstDate, mapFile.FirstTicker, Market.USA) + : null; + } + + return security; + } + + /// + /// Builds the US ISIN for a nine character CUSIP: the country code, the CUSIP, and the + /// check digit, which is arithmetic rather than a lookup. "037833100" gives "US0378331005". + /// + internal static string BuildUnitedStatesIsin(string cusip) + { + var body = "US" + cusip; + return body + ComputeIsinCheckDigit(body); + } + + /// + /// Computes an ISIN check digit: expand each letter to its ordinal (A is 10 through Z is 35), + /// then run Luhn over the resulting digits from the right. + /// + private static char ComputeIsinCheckDigit(string body) + { + var digits = new StringBuilder(body.Length * 2); + foreach (var character in body) + { + if (character >= '0' && character <= '9') + { + digits.Append(character); + } + else if (character >= 'A' && character <= 'Z') + { + digits.Append((character - 'A' + 10).ToStringInvariant()); + } + else + { + throw new FormatException( + $"SEC13FDownloader.ComputeIsinCheckDigit(): '{body}' carries '{character}', which is neither " + + "a digit nor an upper case letter"); + } + } + + var sum = 0; + var doubled = true; + for (var i = digits.Length - 1; i >= 0; i--) + { + var value = digits[i] - '0'; + if (doubled) + { + value *= 2; + if (value > 9) + { + value -= 9; + } + } + + sum += value; + doubled = !doubled; + } + + return (char)('0' + (10 - sum % 10) % 10); + } + + /// + /// The ticker a security traded under on a date, or null when its map file does not cover + /// it. No fallback to the last ticker: managers report dead CUSIPs for years, and by then the + /// ticker can belong to another company. + /// + internal string ResolveTicker(SecurityIdentifier security, DateTime tradingDate) + { + // Before its first row a map file answers with its first ticker, for a security that + // did not trade yet. + var mapFile = MapFileOf(security); + if (mapFile == null || tradingDate < mapFile.FirstDate) + { + return null; + } + + var ticker = mapFile.GetMappedSymbol(tradingDate, null); + return string.IsNullOrEmpty(ticker) ? null : ticker; + } + + /// The map file of a security, cached, or null when it has none. + private MapFile MapFileOf(SecurityIdentifier security) + { + if (!_mapFiles.TryGetValue(security, out var mapFile)) + { + _mapFiles[security] = mapFile = _mapFileProvider + .Get(AuxiliaryDataKey.Create(security)) + .ResolveMapFile(security.Symbol, security.Date); + } + + return mapFile; + } + + /// + /// Queues a row for a security, to be staged when the archive is done. + /// + private void Queue(string security, string ticker, HoldingsRow row) + { + if (!_pendingSecurityRows.TryGetValue(security, out var rows)) + { + _pendingSecurityRows[security] = rows = new List<(string, HoldingsRow)>(); + } + + // Appended, never merged: two lines reported for the same security on the same day are + // two positions and both are published. + rows.Add((ticker, row)); + } + + /// + /// Appends the archive's rows to the staging file of their security; the grouping into files + /// per ticker and filing date happens once, in the finalize pass. + /// + internal void FlushPendingRows() + { + Directory.CreateDirectory(_stagingDirectory); + foreach (var (security, rows) in _pendingSecurityRows) + { + File.AppendAllLines( + StagingPath(security), + rows.OrderBy(entry => entry.Row.Time) + .Select(entry => $"{entry.Ticker},{FormatRow(entry.Row)}")); + _stagedSecurities.Add(security); + } + + _pendingSecurityRows.Clear(); + } + + /// The staging file of a security. The identifier carries a space, so it is escaped. + private string StagingPath(string security) + { + return Path.Combine(_stagingDirectory, Uri.EscapeDataString(security) + ".csv"); + } + + /// + /// Gathers the staged rows into their ticker's zip, one entry per filing date. An incremental + /// run adds its dates to a copy of the published zip. + /// + internal void FinalizeSecurityFiles() + { + if (_stagedSecurities.Count == 0) + { + // A day can carry filings whose every line fails to resolve, and it is recorded as + // folded in all the same, so no later run reads its cover pages again: the names it + // gave are written now or never. + Log.Trace("SEC13FDownloader.FinalizeSecurityFiles(): no rows were staged"); + WriteManagerNames(); + return; + } + + var tickerStaging = Path.Combine(_stagingDirectory, "tickers"); + Directory.CreateDirectory(tickerStaging); + + // A ticker file can hold the rows of more than one security, one after another as the + // ticker changed hands, so every security is gathered into it before it is written. + foreach (var security in _stagedSecurities) + { + foreach (var ticker in ReadStagedRows(StagingPath(security)).GroupBy(entry => entry.Ticker)) + { + File.AppendAllLines(Path.Combine(tickerStaging, $"{ticker.Key}.csv"), + ticker.Select(entry => FormatRow(entry.Row))); + } + + // Deleted as it goes: the whole history staged twice, uncompressed, is tens of gigabytes. + File.Delete(StagingPath(security)); + } + + Directory.CreateDirectory(_destinationDirectory); + var files = Directory.GetFiles(tickerStaging, "*.csv"); + var entries = 0L; + var rows = 0L; + + foreach (var file in files) + { + var ticker = Path.GetFileNameWithoutExtension(file); + var days = ReadRows(file) + .GroupBy(row => row.Time) + .OrderBy(day => day.Key) + .ToList(); + + entries += WriteSecurityZip(ticker, days); + rows += days.Sum(day => day.LongCount()); + File.Delete(file); + } + + Log.Trace($"SEC13FDownloader.FinalizeSecurityFiles(): {_stagedSecurities.Count} securities written into " + + $"{files.Length} ticker files, {entries} filing dates, {rows} reported positions"); + + WriteManagerNames(); + Directory.Delete(_stagingDirectory, recursive: true); + } + + /// + /// Writes one security's filing dates as an entry per date inside its zip. + /// + /// A zip rather than a directory of loose files because a filing date holds three lines at + /// the median and one line a third of the time: as loose files the history would be eight + /// and a half million of them, whose tar headers alone outweigh the data. + /// + private long WriteSecurityZip(string ticker, List> days) + { + var path = Path.Combine(_destinationDirectory, $"{ticker}.zip"); + + // The destination starts empty and whatever lands in it replaces the published file, so + // an incremental run adds its dates to a copy of the published zip. Without the copy it + // would publish the security's history as this run's dates alone. + var published = Path.Combine(_processedDataDirectory, $"{ticker}.zip"); + if (_deploymentDate != null && File.Exists(published)) + { + File.Copy(published, path); + } + + // Update mode only when there is a zip to update. A new one is opened in Create mode, + // which streams its entries out instead of holding the archive in memory. + var updating = File.Exists(path); + + using (var zip = ZipFile.Open(path, updating ? ZipArchiveMode.Update : ZipArchiveMode.Create)) + { + foreach (var day in days) + { + var name = $"{day.Key.ToStringInvariant(PeriodFormat)}.csv"; + + // A date already published is added to, never replaced. What is published for it + // can come from filings this read does not carry: the daily index and the + // quarterly data sets do not name the same filings for a day, and an index can + // name a filing dated an earlier day. Taking the read for the whole of the date + // dropped every other manager's positions of it, and reported success. Only the + // lines of the filings being written are dropped, so a filing read twice is + // published once. + var kept = Array.Empty(); + var existing = updating ? zip.GetEntry(name) : null; + if (existing != null) + { + var rewritten = day.Select(row => row.Line.Submission.Accession).ToHashSet(StringComparer.Ordinal); + using (var reader = new StreamReader(existing.Open())) + { + kept = reader.ReadToEnd() + .Split('\n', StringSplitOptions.RemoveEmptyEntries) + .Where(line => !rewritten.Contains(AccessionOf(line))) + .ToArray(); + } + + existing.Delete(); + } + + // The same bytes whichever system the job runs on. + using var writer = new StreamWriter(zip.CreateEntry(name, CompressionLevel.Optimal).Open()) { NewLine = "\n" }; + foreach (var line in kept) + { + writer.WriteLine(line); + } + + foreach (var row in day) + { + writer.WriteLine(FormatRow(row)); + } + } + } + + using var written = ZipFile.OpenRead(path); + return written.Entries.Count; + } + + /// + /// Writes managers.csv, the filing managers' names by CIK. An incremental run folds in the + /// names already published, so a manager that filed nothing this run keeps its name. + /// + private void WriteManagerNames() + { + Directory.CreateDirectory(_destinationDirectory); + var path = Path.Combine(_destinationDirectory, ManagerNamesFileName); + var names = new Dictionary(); + + var published = Path.Combine(_processedDataDirectory, ManagerNamesFileName); + if (File.Exists(published)) + { + foreach (var line in File.ReadLines(published)) + { + var separator = line.IndexOf(','); + if (separator > 0 && + int.TryParse(line[..separator], NumberStyles.Integer, CultureInfo.InvariantCulture, out var cik)) + { + names[cik] = line[(separator + 1)..]; + } + } + } + + var newerThanThisRun = ManagersPublishedAfter(_managerNames.Values + .Where(manager => manager.Filed <= _foldedThrough) + .Select(manager => manager.Filed) + .DefaultIfEmpty(DateTime.MaxValue) + .Min()); + + foreach (var (cik, manager) in _managerNames) + { + // The name a manager files under is the one it carries today, so only its latest + // filing may name it. The published file carries no date to say when the name in it + // was filed, and a day whose index came late is read after newer days are already + // published, so the date is taken from the published rows themselves: a manager that + // filed again between this filing and what is published keeps the published name. + if (!newerThanThisRun.TryGetValue(cik, out var newer) || newer < manager.Filed) + { + names[cik] = manager.Name; + } + } + + // Sanitized here and not only where the cover page is read, because the names folded in + // come from a file an earlier release wrote, which carried its commas bare. + File.WriteAllLines(path, names.OrderBy(entry => entry.Key) + .Select(entry => $"{entry.Key.ToStringInvariant()},{Sanitize(entry.Value)}")); + + Log.Trace($"SEC13FDownloader.WriteManagerNames(): {names.Count} managers, " + + $"{_managerNames.Count} of them seen this run"); + } + + /// + /// The newest filing date each manager already has published in the days after + /// , read from the published rows themselves. managers.csv carries no + /// date, so this is where the date of a published name comes from: a manager that filed again + /// between the filing this run read and what is published is not renamed by the older one. + /// + /// Only a run folding in a day older than the last one already folded in asks for this, which + /// means a day whose index EDGAR published late, so an ordinary daily run never reaches here. + /// It costs one pass over the published zips, whose central directories answer for every date + /// they do not hold, so only the entries of those few days are read. + /// + /// A manager whose every line failed to resolve has no published row and is not found here, + /// and is then named by its older filing: a stale name rather than a wrong number. + /// + private Dictionary ManagersPublishedAfter(DateTime after) + { + var managers = new Dictionary(); + if (after >= _foldedThrough || !Directory.Exists(_processedDataDirectory)) + { + return managers; + } + + var entries = new Dictionary(StringComparer.Ordinal); + for (var day = after.AddDays(1); day <= _foldedThrough; day = day.AddDays(1)) + { + entries[$"{day.ToStringInvariant(PeriodFormat)}.csv"] = day; + } + + var read = 0; + foreach (var file in Directory.EnumerateFiles(_processedDataDirectory, "*.zip")) + { + using var zip = ZipFile.OpenRead(file); + foreach (var entry in zip.Entries) + { + if (!entries.TryGetValue(entry.Name, out var filed)) + { + continue; + } + + using var reader = new StreamReader(entry.Open()); + while (reader.ReadLine() is { } line) + { + var fields = line.Split(','); + if (fields.Length > ManagerCikColumn && + int.TryParse(fields[ManagerCikColumn], NumberStyles.Integer, CultureInfo.InvariantCulture, out var cik) && + (!managers.TryGetValue(cik, out var known) || known < filed)) + { + managers[cik] = filed; + } + } + + read++; + } + } + + Log.Trace($"SEC13FDownloader.ManagersPublishedAfter(): {managers.Count} managers already published a " + + $"filing in the {entries.Count} days after {after:yyyy-MM-dd}, read from {read} entries"); + return managers; + } + + /// The accession of a published row, or empty for a line too short to carry one. + private static string AccessionOf(string line) + { + var fields = line.Split(','); + return fields.Length > AccessionColumn ? fields[AccessionColumn] : string.Empty; + } + + /// One published row: a single reported position, on the date it was filed. + internal sealed class HoldingsRow + { + /// The filing date, which names the file the row belongs in. + public DateTime Time { get; init; } + + /// The reported line itself, with the filing it came from. + public PositionLine Line { get; init; } + } + + /// Reads a staging file back: the ticker, then the row in the published layout. + private static IEnumerable<(string Ticker, HoldingsRow Row)> ReadStagedRows(string path) + { + foreach (var line in File.ReadLines(path)) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + var comma = line.IndexOf(','); + yield return (line.Substring(0, comma), ParseRow(line.Substring(comma + 1))); + } + } + + /// Reads a ticker's staged rows back, which are already in the published layout. + private static IEnumerable ReadRows(string path) + { + return File.ReadLines(path).Where(line => !string.IsNullOrWhiteSpace(line)).Select(ParseRow); + } + + /// + /// Reads one published line back into a row. The staging files are this processor's own + /// output read back in the same run, so a malformed line is a bug here rather than bad input + /// and is left to throw. + /// + private static HoldingsRow ParseRow(string line) + { + var csv = line.Split(','); + + return new HoldingsRow + { + Time = DateTime.ParseExact(csv[0], PeriodFormat, CultureInfo.InvariantCulture), + Line = new PositionLine + { + Submission = new Submission + { + Accession = csv[1], + Cik = int.Parse(csv[2], NumberStyles.Integer, CultureInfo.InvariantCulture), + Period = DateTime.ParseExact(csv[3], PeriodFormat, CultureInfo.InvariantCulture), + FormType = csv[4], + AmendmentType = csv[5], + AmendmentNumber = csv[6].Length == 0 + ? null + : int.Parse(csv[6], NumberStyles.Integer, CultureInfo.InvariantCulture), + ConfidentialOmitted = csv[18] == "1", + DateReported = csv[19].Length == 0 + ? null + : DateTime.ParseExact(csv[19], PeriodFormat, CultureInfo.InvariantCulture) + }, + TitleOfClass = csv[7], + Amount = ParseOptionalDecimal(csv[8]), + AmountType = csv[9], + ReportedValue = ParseOptionalDecimal(csv[10]), + ValueScale = int.Parse(csv[11], NumberStyles.Integer, CultureInfo.InvariantCulture), + PutCall = csv[12], + InvestmentDiscretion = csv[13], + OtherManager = csv[14], + VotingSole = ParseOptionalDecimal(csv[15]), + VotingShared = ParseOptionalDecimal(csv[16]), + VotingNone = ParseOptionalDecimal(csv[17]) + } + }; + } + + /// + /// Formats one reported position, in the twenty column layout SEC13FHolding parses. The + /// filing date leads the line although the file it lands in is named after it, so that a + /// line lifted out of its file still says when it was filed. + /// + private static string FormatRow(HoldingsRow row) + { + var line = row.Line; + var submission = line.Submission; + + return string.Join(',', + row.Time.ToStringInvariant(PeriodFormat), + submission.Accession, + submission.Cik.ToStringInvariant(), + submission.Period.ToStringInvariant(PeriodFormat), + submission.FormType, + submission.AmendmentType, + submission.AmendmentNumber?.ToStringInvariant(), + line.TitleOfClass, + FormatValue(line.Amount), + line.AmountType, + FormatValue(line.ReportedValue), + line.ValueScale.ToStringInvariant(), + FormatPutCall(line.PutCall), + line.InvestmentDiscretion, + line.OtherManager, + FormatValue(line.VotingSole), + FormatValue(line.VotingShared), + FormatValue(line.VotingNone), + submission.ConfidentialOmitted ? "1" : "0", + submission.DateReported?.ToStringInvariant(PeriodFormat)); + } + + /// + /// The power of ten a detected factor stands for. The detector only ever returns a power of + /// ten, so this is exact; anything else would be a bug here and is left at no scaling rather + /// than silently rounded. + /// + internal static int ScaleOf(decimal factor) + { + var scale = 0; + while (factor >= 10m) + { + factor /= 10m; + scale++; + } + + while (factor > 0m && factor < 1m) + { + factor *= 10m; + scale--; + } + + return factor == 1m ? scale : 0; + } + + /// + /// Shortens the option side to a single letter. The column is written once per reported + /// position, so the three characters saved on every option line are worth the mapping. + /// + /// + /// A row is formatted twice, once into its staging file and once into the published entry, + /// so this has to leave an already shortened side alone rather than blanking it. + /// + private static string FormatPutCall(string putCall) + { + if (putCall.StartsWith("C", StringComparison.OrdinalIgnoreCase)) + { + return "C"; + } + + return putCall.StartsWith("P", StringComparison.OrdinalIgnoreCase) ? "P" : string.Empty; + } + + /// Reports how much of the dataset made it through identity resolution. + private void LogResolutionSummary() + { + var totalValue = _resolvedValue + _unresolvedValue; + var coverage = totalValue == 0m ? 0m : 100m * _resolvedValue / totalValue; + + Log.Trace("SEC13FDownloader.LogResolutionSummary(): working set " + + $"{GC.GetTotalMemory(false) / (1024 * 1024)} MB"); + + Log.Trace($"SEC13FDownloader.LogResolutionSummary(): {_resolvedByCusip} distinct CUSIPs resolved by CUSIP, " + + $"{_resolvedByIsin} by constructed ISIN, {_resolvedByTicker} by the N-PORT ticker crosswalk, " + + $"{_resolvedByOptionUnderlying} by the security an option is written on, " + + $"{_unresolvedCusips.Count} unresolved, {_unresolvedCusips.Count(IsCins)} of them foreign (CINS), " + + $"{_malformedCusips} malformed, " + + $"{_optionSidesInferred} option lines given the side their CUSIP states"); + Log.Trace($"SEC13FDownloader.LogResolutionSummary(): {_optionLinesByPrice} option lines of multi-issue issuers " + + $"resolved by price, {_optionLinesWithoutOneMatch} dropped for matching no single issue"); + Log.Trace($"SEC13FDownloader.LogResolutionSummary(): {_unresolvedGroups} groups dropped, " + + $"{_conflictingTickers} of them, {(totalValue == 0m ? 0m : 100m * _conflictingValue / totalValue).ToStringInvariant("F2")}% " + + "of reported value, because the ticker belonged to another security that day, " + + $"{coverage.ToStringInvariant("F1")}% of reported value covered"); + Log.Trace($"SEC13FDownloader.LogResolutionSummary(): {_mismatchedGroups} crosswalk groups dropped because " + + $"their prices were another security's, {_debtCusipsRejected} groups of CUSIPs with letters in the issue " + + "number dropped for want of a price matching the close, " + + $"{(totalValue == 0m ? 0m : 100m * _mismatchedValue / totalValue).ToStringInvariant("F2")}% of reported value together"); + + // The domestic ones are the gaps worth chasing: a foreign issuer LEAN does not list is expected. + if (_unresolvedCusips.Count > 0) + { + Log.Trace($"SEC13FDownloader.LogResolutionSummary(): unresolved domestic sample: " + + $"{string.Join(", ", _unresolvedCusips.Where(cusip => !IsCins(cusip)).Take(25))}"); + } + + if (_conflictSample.Count > 0) + { + Log.Trace($"SEC13FDownloader.LogResolutionSummary(): ticker conflict sample: {string.Join("; ", _conflictSample)}"); + } + } + + /// One table of an archive, where a short row means the layout moved and throws. + private static IEnumerable<(Dictionary Columns, string[] Fields)> ReadTable( + ZipArchive zip, Archive archive, string table, params string[] required) + { + return SEC13FFiles.ReadTable(zip, archive.Name, table, skipShortRows: false, required); + } + + /// + /// Parses a date as the SEC writes it in these tables, "31-MAR-2026", with the ISO and US + /// shapes accepted too because the older archives are not perfectly consistent. + /// + private static DateTime ParseSecDate(string value, Archive archive, string column) + { + // Some rows carry a time of day the tables do not otherwise use. + var date = value.Trim().Split(' ')[0]; + if (DateTime.TryParseExact(date, SecDateFormats, CultureInfo.InvariantCulture, + DateTimeStyles.None, out var parsed)) + { + return parsed; + } + + throw new FormatException( + $"SEC13FDownloader.ParseSecDate(): {archive.Name} {column} '{value}' matches none of " + + $"{string.Join(", ", SecDateFormats)}"); + } + + /// Parses a reported amount, treating a blank as zero. + private static decimal ParseDecimal(string value) + { + var trimmed = value.Trim(); + if (trimmed.Length == 0) + { + return 0m; + } + + if (!decimal.TryParse(trimmed, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsed)) + { + throw new FormatException($"SEC13FDownloader.ParseDecimal(): '{value}' is not a number"); + } + + return parsed; + } + + /// Parses a reported amount, where a blank is an absent reading rather than a zero. + private static decimal? ParseOptionalDecimal(string value) + { + return string.IsNullOrWhiteSpace(value) ? null : ParseDecimal(value); + } + + /// True for the "Y" the SEC writes in its flag columns. + private static bool IsYes(string value) + { + return value.Trim().Equals("Y", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Formats an amount: whole numbers without a decimal point, everything else invariant, and + /// a field the filing left empty as empty, since that is not a reported zero. + /// + private static string FormatValue(decimal? value) + { + return value switch + { + null => string.Empty, + // Formatted rather than cast: filers type nonsense into VALUE, and a decimal above + // long.MaxValue threw out of the cast and stopped the run over one bad line. + { } whole when whole == Math.Truncate(whole) => whole.ToString("0", CultureInfo.InvariantCulture), + _ => value.Value.ToStringInvariant() + }; + } + + /// + /// True when the ticker can name a file LEAN will ask for. Skips path separators and invalid + /// file name characters, and the space and '|' a Symbol cannot hold: the map files carry + /// "ua.c " for Under Armour's class C, and its file could never be read. + /// + internal static bool IsFileNameSafe(string ticker) + { + return ticker.IndexOf('/') < 0 + && ticker.IndexOf('\\') < 0 + && ticker.IndexOf('|') < 0 + && !ticker.Any(char.IsWhiteSpace) + && ticker.IndexOfAny(Path.GetInvalidFileNameChars()) < 0; + } + + /// Disposes unmanaged resources. + public void Dispose() + { + _edgar.DisposeSafely(); + + // A run that failed half way leaves its staging behind, and nothing else ever reads it. + try + { + if (Directory.Exists(_stagingDirectory)) + { + Directory.Delete(_stagingDirectory, recursive: true); + } + } + catch (IOException err) + { + Log.Error(err, $"SEC13FDownloader.Dispose(): could not delete {_stagingDirectory}"); + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/DataProcessing/SEC13FEdgarDay.cs b/DataProcessing/SEC13FEdgarDay.cs new file mode 100644 index 0000000..0610739 --- /dev/null +++ b/DataProcessing/SEC13FEdgarDay.cs @@ -0,0 +1,318 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using QuantConnect.Logging; + +namespace QuantConnect.DataProcessing +{ + /// + /// One day of Form 13F filings read straight from EDGAR and written as an archive with the + /// tables the SEC data sets carry, so the processor reads it like any other window. + /// + /// The data sets come out in three month batches a few days after each window closes. A daily + /// job waiting for them publishes a filing up to three months after it was public, while a + /// backtest stamped at the filing date sees it the day it was filed. EDGAR lists each day's + /// filings in its daily index that night, and their information tables carry the lines the data + /// sets do: 48 of 48 filings compared line by line, and the same accession numbers on six sample + /// days, so reading EDGAR is what lets the live job publish on the date the history uses. + /// + internal static class SEC13FEdgarDay + { + private const string FormPattern = "13F-HR(?:/A)?"; + + private const string PrimaryDocumentTypePrefix = "13F-HR"; + private const string InformationTableDocumentType = "INFORMATION TABLE"; + + /// What the archive's tables carry for one filing. + internal sealed class Filing + { + public string Accession { get; init; } + public string SubmissionType { get; init; } + public int Cik { get; init; } + public DateTime Filed { get; init; } + public DateTime Period { get; init; } + public bool ConfidentialOmitted { get; init; } + + /// The cover page: the manager's name and what an amendment declares about itself. + public string ManagerName { get; init; } + public string AmendmentNumber { get; init; } + public string AmendmentType { get; init; } + public DateTime? DateReported { get; init; } + + /// INFOTABLE rows, in the order of . + public List Lines { get; } = new(); + } + + private static readonly string[] LineColumns = + [ + "CUSIP", "TITLEOFCLASS", "VALUE", "SSHPRNAMT", "SSHPRNAMTTYPE", "PUTCALL", "INVESTMENTDISCRETION", + "OTHERMANAGER", "VOTING_AUTH_SOLE", "VOTING_AUTH_SHARED", "VOTING_AUTH_NONE" + ]; + + private const string XmlDateFormat = "MM-dd-yyyy"; + + /// + /// The share of a day's filings that may be unreadable before the day fails. A filing the SEC + /// accepted and this reader cannot is skipped, since failing the day over it would stop the + /// data set for good; a whole day of them is a layout change and must still throw. One filing + /// is always allowed, so a quiet day of three does not fail over its one bad one. + /// + private const double MaxUnreadableShare = 0.05; + + /// The name a day's archive is cached and logged under. + public static string ArchiveName(DateTime day) => $"edgar-{day.ToString(DateFormat.EightCharacter, CultureInfo.InvariantCulture)}_form13f.zip"; + + /// What building one day came to: its archive, or why there is none. + /// The day's archive, or null when the day was not read. + /// The holdings filings fetched, which is what reading the day cost. + /// The day was left unread because it did not fit in what the run had left. + public readonly record struct Built(string Path, int Filings, bool OverBudget); + + /// + /// Writes the day's archive into and returns its path, or no path + /// when EDGAR has not published an index for the day, which is every weekend and holiday and a + /// day whose index is late. returns the names in one of EDGAR's + /// directory listings and a file; both throw on any failure. + /// + /// is how many filings the caller has room left to fetch. The + /// index costs one round trip and each filing another, so the whole cost of a day is known + /// before the expensive part starts: a day that does not fit is left whole for the next run + /// rather than read in half. + /// + public static Built Build(DateTime day, string directory, Func> listDirectory, + Func getText, int filingBudget = int.MaxValue) + { + var path = System.IO.Path.Combine(directory, ArchiveName(day)); + if (File.Exists(path)) + { + // Fetched already, by an earlier run or a test: it costs no round trip and no budget. + return new Built(path, 0, OverBudget: false); + } + + if (!SECEdgarIndex.IsIndexPublished(day, listDirectory)) + { + return new Built(null, 0, OverBudget: false); + } + + // The index lists a filing once for every CIK it names. + var entries = SECEdgarIndex.DistinctFilings(ParseIndex(getText(SECEdgarIndex.IndexUrl(day)))); + if (entries.Count > filingBudget) + { + Log.Trace($"SEC13FEdgarDay.Build(): {day:yyyy-MM-dd} carries {entries.Count} holdings filings and " + + $"{filingBudget} are left in this run, so it is left whole for the next one"); + return new Built(null, entries.Count, OverBudget: true); + } + + var filings = new List(entries.Count); + var unreadable = 0; + foreach (var entry in entries) + { + try + { + filings.Add(ParseFiling(entry, getText(SECEdgarIndex.ArchivesBaseUrl + entry.Path))); + } + catch (Exception error) when (error is InvalidDataException or FormatException) + { + // One filing the SEC accepted and this reader cannot is not worth the data set + // for: thrown, it fails the day, the day is never recorded, and every run after + // it meets the same filing and fails again until someone ships code. An + // HttpRequestException is not caught, so a network failure still fails the day + // and the day is read again. + Log.Error($"SEC13FEdgarDay.Build(): {day:yyyy-MM-dd} {entry.Accession} cannot be read, " + + $"skipping it: {error.Message}"); + unreadable++; + } + } + + // A layout change reads as filing after filing being unreadable, and that has to fail + // loudly rather than publish a day emptied of most of what it held. + if (unreadable > Math.Max(1, entries.Count * MaxUnreadableShare)) + { + throw new InvalidDataException( + $"SEC13FEdgarDay.Build(): {day:yyyy-MM-dd}: {unreadable} of {entries.Count} filings could not be " + + "read, which is more than a bad filing or two. The layout of the primary document has likely changed."); + } + + Directory.CreateDirectory(directory); + SEC13FFiles.WriteThenMove(path, stream => WriteArchive(stream, filings)); + + Log.Trace($"SEC13FEdgarDay.Build(): {day:yyyy-MM-dd}: {filings.Count} holdings filings, " + + $"{filings.Sum(filing => filing.Lines.Count)} information table lines"); + return new Built(path, filings.Count, OverBudget: false); + } + + /// + /// The holdings reports and their amendments listed in a daily index. Notices are left out: + /// they carry no information table, and the processor skips them in the data sets too. + /// + internal static List ParseIndex(string text) + { + return SECEdgarIndex.ParseIndex(text, FormPattern); + } + + /// + /// Reads one full submission file: the period, the cover page and the confidential treatment + /// flag from the primary document, and every line of its information tables. The form type, filer and + /// filing date come from the index, as they do in the data sets. + /// + internal static Filing ParseFiling(SECEdgarIndex.Entry entry, string text) + { + XElement primary = null; + var tables = new List(); + + foreach (var document in SECEdgarIndex.Documents(text)) + { + if (document.Xml == null) + { + continue; + } + + if (document.Type.StartsWith(PrimaryDocumentTypePrefix, StringComparison.OrdinalIgnoreCase)) + { + primary = SECEdgarIndex.ParseXml(document.Xml, entry.Path); + } + else if (document.Type.Equals(InformationTableDocumentType, StringComparison.OrdinalIgnoreCase)) + { + tables.Add(SECEdgarIndex.ParseXml(document.Xml, entry.Path)); + } + } + + if (primary == null) + { + throw new InvalidDataException($"SEC13FEdgarDay.ParseFiling(): {entry.Path} carries no primary document"); + } + + var period = Value(primary, "periodOfReport") ?? Value(primary, "reportCalendarOrQuarter") + ?? throw new InvalidDataException($"SEC13FEdgarDay.ParseFiling(): {entry.Path} carries no period of report"); + + var filing = new Filing + { + Accession = entry.Accession, + SubmissionType = entry.FormType, + + // The filing names its own filer. The index lists an accession once per CIK it + // names, so the first line of it can be a co-filer whose name sorts earlier, and + // the data sets take the CIK from the filing: the two paths have to agree. + Cik = FilerCik(primary) ?? entry.Cik, + Filed = entry.Filed, + Period = DateTime.ParseExact(period, XmlDateFormat, CultureInfo.InvariantCulture), + ConfidentialOmitted = IsTrue(Value(primary, "isConfidentialOmitted")), + + // Scoped to the filing manager: the signature and the other managers carry a name too. + ManagerName = SECEdgarIndex.Elements(primary, "filingManager") + .Select(manager => Value(manager, "name")).FirstOrDefault(), + AmendmentNumber = Value(primary, "amendmentNo"), + AmendmentType = Value(primary, "amendmentType"), + DateReported = DateTime.TryParseExact(Value(primary, "dateReported"), XmlDateFormat, + CultureInfo.InvariantCulture, DateTimeStyles.None, out var reported) ? reported : null + }; + + foreach (var line in tables.SelectMany(table => SECEdgarIndex.Elements(table, "infoTable"))) + { + filing.Lines.Add(new[] + { + Value(line, "cusip"), + Value(line, "titleOfClass"), + Value(line, "value"), + Value(line, "sshPrnamt"), + Value(line, "sshPrnamtType"), + Value(line, "putCall"), + Value(line, "investmentDiscretion"), + Value(line, "otherManager"), + Value(line, "Sole"), + Value(line, "Shared"), + Value(line, "None") + }); + } + + return filing; + } + + /// + /// The filer's CIK as the primary document states it, or null when it carries none. Scoped to + /// the credentials of the filer info, which is the only place that element appears: a search + /// of the whole document would also reach the CIKs of the other managers on the cover page. + /// + private static int? FilerCik(XElement primary) + { + var credentials = SECEdgarIndex.Elements(primary, "credentials").FirstOrDefault(); + return credentials != null && + int.TryParse(SECEdgarIndex.Value(credentials, "cik"), NumberStyles.Integer, + CultureInfo.InvariantCulture, out var cik) + ? cik + : null; + } + + /// Writes the four tables the processor reads, in the data sets' layout. + internal static void WriteArchive(Stream stream, IEnumerable filings) + { + var submissions = new StringBuilder("ACCESSION_NUMBER\tFILING_DATE\tSUBMISSIONTYPE\tCIK\tPERIODOFREPORT\n"); + var summaries = new StringBuilder("ACCESSION_NUMBER\tISCONFIDENTIALOMITTED\n"); + var covers = new StringBuilder("ACCESSION_NUMBER\tAMENDMENTNO\tAMENDMENTTYPE\tDATEREPORTED\tFILINGMANAGER_NAME\n"); + var lines = new StringBuilder("ACCESSION_NUMBER\t").AppendJoin('\t', LineColumns).Append('\n'); + + foreach (var filing in filings) + { + submissions.Append(string.Join('\t', filing.Accession, + filing.Filed.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture), filing.SubmissionType, + filing.Cik.ToString(CultureInfo.InvariantCulture), filing.Period.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture))).Append('\n'); + summaries.Append(filing.Accession).Append('\t').Append(filing.ConfidentialOmitted ? "Y" : "N").Append('\n'); + covers.Append(string.Join('\t', filing.Accession, Clean(filing.AmendmentNumber), Clean(filing.AmendmentType), + filing.DateReported?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? string.Empty, + Clean(filing.ManagerName))).Append('\n'); + + foreach (var line in filing.Lines) + { + lines.Append(filing.Accession); + foreach (var field in line) + { + lines.Append('\t').Append(Clean(field)); + } + + lines.Append('\n'); + } + } + + using var zip = new ZipArchive(stream, ZipArchiveMode.Create, leaveOpen: true); + foreach (var (name, content) in new[] { ("SUBMISSION.tsv", submissions), ("SUMMARYPAGE.tsv", summaries), ("COVERPAGE.tsv", covers), ("INFOTABLE.tsv", lines) }) + { + using var writer = new StreamWriter(zip.CreateEntry(name, CompressionLevel.Optimal).Open()); + writer.Write(content.ToString()); + } + } + + private static string Value(XElement root, string name) => SECEdgarIndex.Value(root, name); + + private static bool IsTrue(string value) + { + return value != null && (value.Equals("true", StringComparison.OrdinalIgnoreCase) || value.Equals("Y", StringComparison.OrdinalIgnoreCase)); + } + + /// A field as a table cell: tabs and line breaks would split the row. + private static string Clean(string value) + { + return value == null ? string.Empty : Regex.Replace(value, @"\s+", " ").Trim(); + } + } +} diff --git a/DataProcessing/SEC13FFiles.cs b/DataProcessing/SEC13FFiles.cs new file mode 100644 index 0000000..fe0af1a --- /dev/null +++ b/DataProcessing/SEC13FFiles.cs @@ -0,0 +1,108 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace QuantConnect.DataProcessing +{ + /// File helpers shared by the 13F downloader and the N-PORT crosswalk. + internal static class SEC13FFiles + { + /// + /// Streams one tab separated table out of an SEC archive, yielding the header's column index + /// map with each row. A missing table or required column throws, since either means the + /// layout moved; so does a row too short for the required columns, unless + /// asks for it to be skipped. + /// + public static IEnumerable<(Dictionary Columns, string[] Fields)> ReadTable( + ZipArchive zip, string source, string table, bool skipShortRows, params string[] required) + { + var entry = zip.Entries.FirstOrDefault(x => + string.Equals(Path.GetFileName(x.FullName), table, StringComparison.OrdinalIgnoreCase)); + if (entry == null) + { + throw new FileNotFoundException( + $"SEC13FFiles.ReadTable(): {source} carries no {table}. Entries: " + + $"{string.Join(", ", zip.Entries.Select(x => x.FullName))}"); + } + + using var reader = new StreamReader(entry.Open()); + + var header = reader.ReadLine(); + if (header == null) + { + throw new InvalidDataException($"SEC13FFiles.ReadTable(): {source} {table} is empty"); + } + + var columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + var names = header.Split('\t'); + for (var i = 0; i < names.Length; i++) + { + columns[names[i].Trim()] = i; + } + + var missing = required.Where(x => !columns.ContainsKey(x)).ToList(); + if (missing.Count > 0) + { + throw new InvalidDataException( + $"SEC13FFiles.ReadTable(): {source} {table} is missing {string.Join(", ", missing)}. Header: {header}"); + } + + var maximum = required.Max(x => columns[x]); + string line; + while ((line = reader.ReadLine()) != null) + { + if (line.Length == 0) + { + continue; + } + + var fields = line.Split('\t'); + if (fields.Length <= maximum) + { + if (skipShortRows) + { + continue; + } + + throw new InvalidDataException( + $"SEC13FFiles.ReadTable(): {source} {table} row holds {fields.Length} fields, " + + $"fewer than the {maximum + 1} the required columns need: {line}"); + } + + yield return (columns, fields); + } + } + + /// + /// Writes a file through a temporary sibling that is moved into place at the end, so an + /// interrupted run never leaves a half written file for the next one to read. + /// + public static void WriteThenMove(string path, Action write) + { + var temporaryPath = path + ".tmp"; + using (var stream = File.Create(temporaryPath)) + { + write(stream); + } + + File.Move(temporaryPath, path, overwrite: true); + } + } +} diff --git a/DataProcessing/SEC13FTickerCrosswalk.cs b/DataProcessing/SEC13FTickerCrosswalk.cs new file mode 100644 index 0000000..4b0ca69 --- /dev/null +++ b/DataProcessing/SEC13FTickerCrosswalk.cs @@ -0,0 +1,297 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text.RegularExpressions; +using QuantConnect.Logging; + +namespace QuantConnect.DataProcessing +{ + /// + /// CUSIP to ticker crosswalk built from the SEC Form N-PORT data sets, used as the last step of + /// the 13F identity chain. + /// + /// A 13F reports a CUSIP and nothing else, and LEAN's security database does not carry every + /// issuer: it misses names as large as Alphabet, and for foreign issuers filing under a CINS the + /// arithmetic US ISIN is wrong by construction. N-PORT publishes, for the same securities, both + /// the CUSIP and the ticker the fund reported, so joining its tables yields a CUSIP to ticker + /// map made entirely out of filings. On the March to May 2026 13F window it covers 98.1 percent + /// of reported value, against 88.9 percent for the security database steps on their own. + /// + /// The ticker is free text written by fund administrators ("GOOGL", "GOOGL US", "goog"), so it + /// is normalised and voted on across every fund that reported the security. Each entry keeps the + /// day its data set begins, because a ticker only names a security on a date: META named a + /// Roundhill ETF from June 2021 to January 2022, before Facebook took it. + /// + /// N-PORT begins in late 2019, so a security that stopped trading before then never appears + /// here and still depends on the security database. + /// + public static class SEC13FTickerCrosswalk + { + private const string NPortUrlFormat = + "https://www.sec.gov/files/dera/data/form-n-port-data-sets/{0}q{1}_nport.zip"; + + private const string HoldingTable = "FUND_REPORTED_HOLDING.tsv"; + private const string IdentifierTable = "IDENTIFIERS.tsv"; + + /// + /// The file the built map is cached in, so the next run does not re-download gigabytes. Not + /// a .csv, so nothing that walks the security files mistakes it for one. + /// + public const string CacheFileName = "nport-crosswalk.txt"; + + /// The ticker the funds reported for a CUSIP, and the first day of the data set it came from. + public readonly record struct Entry(string Ticker, DateTime Observed); + + /// + /// Tickers arrive with a venue suffix in the Bloomberg style ("GOOGL US"), in lower case, + /// and as the literal "N/A". Strip the suffix, upper case the rest and accept only what + /// looks like a US equity ticker. + /// + private static readonly Regex VenueSuffix = new(@"\s+[A-Z]{2}$", RegexOptions.Compiled); + private static readonly Regex TickerShape = new(@"^[A-Z.\-]{1,8}$", RegexOptions.Compiled); + + /// + /// Loads the crosswalk cached in , building it from the + /// newest N-PORT data sets when the cache does not cover them and + /// writing the result to . says + /// whether a data set is published and puts one on disk, both + /// through the caller's rate limit and retries. + /// + public static Dictionary Load(string readDirectory, string writeDirectory, int quarters, + Func exists, Func download) + { + var cached = ReadCache(Path.Combine(readDirectory, CacheFileName), out var cachedQuarters); + + var missing = FindAvailableQuarters(quarters, exists) + .Where(quarter => !cachedQuarters.Contains(QuarterKey(quarter))) + .ToList(); + + if (missing.Count == 0) + { + // Written back all the same, so the published folder always carries the map the data was + // built with. A run that found it complete used to leave it out of its output, and the + // next daily run rebuilt it from 1.8 GB of N-PORT, possibly with a newer quarter. + WriteCache(Path.Combine(writeDirectory, CacheFileName), cached, cachedQuarters); + Log.Trace($"SEC13FTickerCrosswalk.Load(): cache holds {cached.Count} CUSIPs, nothing to fetch"); + return cached; + } + + var downloads = new List(); + foreach (var (year, quarter) in missing) + { + var path = download(Url(year, quarter), $"{QuarterKey((year, quarter))}_nport.zip"); + downloads.Add(path); + + Fold(cached, QuarterStart(year, quarter), BuildFromQuarter(path, year, quarter)); + cachedQuarters.Add(QuarterKey((year, quarter))); + } + + WriteCache(Path.Combine(writeDirectory, CacheFileName), cached, cachedQuarters); + + // The derived map is what is kept; each archive is several hundred megabytes. + foreach (var path in downloads) + { + File.Delete(path); + } + + Log.Trace($"SEC13FTickerCrosswalk.Load(): {cached.Count} CUSIPs after folding in " + + $"{string.Join(", ", missing.Select(QuarterKey))}"); + return cached; + } + + /// + /// Folds one data set's tickers into the map. The newest observation of a CUSIP wins whatever + /// order the data sets arrive in: a first build walks back from today, while a refresh adds + /// one newer quarter on top of the cache. + /// + internal static void Fold(Dictionary map, DateTime observed, + IEnumerable> tickers) + { + foreach (var (cusip, ticker) in tickers) + { + if (!map.TryGetValue(cusip, out var held) || held.Observed <= observed) + { + map[cusip] = new Entry(ticker, observed); + } + } + } + + /// Walks back from the current quarter until it has found the requested number of data sets. + private static List<(int Year, int Quarter)> FindAvailableQuarters(int quarters, Func exists) + { + var found = new List<(int, int)>(); + var probe = DateTime.UtcNow; + + // Twelve quarters of lookback is far more than the publication lag and stops the probe + // from walking to 2019 if the SEC ever moves the files. + for (var attempt = 0; attempt < 12 && found.Count < quarters; attempt++, probe = probe.AddMonths(-3)) + { + var year = probe.Year; + var quarter = (probe.Month - 1) / 3 + 1; + if (exists(Url(year, quarter))) + { + found.Add((year, quarter)); + } + } + + if (found.Count == 0) + { + throw new InvalidOperationException( + "SEC13FTickerCrosswalk.FindAvailableQuarters(): no N-PORT data set is published"); + } + + return found; + } + + /// + /// Builds the map for one quarter. Two passes over the archive, because the tables join on + /// HOLDING_ID and neither is sorted: the first collects the identifier rows that carry a + /// ticker, only 6.9 percent of them, and the second votes those tickers onto the CUSIP each + /// holding belongs to. + /// + private static IEnumerable> BuildFromQuarter(string path, int year, int quarter) + { + using var archive = ZipFile.OpenRead(path); + + var tickersByHolding = new Dictionary(StringComparer.Ordinal); + foreach (var fields in ReadTable(archive, IdentifierTable, "HOLDING_ID", "IDENTIFIER_TICKER")) + { + var ticker = Normalize(fields[1]); + if (ticker != null) + { + tickersByHolding[fields[0]] = ticker; + } + } + + var votes = new Dictionary>(StringComparer.Ordinal); + foreach (var fields in ReadTable(archive, HoldingTable, "HOLDING_ID", "ISSUER_CUSIP")) + { + if (!tickersByHolding.TryGetValue(fields[0], out var ticker)) + { + continue; + } + + var cusip = fields[1].Trim().ToUpperInvariant(); + + // Foreign issuers without a CUSIP are masked as all zeros in this data set. + if (cusip.Length != 9 || cusip == "000000000") + { + continue; + } + + if (!votes.TryGetValue(cusip, out var tally)) + { + votes[cusip] = tally = new Dictionary(StringComparer.Ordinal); + } + + tally.TryGetValue(ticker, out var count); + tally[ticker] = count + 1; + } + + Log.Trace($"SEC13FTickerCrosswalk.BuildFromQuarter(): {year}Q{quarter}: " + + $"{tickersByHolding.Count} holdings carried a ticker, {votes.Count} CUSIPs resolved"); + + return votes.Select(pair => new KeyValuePair( + pair.Key, + pair.Value.OrderByDescending(vote => vote.Value).ThenBy(vote => vote.Key, StringComparer.Ordinal).First().Key)) + .ToList(); + } + + /// + /// The requested columns of one N-PORT table. Short rows are skipped rather than thrown on, + /// so one malformed holding cannot stop the whole build. + /// + private static IEnumerable ReadTable(ZipArchive archive, string table, params string[] wanted) + { + return SEC13FFiles.ReadTable(archive, "N-PORT", table, skipShortRows: true, wanted) + .Select(row => wanted.Select(column => row.Fields[row.Columns[column]]).ToArray()); + } + + /// Cleans one raw ticker, returning null when it is not usable. + internal static string Normalize(string raw) + { + var ticker = VenueSuffix.Replace(raw.Trim().ToUpperInvariant(), string.Empty); + return ticker.Length > 0 && ticker != "N/A" && TickerShape.IsMatch(ticker) ? ticker : null; + } + + private static string Url(int year, int quarter) + => string.Format(CultureInfo.InvariantCulture, NPortUrlFormat, year, quarter); + + private static string QuarterKey((int Year, int Quarter) quarter) + => $"{quarter.Year}q{quarter.Quarter}"; + + /// + /// The day a data set's tickers are taken to be observed on: its quarter's first day. A ticker + /// renamed within that quarter fails the traded-under check in the downloader, so its CUSIP + /// stays unresolved until a newer quarter is folded in. Rare, and it never resolves wrongly. + /// + private static DateTime QuarterStart(int year, int quarter) + => new(year, quarter * 3 - 2, 1); + + private static Dictionary ReadCache(string path, out HashSet quarters) + { + var map = new Dictionary(StringComparer.Ordinal); + quarters = new HashSet(StringComparer.OrdinalIgnoreCase); + + if (!File.Exists(path)) + { + return map; + } + + foreach (var line in File.ReadLines(path)) + { + if (line.StartsWith("#", StringComparison.Ordinal)) + { + quarters.UnionWith(line.TrimStart('#').Split(',', StringSplitOptions.RemoveEmptyEntries) + .Select(value => value.Trim())); + continue; + } + + var fields = line.Split(','); + if (fields.Length == 3 && fields[0].Length == 9) + { + map[fields[0]] = new Entry(fields[1], + DateTime.ParseExact(fields[2], DateFormat.EightCharacter, CultureInfo.InvariantCulture)); + } + } + + return map; + } + + private static void WriteCache(string path, Dictionary map, HashSet quarters) + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + var lines = new List { "#" + string.Join(",", quarters.OrderBy(value => value, StringComparer.Ordinal)) }; + lines.AddRange(map.OrderBy(pair => pair.Key, StringComparer.Ordinal) + .Select(pair => $"{pair.Key},{pair.Value.Ticker},{pair.Value.Observed.ToString(DateFormat.EightCharacter, CultureInfo.InvariantCulture)}")); + + SEC13FFiles.WriteThenMove(path, stream => + { + using var writer = new StreamWriter(stream, leaveOpen: true); + foreach (var line in lines) + { + writer.WriteLine(line); + } + }); + } + } +} diff --git a/DataProcessing/SECEdgarClient.cs b/DataProcessing/SECEdgarClient.cs new file mode 100644 index 0000000..13d0313 --- /dev/null +++ b/DataProcessing/SECEdgarClient.cs @@ -0,0 +1,220 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using QuantConnect.Configuration; +using QuantConnect.Logging; +using QuantConnect.Util; + +namespace QuantConnect.DataProcessing +{ + /// + /// The HTTP side every SEC dataset processor shares: the User-Agent the SEC asks automated + /// readers for, its rate limit, retries with backoff, and downloads that never leave a truncated + /// file behind. Safe to call from several threads, which share the one rate gate. + /// + public class SECEdgarClient : IDisposable + { + private const int MaxRetries = 8; + + private readonly HttpClient _client = new() { Timeout = TimeSpan.FromMinutes(30) }; + private bool _userAgentSet; + + /// + /// Config key for the requests a second the client sends. The SEC allows ten; a long rebuild + /// runs lower, since EDGAR answered a day of sustained reading at ten with 503s and responses + /// slowed to seconds for the whole address, even at one request at a time. + /// + public const string RequestsPerSecondKey = "sec-requests-per-second"; + + private const int MaximumRequestsPerSecond = 10; + + private readonly RateGate _rateGate = new( + Math.Clamp(Config.GetInt(RequestsPerSecondKey, MaximumRequestsPerSecond), 1, MaximumRequestsPerSecond), + TimeSpan.FromSeconds(1)); + + /// The requests a second the client sends. + public int RequestsPerSecond => _rateGate.Occurrences; + + /// EDGAR's directory listings, by folder URL, read once per client. + private readonly Dictionary> _listings = new(StringComparer.Ordinal); + + /// + /// GETs a URL as text, with retry and backoff. false stops at + /// the first 404, for a file the SEC removed long ago rather than one it has not served yet. + /// + public string GetText(string url, bool retryNotFound = true) + { + return WithRetry(url, retryNotFound, () => + { + using var response = _client.GetAsync(url).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + return response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + }); + } + + /// + /// Downloads a file into a directory, reusing a copy already there. It lands as ".part" first, + /// so an interrupted run cannot leave a truncated file for the next one. + /// + public string DownloadFile(string url, string name, string directory) + { + Directory.CreateDirectory(directory); + var path = Path.Combine(directory, name); + if (File.Exists(path)) + { + Log.Trace($"SECEdgarClient.DownloadFile(): {name} already on disk"); + return path; + } + + var temporaryPath = path + ".part"; + WithRetry(url, true, () => + { + using var response = _client + .GetAsync(url, HttpCompletionOption.ResponseHeadersRead) + .GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + + using var source = response.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + using var destination = new FileStream(temporaryPath, FileMode.Create, FileAccess.Write); + source.CopyTo(destination); + return true; + }); + + File.Move(temporaryPath, path, overwrite: true); + Log.Trace($"SECEdgarClient.DownloadFile(): {name}, {new FileInfo(path).Length} bytes"); + return path; + } + + /// + /// Whether the SEC has published a file: false only on 404. Anything else fails once the + /// retries run out, since a 403 taken for "not published" would change a result quietly. + /// + public bool UrlExists(string url) + { + return WithRetry(url, true, () => + { + using var request = new HttpRequestMessage(HttpMethod.Head, url); + using var response = _client.Send(request); + if (response.StatusCode == HttpStatusCode.NotFound) + { + return false; + } + + response.EnsureSuccessStatusCode(); + return true; + }); + } + + /// + /// The names in one of EDGAR's directory listings. Read once per client, so a day published + /// after the run read its quarter is left to the next run. Fails like any other GET, a block included. + /// + public ISet ListDirectory(string url) + { + lock (_listings) + { + if (!_listings.TryGetValue(url, out var names)) + { + names = SECEdgarIndex.ListingNames(GetText(url + "index.json")); + _listings[url] = names; + } + + return names; + } + } + + /// + /// True when a failed request is worth asking again: transport errors, server errors, + /// throttling and a 404. Every file requested is one the SEC lists, so a 404 is its own + /// hiccup: the first filings of two new filers, in the 24 and 27 July 2026 indexes, answered + /// 404 for up to a minute and 200 afterwards. A 403 is a block, which outlasts any backoff. + /// + internal static bool IsWorthRetrying(Exception error) + { + var status = (error as HttpRequestException)?.StatusCode; + + return status == null + || (int)status >= 500 + || status == HttpStatusCode.NotFound + || status == HttpStatusCode.TooManyRequests + || status == HttpStatusCode.RequestTimeout; + } + + /// + /// Sends a request through the rate gate, retrying whatever IsWorthRetrying accepts with a + /// backoff that doubles up to two minutes, about four minutes in all: EDGAR served a new + /// filer's listed filing as a 404 for over a minute. The last failure is the one that surfaces. + /// + private T WithRetry(string url, bool retryNotFound, Func request) + { + RequireUserAgent(); + for (var attempt = 1; ; attempt++) + { + try + { + _rateGate.WaitToProceed(); + return request(); + } + catch (Exception err) when (attempt < MaxRetries && IsWorthRetrying(err) + && (retryNotFound || (err as HttpRequestException)?.StatusCode != HttpStatusCode.NotFound)) + { + Log.Trace($"SECEdgarClient.WithRetry(): {url} retry {attempt}/{MaxRetries} after: {err.Message}"); + Thread.Sleep(TimeSpan.FromSeconds(Math.Min(120, Math.Pow(2, attempt)))); + } + } + } + + /// + /// Sets the User-Agent the SEC asks automated readers for, from the same config keys the + /// reports dataset reads, on the first request: a run that never reaches the network, like + /// the unit tests, does not need them. + /// + private void RequireUserAgent() + { + lock (_client) + { + if (_userAgentSet) + { + return; + } + + var companyName = Config.Get("sec-user-agent-company-name"); + var companyEmail = Config.Get("sec-user-agent-company-email"); + if (string.IsNullOrEmpty(companyName) || string.IsNullOrEmpty(companyEmail)) + { + throw new ArgumentException("The SEC requires a company name and email to download data using " + + "automation. Set `sec-user-agent-company-name` and `sec-user-agent-company-email` in the config."); + } + + _client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", string.Join(" ", companyName, companyEmail)); + _userAgentSet = true; + } + } + + /// Disposes the HTTP client and the rate gate. + public void Dispose() + { + _client.DisposeSafely(); + _rateGate.DisposeSafely(); + GC.SuppressFinalize(this); + } + } +} diff --git a/DataProcessing/SECEdgarIndex.cs b/DataProcessing/SECEdgarIndex.cs new file mode 100644 index 0000000..466dc91 --- /dev/null +++ b/DataProcessing/SECEdgarIndex.cs @@ -0,0 +1,171 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Newtonsoft.Json; +using QuantConnect.DataSource; + +namespace QuantConnect.DataProcessing +{ + /// + /// EDGAR's daily form index and full submission files, which every SEC dataset read from EDGAR + /// goes through: where a day's index lives, whether it is published, the filings it lists, and + /// the documents and XML inside one submission. + /// + public static class SECEdgarIndex + { + /// Root of the daily indexes, one folder per year and quarter. + public const string DailyIndexRootUrl = "https://www.sec.gov/Archives/edgar/daily-index/"; + + /// Root the index's file paths are relative to. + public const string ArchivesBaseUrl = "https://www.sec.gov/Archives/"; + + private static readonly Regex DocumentBlock = new(@"(?.*?)", + RegexOptions.Compiled | RegexOptions.Singleline); + + private static readonly Regex DocumentType = new(@"(?[^\r\n<]+)", RegexOptions.Compiled); + + private static readonly Regex XmlBlock = new(@"(?.*?)", + RegexOptions.Compiled | RegexOptions.Singleline); + + /// One filing listed in a daily index, under one of the CIKs the filing names. + public readonly record struct Entry(string FormType, int Cik, DateTime Filed, string Path) + { + /// The accession number, which is the name of the submission file. + public string Accession => System.IO.Path.GetFileNameWithoutExtension(Path); + } + + /// One document of a submission: its type and, when it carries one, its XML. + public readonly record struct Document(string Type, string Xml); + + /// The daily form index, which lists a day's filings by form type. + public static string IndexUrl(DateTime day) + { + return $"{QuarterUrl(day)}{IndexFileName(day)}"; + } + + /// + /// Whether EDGAR lists the day's form index. The answer comes from its directory listings, + /// never from a failed request: EDGAR answers 403 both for an index that does not exist and + /// for a reader it has blocked, and a block taken for "no index" dropped the day for good. Each + /// folder is looked up in its parent's listing first, from the root, which always exists: a + /// year or quarter EDGAR has not created yet answers 403 as well. + /// + public static bool IsIndexPublished(DateTime day, Func> listDirectory) + { + var year = day.Year.ToString(CultureInfo.InvariantCulture); + return listDirectory(DailyIndexRootUrl).Contains(year) + && listDirectory($"{DailyIndexRootUrl}{year}/").Contains(QuarterName(day)) + && listDirectory(QuarterUrl(day)).Contains(IndexFileName(day)); + } + + /// The names in one of EDGAR's index.json directory listings. + public static HashSet ListingNames(string json) + { + var listing = JsonConvert.DeserializeObject(json)?.Directory + ?? throw new InvalidDataException("SECEdgarIndex.ListingNames(): not an EDGAR directory listing"); + + return (listing.Items ?? new List()).Select(item => item.Name).ToHashSet(StringComparer.Ordinal); + } + + /// + /// The filings of the form types matches, a regular expression + /// for the whole form type such as 13F-HR(?:/A)?. EDGAR lists a filing once for every + /// CIK it names, so a filing can come back more than once; keeps one. + /// + public static List ParseIndex(string text, string formPattern) + { + var line = new Regex( + $@"^(?
{formPattern})\s+.+?\s+(?\d+)\s+(?\d{{8}})\s+(?edgar/data/\S+\.txt)\s*$"); + + var entries = new List(); + foreach (var raw in text.Split('\n')) + { + var match = line.Match(raw.TrimEnd('\r')); + if (!match.Success) + { + continue; + } + + entries.Add(new Entry( + match.Groups["form"].Value, + int.Parse(match.Groups["cik"].Value, CultureInfo.InvariantCulture), + DateTime.ParseExact(match.Groups["date"].Value, DateFormat.EightCharacter, CultureInfo.InvariantCulture), + match.Groups["file"].Value)); + } + + return entries; + } + + /// One entry per accession, the first the index lists. + public static List DistinctFilings(IEnumerable entries) + { + var seen = new HashSet(StringComparer.Ordinal); + return entries.Where(entry => seen.Add(entry.Accession)).ToList(); + } + + /// The documents of a full submission file, in order, with the XML of those that carry it. + public static IEnumerable Documents(string submission) + { + foreach (Match document in DocumentBlock.Matches(submission)) + { + var body = document.Groups["body"].Value; + var xml = XmlBlock.Match(body); + yield return new Document( + DocumentType.Match(body).Groups["type"].Value.Trim(), + xml.Success ? xml.Groups["xml"].Value : null); + } + } + + /// Parses a document's XML, naming the submission when it is malformed. + public static XElement ParseXml(string xml, string source) + { + try + { + return XDocument.Parse(xml.Trim()).Root; + } + catch (Exception err) + { + throw new InvalidDataException($"SECEdgarIndex.ParseXml(): {source} carries malformed XML: {err.Message}", err); + } + } + + /// Elements by local name, whatever namespace and case the filer's software used. + public static IEnumerable Elements(XElement root, string name) + { + return root.DescendantsAndSelf().Where(element => element.Name.LocalName.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + /// The trimmed text of the first element with a local name, or null. + public static string Value(XElement root, string name) + { + return Elements(root, name).FirstOrDefault()?.Value.Trim(); + } + + private static string QuarterName(DateTime day) => $"QTR{(day.Month - 1) / 3 + 1}"; + + private static string QuarterUrl(DateTime day) => + $"{DailyIndexRootUrl}{day.Year.ToString(CultureInfo.InvariantCulture)}/{QuarterName(day)}/"; + + private static string IndexFileName(DateTime day) => + $"form.{day.ToString(DateFormat.EightCharacter, CultureInfo.InvariantCulture)}.idx"; + } +} diff --git a/DataProcessing/SECProcessingContext.cs b/DataProcessing/SECProcessingContext.cs new file mode 100644 index 0000000..4183d45 --- /dev/null +++ b/DataProcessing/SECProcessingContext.cs @@ -0,0 +1,101 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Globalization; +using System.IO; +using QuantConnect.Configuration; +using QuantConnect.Logging; + +namespace QuantConnect.DataProcessing +{ + /// + /// What the job hands every SEC dataset alike: the date it runs for and the folders it reads + /// and writes, from the environment and config keys the data fleet sets. + /// + /// The date the run is for, or null when it rebuilds the whole history + /// The temp-output-directory, which the job hands over empty and publishes + internal sealed record SECProcessingContext(DateTime? DeploymentDate, string OutputRoot) + { + private const string DeploymentDateVariable = "QC_DATAFLEET_DEPLOYMENT_DATE"; + private const string VendorName = "sec"; + + /// Where the SEC datasets are written: {temp-output-directory}/alternative/sec. + public string OutputDirectory => VendorFolder(OutputRoot); + + /// + /// The published SEC data an incremental run builds on. Not the output, which arrives empty. + /// + public string ProcessedDirectory { get; } = VendorFolder(Config.Get("processed-data-directory", Globals.DataFolder)); + + /// + /// Where downloads land. The job archives this folder after every run but does not restore + /// it before the next. + /// + public string RawDirectory { get; } = VendorFolder(Config.Get("raw-data-folder", "/raw")); + + /// + /// Reads the context, or logs why it cannot. A missing date is a misconfigured job rather + /// than a request for the whole history, which a dataset that supports it is asked for by + /// name with . + /// + /// The config key that asks the dataset for a full rebuild, or null when it has none + /// The context, whose date is null when the run rebuilds the whole history + /// True when the date is well formed, or absent with the rebuild asked for + public static bool TryCreate(string rebuildHistoryKey, out SECProcessingContext context) + { + context = null; + if (!TryParseDeploymentDate(rebuildHistoryKey, out var deploymentDate)) + { + return false; + } + + context = new SECProcessingContext(deploymentDate, Config.Get("temp-output-directory", "/temp-output-directory")); + return true; + } + + internal static bool TryParseDeploymentDate(string rebuildHistoryKey, out DateTime? deploymentDate) + { + deploymentDate = null; + + var raw = Environment.GetEnvironmentVariable(DeploymentDateVariable); + if (string.IsNullOrWhiteSpace(raw)) + { + if (rebuildHistoryKey != null && Config.GetBool(rebuildHistoryKey)) + { + return true; + } + + Log.Error($"SECProcessingContext.TryParseDeploymentDate(): {DeploymentDateVariable} is not set" + + (rebuildHistoryKey == null ? string.Empty : $". Set it, or set \"{rebuildHistoryKey}\": true to rebuild the whole history")); + return false; + } + + // A malformed date must not quietly become a full history run: that would turn a daily + // job into a complete refetch of five gigabytes without anyone noticing. + if (!DateTime.TryParseExact(raw.Trim(), "yyyyMMdd", CultureInfo.InvariantCulture, + DateTimeStyles.None, out var parsed)) + { + Log.Error($"SECProcessingContext.TryParseDeploymentDate(): {DeploymentDateVariable} '{raw}' is not yyyyMMdd"); + return false; + } + + deploymentDate = parsed; + return true; + } + + private static string VendorFolder(string root) => Path.Combine(root, "alternative", VendorName); + } +} diff --git a/QuantConnect.DataSource.csproj b/QuantConnect.DataSource.csproj index 70a709c..e5bbc05 100644 --- a/QuantConnect.DataSource.csproj +++ b/QuantConnect.DataSource.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -22,12 +22,10 @@ - - - - - - + + + diff --git a/README.md b/README.md index 073f8e4..2c16e8e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,61 @@ -# Lean.DataSource.QuiverWallStreetBets -Example production implementation for our data marketplace Quiver Quantitative WallStreetBets dataset. +# Lean.DataSource.SEC -# Implementing your own data source -To learn more about implementing your own data source for our marketplace, visit the [LeanDataSdk](https://github.com/QuantConnect/LeanDataSdk) repository for more information. +LEAN data sources built from U.S. Securities and Exchange Commission filings, for the QuantConnect +data marketplace. + +| Dataset | Data classes | Files | +|---|---|---| +| SEC Reports: 10-K, 10-Q and 8-K filings | `SECReport10K`, `SECReport10Q`, `SECReport8K` | `alternative/sec//_.zip` | +| SEC Whales: Form 13F institutional holdings, every position as its manager filed it | `SEC13FHoldings`, a collection of `SEC13FHolding` | `alternative/sec/13f/.zip`, one entry per filing date, and `managers.csv` | + +`SEC13FAlgorithm` and the `SECReport*Algorithm` files are the demonstration algorithms, in C# and +Python. The `listing-*.md` files are the marketplace listings. + +## Processing + +`DataProcessing` builds `process.dll`, which runs one dataset per invocation, chosen with the +`dataset-name` config key: `reports` (the default) or `13f`. + +Both read the same environment and config: + +| Key | Meaning | +|---|---| +| `QC_DATAFLEET_DEPLOYMENT_DATE` (environment) | The date the run is for, `yyyyMMdd` | +| `temp-output-directory` | Where the output is written; it must start empty | +| `processed-data-directory` | The published data an incremental 13F run adds to; defaults to the data folder | +| `raw-data-folder` | Where downloads are kept | +| `sec-user-agent-company-name`, `sec-user-agent-company-email` | The User-Agent the SEC asks automated readers for | +| `sec-requests-per-second` | Request rate against the SEC, 10 at most | + +### Form 13F + +With a deployment date the run reads that day's filings from EDGAR's daily index, and any of the +ten days before it whose index came late, and adds them to the published history. Without one, and +with `sec-13f-rebuild-history` set to `true`, it rebuilds the whole history: the SEC's Form 13F data +sets from 2013 as far as they reach, then EDGAR day by day. A full rebuild takes about an hour, +3 GB of downloads, 4 GB of output and some 10 GB of temporary disk. + +The 13F run depends on three things in the LEAN data folder, none of which it downloads: + +| Data | Path under the data folder | Used for | +|---|---|---| +| Map files | `equity/usa/map_files/map_files_.zip` | The ticker a security traded under on a filing date, which names its file. The zip is required: the run fails without one | +| Security database | `symbol-properties/security-database.csv` | Resolving a reported CUSIP, and the ISIN built from it, to a security. Without it only the N-PORT crosswalk resolves anything, and coverage falls from about 98 to 88 percent of reported value | +| Coarse universe files | `equity/usa/fundamental/coarse/.csv` | The close of each quarter's last trading day, from 2012. It decides whether a filing states values in dollars or thousands, vets the N-PORT crosswalk matches, and picks the fund an option on a fund family is written on. Only the quarter-end days are read, up to seven days back. Without them the run logs an error and falls back to the SEC's unit rule for the filing date | + +The CUSIP to ticker crosswalk comes from the SEC's Form N-PORT data sets. It is downloaded once, +about 1.8 GB, and then kept beside the output as `nport-crosswalk.txt`. + +## Tests + +``` +dotnet build tests/Tests.csproj +dotnet test tests/Tests.csproj +``` + +`SEC13FPilotTests` is explicit: it runs the processor over a folder of SEC 13F tables named by +`SEC13F_PILOT_RAW`, against the LEAN data folder named by `SEC13F_PILOT_DATA`. + +## Implementing your own data source + +See the [LeanDataSdk](https://github.com/QuantConnect/LeanDataSdk) repository. diff --git a/SEC13FAlgorithm.cs b/SEC13FAlgorithm.cs new file mode 100644 index 0000000..ea6be54 --- /dev/null +++ b/SEC13FAlgorithm.cs @@ -0,0 +1,174 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Linq; +using System.Collections.Generic; +using QuantConnect.Data; +using QuantConnect.Orders; +using QuantConnect.Algorithm; +using QuantConnect.Algorithm.Framework.Portfolio; +using QuantConnect.DataSource; + +namespace QuantConnect.DataLibrary.Tests +{ + /// + /// 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 PeriodEnd. + /// + /// The 13F symbols returned by AddData 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. + /// + public class SEC13FAlgorithm : QCAlgorithm + { + /// + /// 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. + /// + private static readonly HashSet Managers = [1336528, 2026053]; + + /// The shares the manager reported for each equity, by the quarter they describe. + private readonly Dictionary> _sharesByEquity = []; + + /// The newest quarter the managers have reported, for any name. + private DateTime _latestPeriod; + + private bool _rebalance; + + /// + /// Initialise the data and resolution required, as well as the cash and start-end dates. + /// + public override void Initialize() + { + // Two filings fall in this window: the March 2026 quarter, filed on 15 May, and the June + // quarter, filed on 14 August. Each reaches the algorithm at midnight after its filing date. + SetStartDate(2026, 5, 1); + SetEndDate(2026, 8, 31); + SetCash(100000); + + foreach (var ticker in new[] { "META", "UBER", "QSR", "MSFT", "BN", "HTZ", "AMZN" }) + { + var equity = AddEquity(ticker, Resolution.Daily).Symbol; + AddData(equity); + _sharesByEquity[equity] = []; + } + } + + /// + /// OnData event is the primary entry point for your algorithm. Each new data point is here. + /// + /// Slice object keyed by symbol containing the data + public override void OnData(Slice slice) + { + foreach (var (dataSymbol, point) in slice.Get()) + { + // The data symbol carries the equity it was subscribed on as its underlying. + var equity = dataSymbol.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. + foreach (var holding in point.OfType().Where(holding => + Managers.Contains(holding.ManagerCik) && holding.FormType == "13F-HR" && + holding.AmountType == "SH" && !holding.PutCall.HasValue)) + { + var shares = _sharesByEquity[equity]; + shares[holding.PeriodEnd] = shares.GetValueOrDefault(holding.PeriodEnd) + (holding.Amount ?? 0); + _latestPeriod = holding.PeriodEnd > _latestPeriod ? holding.PeriodEnd : _latestPeriod; + _rebalance = true; + + Log($"{Time:yyyy-MM-dd} {equity.Value} - {holding.ManagerName} reports {holding.Amount:N0} shares, " + + $"{holding.MarketValue:C0}, for {holding.PeriodEnd:yyyy-MM-dd}"); + } + } + + // A 13F point arrives at midnight the day after its filing date, which is not + // necessarily a day the equities print a bar, so the orders wait for prices. + if (!_rebalance || slice.Bars.Count == 0) + { + return; + } + + _rebalance = false; + + // The manager's trades, which no filing states: the change between two reported quarters. + foreach (var (equity, shares) in _sharesByEquity.Where(kvp => kvp.Value.Count > 1)) + { + var (previous, latest) = (shares.Values.ElementAt(shares.Count - 2), shares.Values.Last()); + + // A quarter the manager opened the position in reports no shares before it. + var change = previous > 0 ? $" ({latest / previous - 1:+0.0%;-0.0%})" : string.Empty; + Log($"{Time:yyyy-MM-dd} {equity.Value}: {previous:N0} -> {latest:N0} shares{change} " + + $"between {shares.Keys.ElementAt(shares.Count - 2):yyyy-MM-dd} and {shares.Keys.Last():yyyy-MM-dd}"); + } + + // With one quarter known, hold what the manager holds. With two, hold what it added to. + var selected = _sharesByEquity + .Select(kvp => (Equity: kvp.Key, Quarters: QuartersOf(kvp.Value))) + .Where(entry => entry.Quarters.Count > 0) + .Where(entry => entry.Quarters.Count == 1 + ? entry.Quarters[0] > 0 + : entry.Quarters[^1] > entry.Quarters[^2]) + .Select(entry => entry.Equity) + .ToList(); + + if (selected.Count == 0) + { + Liquidate(); + return; + } + + Log($"{Time:yyyy-MM-dd} holding {string.Join(", ", selected.Select(symbol => symbol.Value))}"); + SetHoldings(selected.Select(symbol => new PortfolioTarget(symbol, 1m / selected.Count)).ToList(), + liquidateExistingHoldings: true); + } + + /// + /// The shares reported for one equity, oldest quarter first, with a closing zero for a name + /// the manager has stopped reporting. 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. + /// + private List QuartersOf(SortedDictionary shares) + { + var quarters = shares.Values.ToList(); + if (shares.Count > 0 && shares.Keys.Last() < _latestPeriod) + { + quarters.Add(0m); + } + + return quarters; + } + + /// + /// Order fill event handler. + /// + /// Order event details + public override void OnOrderEvent(OrderEvent orderEvent) + { + if (orderEvent.Status == OrderStatus.Filled) + { + Debug($"{Time} - Filled: {orderEvent.Symbol} {orderEvent.FillQuantity}"); + } + } + } +} diff --git a/SEC13FAlgorithm.py b/SEC13FAlgorithm.py new file mode 100644 index 0000000..f7aae52 --- /dev/null +++ b/SEC13FAlgorithm.py @@ -0,0 +1,117 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from AlgorithmImports import * +from QuantConnect.DataSource import * + + +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.''' + + # 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. + MANAGERS = {1336528, 2026053} + + def initialize(self) -> None: + # Two filings fall in this window: the March 2026 quarter, filed on 15 May, and the June + # quarter, filed on 14 August. Each reaches the algorithm at midnight after its filing date. + self.set_start_date(2026, 5, 1) + 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"]: + equity = self.add_equity(ticker, Resolution.DAILY).symbol + self.add_data(SEC13FHoldings, equity) + self._shares_by_equity[equity] = {} + + 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. + 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 + + 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) + self._rebalance = True + + self.log(f"{self.time:%Y-%m-%d} {equity.value} - {holding.manager_name} reports " + f"{holding.amount:,.0f} shares, {holding.market_value:,.0f} USD, " + f"for {holding.period_end:%Y-%m-%d}") + + # A 13F point arrives at midnight the day after its filing date, which is not necessarily + # a day the equities print a bar, so the orders wait for prices. + if not self._rebalance or slice.bars.count == 0: + return + + 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}") + + # 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: + quarters.append(0) + + if quarters and (quarters[0] > 0 if len(quarters) == 1 else quarters[-1] > quarters[-2]): + selected.append(equity) + + if not selected: + 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) + + def on_order_event(self, order_event: OrderEvent) -> None: + if order_event.status == OrderStatus.FILLED: + self.debug(f"{self.time} - Filled: {order_event.symbol} {order_event.fill_quantity}") diff --git a/SEC13FHolding.cs b/SEC13FHolding.cs new file mode 100644 index 0000000..f9dc156 --- /dev/null +++ b/SEC13FHolding.cs @@ -0,0 +1,373 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using NodaTime; +using QuantConnect.Data; +using QuantConnect.Util; +using static QuantConnect.StringExtensions; +using System.Runtime.CompilerServices; + +// The processor writes the line layout this assembly reads, so both use one implementation of it. +[assembly: InternalsVisibleTo("process")] +[assembly: InternalsVisibleTo("Tests")] + +namespace QuantConnect.DataSource +{ + /// + /// One position as a single institutional manager reported it on a single SEC Form 13F + /// submission. Managers exercising discretion over at least 100 million dollars must file a + /// Form 13F within 45 days of quarter end, listing the covered securities they hold. + /// + /// Nothing here is summed or otherwise derived: every field is the value the SEC publishes for + /// that line of that filing's information table. A manager that reports the same security on + /// two lines, which the rules allow when the discretion differs, produces two records, and they + /// are left apart. Holder counts, quarter-over-quarter change and concentration are all + /// derivable from the records of a day and are left to the algorithm. + /// + /// This is the factory that reads one line. The points an algorithm receives are + /// SEC13FHoldings, the collection of every record a security carries for one + /// filing date. + /// + public class SEC13FHolding : BaseData + { + // The frozen layout: the filing date, the filing's identity, the reported quarter, the + // position, the voting authority and the two confidential treatment columns. + private const int ExpectedColumns = 20; + + /// Format of the filing date column, which is also the name of the file. + public const string FilingDateFormat = "yyyyMMdd"; + + /// + /// EDGAR accession number of the submission this line was reported on, such as + /// 0001067983-26-000012. It identifies the filing on the SEC's own site and is what groups + /// the records of one submission back together. + /// + public string AccessionNumber { get; set; } + + /// + /// Central Index Key of the manager that filed the submission. It is the stable identity of + /// a fund across name changes, which is why it, and not the name, is carried on every + /// line of the files. The names live once in managers.csv beside the dataset. + /// + public int ManagerCik { get; set; } + + /// + /// Name of the manager as its most recent cover page states it, read from managers.csv, with + /// any comma taken out because that file is split on every one. It is the current name even + /// on an old filing, and null for a CIK the file does not carry. + /// + public string ManagerName { get; set; } + + /// + /// The quarter the position is reported for, which is the SEC PERIODOFREPORT. It is carried + /// rather than derived from Time because the two are unrelated: late + /// filings and amendments mean one filing date carries several different reported quarters, + /// and the gap between them runs from zero to years. + /// + public DateTime PeriodEnd { get; set; } + + /// + /// Submission type, which is 13F-HR for a holdings report and 13F-HR/A for an amendment. + /// A 13F-NT notice reports no positions and so contributes no records at all. + /// + 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. + /// + public string AmendmentType { get; set; } + + /// Sequence number of the amendment, or null on an original filing. + public int? AmendmentNumber { get; set; } + + /// + /// Class of the security as the manager titled it, such as COM or CL A. It is free text + /// that the filer writes, so it varies between managers for the same security. + /// + public string TitleOfClass { get; set; } + + /// + /// Size of the position, which is a number of shares when AmountType is SH + /// and a principal amount when it is PRN. The two are not comparable and are deliberately + /// left in one field with its unit beside it, as the SEC reports them. + /// + public decimal? Amount { get; set; } + + /// Unit of Amount: SH for shares, PRN for a principal amount. + public string AmountType { get; set; } + + /// + /// Market value of the position exactly as the manager reported it, in the unit the filing + /// used. Before 2023 the SEC asked for thousands of dollars and since then for whole + /// dollars, and filers on both sides of that change ignore the instruction, so the number + /// is published untouched with ValueScale beside it. + /// + public decimal? ReportedValue { get; set; } + + /// + /// The power of ten that turns ReportedValue into whole dollars: 3 for a value + /// stated in thousands, 0 for one already in dollars, and -3 for a line that overstated its + /// value a thousandfold, which happens often enough to matter. + /// + /// This is the one reading in the record that the SEC does not publish. It comes from the + /// filing's period and from the size of the value against the security's close, because + /// filers disagree with the instruction often enough that the period alone is wrong. It is + /// carried beside the reported number rather than multiplied into it, so that what the + /// manager filed stays readable and this judgement stays separable from it. Use + /// MarketValue to apply it. + /// + public int ValueScale { get; set; } + + /// + /// Market value of the position in whole dollars. Worked out from ReportedValue + /// and ValueScale rather than carried as a column of its own, so the three can never disagree. + /// + public decimal? MarketValue => ReportedValue * PowerOfTen(ValueScale); + + /// Ten to the , in decimal so the result stays exact. + internal static decimal PowerOfTen(int scale) + { + var power = 1m; + for (var i = 0; i < Math.Abs(scale); i++) + { + power *= 10m; + } + + return scale < 0 ? 1m / power : power; + } + + /// + /// Whether the position is an option on the security rather than the security itself, and + /// on which side. Null for a holding of the security. An option line states the shares + /// underlying the contracts, not the number of contracts. + /// + public OptionRight? PutCall { get; set; } + + /// + /// Who exercises investment discretion over the position: SOLE for the filing manager + /// alone, DFND when it is defined by other managers, OTR otherwise. + /// + public string InvestmentDiscretion { get; set; } + + /// + /// The other managers that share the position, as the sequence numbers the filing gives + /// them on its cover page, separated by semicolons. Empty when the manager reports alone. + /// + public string OtherManager { get; set; } + + /// Shares over which the manager holds sole voting authority. + public decimal? VotingSole { get; set; } + + /// Shares over which the manager shares voting authority. + public decimal? VotingShared { get; set; } + + /// Shares over which the manager holds no voting authority. + public decimal? VotingNone { get; set; } + + /// + /// True when the submission this line belongs to withheld other positions under + /// confidential treatment. The filing is then incomplete by design and the withheld + /// positions surface in a later one, so the flag is carried rather than silently ignored. + /// + public bool ConfidentialOmitted { get; set; } + + /// + /// The date a previously confidential filing was originally made, which the SEC publishes + /// as DATEREPORTED. It is filled on about two filings in a thousand and is null on the + /// rest, so it marks positions that were withheld and later released rather than serving as + /// a timestamp. The timestamp is Time, the filing date. + /// + public DateTime? DateReported { get; set; } + + /// + /// The record covers the filing date it is stamped with, ending at midnight that night. + /// + /// LEAN emits a point at its end time rather than at its time, so this is what decides when + /// an algorithm sees the filing: the day's filings all arrive at 00:00 the following day, + /// after EDGAR has finished listing that day at about 22:05 ET. Nothing is readable before + /// it was filed, and a whole day of filings arrives at once instead of trickling in. + /// + public override DateTime EndTime => Time.AddDays(1); + + /// Name of the dataset's folder under alternative/sec/, which is where its files live. + public static string ReportFolder => "13f"; + + /// Creates a new default instance. + public SEC13FHolding() + { + } + + /// + /// Location of the source file. One zip per security holds one entry per filing date, so + /// that the dataset stays at a file per security instead of the eight and a half million a + /// loose file per date would take. LEAN reads the entry straight out of the zip. + /// + public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) + { + return new SubscriptionDataSource( + Path.Combine( + Globals.DataFolder, + "alternative", + "sec", + ReportFolder, + $"{config.Symbol.Value.ToLowerInvariant()}.zip#{date.ToStringInvariant(FilingDateFormat)}.csv" + ), + SubscriptionTransportMedium.LocalFile, + FileFormat.FoldingCollection + ); + } + + /// Parses one line of the file into one reported position. + public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) + { + var csv = line.Split(','); + + // A truncated line is skipped rather than thrown on: LEAN takes an exception out of + // Reader as a reader error and drops the line, so throwing would turn a silent skip + // into a logged one and nothing more. The test is "fewer than" and not "not equal to", + // so a column appended in a later revision of the file leaves every existing one + // readable instead of muting the whole dataset. + if (csv.Length < ExpectedColumns) + { + return null; + } + + var point = Parse(csv); + point.Symbol = config.Symbol; + point.ManagerName = SEC13FManagerNameProvider.GetName(point.ManagerCik); + return point; + } + + /// Reads one already split line into a point that has no symbol yet. + internal static SEC13FHolding Parse(string[] csv) + { + var point = new SEC13FHolding + { + Time = DateTime.ParseExact(csv[0], FilingDateFormat, CultureInfo.InvariantCulture), + AccessionNumber = csv[1], + ManagerCik = int.Parse(csv[2], NumberStyles.Integer, CultureInfo.InvariantCulture), + PeriodEnd = DateTime.ParseExact(csv[3], FilingDateFormat, CultureInfo.InvariantCulture), + FormType = csv[4], + AmendmentType = csv[5], + AmendmentNumber = ParseCount(csv[6]), + TitleOfClass = csv[7], + Amount = ParseMeasure(csv[8]), + AmountType = csv[9], + ReportedValue = ParseMeasure(csv[10]), + ValueScale = int.Parse(csv[11], NumberStyles.Integer, CultureInfo.InvariantCulture), + PutCall = ParsePutCall(csv[12]), + InvestmentDiscretion = csv[13], + OtherManager = csv[14], + VotingSole = ParseMeasure(csv[15]), + VotingShared = ParseMeasure(csv[16]), + VotingNone = ParseMeasure(csv[17]), + ConfidentialOmitted = csv[18] == "1", + DateReported = ParseOptionalDate(csv[19]) + }; + + // The point's value is the position in dollars, which is the one measure of a holding + // that is comparable between managers and between securities. + point.Value = point.MarketValue ?? 0m; + return point; + } + + /// Parses one measure column, where an empty field is an absent reading. + internal static decimal? ParseMeasure(string value) + { + return value.IfNotNullOrEmpty(s => decimal.Parse(s, NumberStyles.Any, CultureInfo.InvariantCulture)); + } + + /// Parses one whole number column, where an empty field is an absent reading. + internal static int? ParseCount(string value) + { + return value.IfNotNullOrEmpty(s => int.Parse(s, NumberStyles.Integer, CultureInfo.InvariantCulture)); + } + + /// Parses one date column, where an empty field is an absent reading. + internal static DateTime? ParseOptionalDate(string value) + { + return value.IfNotNullOrEmpty(s => DateTime.ParseExact(s, FilingDateFormat, CultureInfo.InvariantCulture)); + } + + /// Parses the option column, where an empty field means the security itself. + internal static OptionRight? ParsePutCall(string value) + { + return value switch + { + "C" => OptionRight.Call, + "P" => OptionRight.Put, + _ => null + }; + } + + /// Data time zone (Eastern, the SEC filing time zone). + public override DateTimeZone DataTimeZone() => TimeZones.NewYork; + + /// Supported resolutions (Daily only, the quarterly cadence is modeled as Daily). + public override List SupportedResolutions() => DailyResolution; + + /// Default resolution. + public override Resolution DefaultResolution() => Resolution.Daily; + + /// Sparse data: a security is only reported on the days managers file for it. + public override bool IsSparseData() => true; + + /// Linked to Equities, so renames and delistings are applied via map files. + public override bool RequiresMapping() => true; + + /// Creates a copy of the instance. + public override BaseData Clone() + { + return new SEC13FHolding + { + Symbol = Symbol, + Time = Time, + Value = Value, + AccessionNumber = AccessionNumber, + ManagerCik = ManagerCik, + ManagerName = ManagerName, + PeriodEnd = PeriodEnd, + FormType = FormType, + AmendmentType = AmendmentType, + AmendmentNumber = AmendmentNumber, + TitleOfClass = TitleOfClass, + Amount = Amount, + AmountType = AmountType, + ReportedValue = ReportedValue, + ValueScale = ValueScale, + PutCall = PutCall, + InvestmentDiscretion = InvestmentDiscretion, + OtherManager = OtherManager, + VotingSole = VotingSole, + VotingShared = VotingShared, + VotingNone = VotingNone, + ConfidentialOmitted = ConfidentialOmitted, + DateReported = DateReported + }; + } + + /// String representation for debugging. + public override string ToString() + { + return Invariant($"{Symbol} - {ManagerName ?? $"CIK {ManagerCik}"} for {PeriodEnd:yyyy-MM-dd}: {Amount} {AmountType}, {MarketValue:C0}"); + } + } +} diff --git a/SEC13FHoldings.cs b/SEC13FHoldings.cs new file mode 100644 index 0000000..892fb39 --- /dev/null +++ b/SEC13FHoldings.cs @@ -0,0 +1,98 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NodaTime; +using QuantConnect.Data; +using QuantConnect.Data.UniverseSelection; +using QuantConnect.Util; + +namespace QuantConnect.DataSource +{ + /// + /// Every SEC Form 13F position reported for one security on one filing date. A day's point + /// carries one SEC13FHolding per reported line, so a manager that filed for the security that + /// day appears once for each line it reported it on, and a day on which several managers filed + /// carries all of them. + /// + /// The collection carries no totals of its own. How many managers hold the security and how + /// many shares they hold between them are counts over the records, and no 13F filing states + /// either, so they are left to the algorithm. + /// + public class SEC13FHoldings : BaseDataCollection + { + // Nothing may be declared here but the overrides below. LEAN builds the collection itself, + // in BaseDataCollectionAggregatorReader, and sets only its symbol and its timestamps, so a + // measure added to this class would silently stay null for every point ever read. + + private static readonly SEC13FHolding _factory = new(); + + /// + /// Location of the source file. One zip per security holds one entry per filing date, so + /// that the dataset stays at a file per security instead of the eight and a half million a + /// loose file per date would take. LEAN reads the entry straight out of the zip. + /// + public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode) + { + return _factory.GetSource(config, date, isLiveMode); + } + + /// + /// Reads one line of the file into one reported position. The engine folds the lines this + /// returns into the collection, grouping them by their end time, and every line of a file + /// carries the same filing date, so one file gives one point. + /// + public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode) + { + return _factory.Reader(config, line, date, isLiveMode); + } + + /// Creates a copy of the instance. + public override BaseData Clone() + { + return new SEC13FHoldings + { + Symbol = Symbol, + Time = Time, + EndTime = EndTime, + Data = Data?.ToList(point => point.Clone()) + }; + } + + /// Data time zone (Eastern, the SEC filing time zone). + public override DateTimeZone DataTimeZone() => _factory.DataTimeZone(); + + /// Supported resolutions (Daily only, the quarterly cadence is modeled as Daily). + public override List SupportedResolutions() => _factory.SupportedResolutions(); + + /// Default resolution. + public override Resolution DefaultResolution() => _factory.DefaultResolution(); + + /// Sparse data: a security is only reported on the days managers file for it. + public override bool IsSparseData() => _factory.IsSparseData(); + + /// Linked to Equities, so renames and delistings are applied via map files. + public override bool RequiresMapping() => _factory.RequiresMapping(); + + /// String representation for debugging. + public override string ToString() + { + return $"[{string.Join(",", Data.Select(point => point.ToString()))}]"; + } + } +} diff --git a/SEC13FManagerNameProvider.cs b/SEC13FManagerNameProvider.cs new file mode 100644 index 0000000..421820b --- /dev/null +++ b/SEC13FManagerNameProvider.cs @@ -0,0 +1,110 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using QuantConnect.Interfaces; +using QuantConnect.Logging; +using QuantConnect.Util; + +namespace QuantConnect.DataSource +{ + /// + /// The names of the managers that file Form 13F, by CIK, read from the managers.csv published + /// beside the dataset. A name is the one the manager's most recent cover page states, so an old + /// filing shows the name the manager carries today. + /// + public static class SEC13FManagerNameProvider + { + private const string FileName = "managers.csv"; + + /// How long a read that found no file waits before trying again. + internal static TimeSpan RetryInterval = TimeSpan.FromMinutes(5); + + private static readonly object _lock = new(); + private static Dictionary _names; + private static DateTime _loaded; + private static DateTime _attempted; + + /// The manager's name, or null for a CIK the file does not carry. + public static string GetName(int managerCik) + { + lock (_lock) + { + // Read again when the day changes, since the file gains the managers that filed for + // the first time each night. A read that found no file is not an answer: stamping + // the day for it would leave every name null until midnight over one missed fetch. + // It is retried, but not on every line, since a data folder that simply does not + // carry the file would otherwise never stop asking for it. + var now = DateTime.UtcNow; + if (_names == null || (_loaded != now.Date && now - _attempted >= RetryInterval)) + { + _attempted = now; + var read = Read(); + if (read != null) + { + _names = read; + _loaded = now.Date; + } + + _names ??= []; + } + + return _names.GetValueOrDefault(managerCik); + } + } + + /// Drops the names read so far, for tests that move the data folder. + internal static void Reset() + { + lock (_lock) + { + _names = null; + _loaded = default; + _attempted = default; + } + } + + /// The names in the file, or null when there is no file to read. + private static Dictionary Read() + { + var path = Path.Combine(Globals.DataFolder, "alternative", "sec", SEC13FHolding.ReportFolder, FileName); + + // Through the data provider where there is one, so the cloud fetches the file. + using var stream = Composer.Instance.GetPart()?.Fetch(path) + ?? (File.Exists(path) ? File.OpenRead(path) : null); + if (stream == null) + { + Log.Trace($"SEC13FManagerNameProvider.Read(): no {FileName} at {path}, so every ManagerName is null"); + return null; + } + + Dictionary names = []; + using var reader = new StreamReader(stream); + while (reader.ReadLine() is { } line) + { + var separator = line.IndexOf(','); + if (separator > 0 && int.TryParse(line.AsSpan(0, separator), NumberStyles.Integer, CultureInfo.InvariantCulture, out var cik)) + { + names[cik] = line[(separator + 1)..]; + } + } + + return names; + } + } +} diff --git a/listing-about-whales.md b/listing-about-whales.md new file mode 100644 index 0000000..c4007b8 --- /dev/null +++ b/listing-about-whales.md @@ -0,0 +1,251 @@ +## 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. + +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. + +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 +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. + +## 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. + +## Getting Started + +```python +self._symbol = self.add_equity("AAPL", Resolution.DAILY).symbol +self._holdings_symbol = self.add_data(SEC13FHoldings, self._symbol).symbol +``` +```csharp +_symbol = AddEquity("AAPL", Resolution.Daily).Symbol; +_holdingsSymbol = AddData(_symbol).Symbol; +``` + +## Data Summary + +The following table describes the dataset properties: + +| Property | Value | +| --- | --- | +| Start Date | May 2013 | +| Asset Coverage | 8,520 US Equities | +| Data Density | Sparse | +| 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. + +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. + +## Example Applications + +SEC Whales lets you see what large holders actually hold and trade against how crowded a name is. +Examples include the following strategies: + +- Screening for crowding by counting the distinct managers reporting each security, and for + de-crowding by taking the securities whose count fell hardest against the previous quarter. +- Building an ownership change momentum signal from the quarter over quarter move in reported + shares, and going long the names institutions are accumulating. +- 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 + +| Field | Value | +| --- | --- | +| name | SEC Whales | +| url | sec-whales | +| vendorName | U.S. Securities and Exchange Commission | +| website | https://www.sec.gov | +| history | May 2013 | +| reach | 8,520 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 | + +Tags: Financial Market Data + +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
  • +
  • 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 new file mode 100644 index 0000000..cefe341 --- /dev/null +++ b/listing-documentation-whales.md @@ -0,0 +1,258 @@ +## Requesting Data + +SEC Whales carries Form 13F institutional holdings today. To add it to your algorithm, call the +**AddData** method with the `Symbol` of an Equity you already subscribe to. Save a reference to the dataset **Symbol** so you +can access the data later in your algorithm. + +```python +class SEC13FDataAlgorithm(QCAlgorithm): + + def initialize(self) -> None: + # Coverage runs 2013-05-20 to 2026-05-29 + self.set_start_date(2020, 10, 1) + self.set_end_date(2020, 12, 31) + self.set_cash(100000) + + self._symbol = self.add_equity("AAPL", Resolution.DAILY).symbol + self._dataset_symbol = self.add_data(SEC13FHoldings, self._symbol).symbol +``` +```csharp +public class SEC13FDataAlgorithm : QCAlgorithm +{ + private Symbol _symbol, _datasetSymbol; + + public override void Initialize() + { + // Coverage runs 2013-05-20 to 2026-05-29 + SetStartDate(2020, 10, 1); + SetEndDate(2020, 12, 31); + SetCash(100000); + + _symbol = AddEquity("AAPL", Resolution.Daily).Symbol; + _datasetSymbol = AddData(_symbol).Symbol; + } +} +``` + +## Accessing Data + +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. + +```python +def on_data(self, slice: Slice) -> None: + if self._dataset_symbol in slice: + for holding in slice[self._dataset_symbol]: + self.log(f"CIK {holding.manager_cik}: {holding.amount} {holding.amount_type}") +``` +```csharp +public override void OnData(Slice slice) +{ + if (slice.ContainsKey(_datasetSymbol)) + { + SEC13FHoldings point = slice[_datasetSymbol]; + foreach (SEC13FHolding holding in point) + { + Log($"CIK {holding.ManagerCik}: {holding.Amount} {holding.AmountType}"); + } + } +} +``` + +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. + +### 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: + +```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") +``` +```csharp +public override void OnData(Slice slice) +{ + foreach (var kvp in slice.Get()) + { + var shareLines = 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"); + } +} +``` + +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 + +`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. + +```python +shares = holding.amount or 0 +``` +```csharp +var shares = holding.Amount.GetValueOrDefault(); +``` + +## Historical Data + +To get historical Form 13F data, call the **History** method with the +dataset **Symbol**. If there is no data in the period you request, the history result is empty. + +```python +# 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 +history_df = self.history(SEC13FHoldings, self._dataset_symbol, timedelta(days=60), Resolution.DAILY, flatten=True) + +# Dataset objects, one per filing date +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. + +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. + +For more information about historical data, see [History Requests](https://www.quantconnect.com/docs/v2/writing-algorithms/historical-data/history-requests). + +## Remove Subscriptions + +To remove your subscription to SEC Whales data, call the **RemoveSecurity** +method. + +```python +self.remove_security(self._dataset_symbol) +``` +```csharp +RemoveSecurity(_datasetSymbol); +``` + +If you subscribe to SEC Whales data for assets in a dynamic universe, +remove the dataset subscription when the asset leaves your universe. To view a common design +pattern, see [Track Security Changes](https://www.quantconnect.com/docs/v2/writing-algorithms/algorithm-framework/alpha/key-concepts#05-Track-Security-Changes). diff --git a/output/alternative/sec/13f/amzn.zip b/output/alternative/sec/13f/amzn.zip new file mode 100644 index 0000000..a0525fa Binary files /dev/null and b/output/alternative/sec/13f/amzn.zip differ diff --git a/output/alternative/sec/13f/bn.zip b/output/alternative/sec/13f/bn.zip new file mode 100644 index 0000000..6df9d1b Binary files /dev/null and b/output/alternative/sec/13f/bn.zip differ diff --git a/output/alternative/sec/13f/htz.zip b/output/alternative/sec/13f/htz.zip new file mode 100644 index 0000000..3518a0e Binary files /dev/null and b/output/alternative/sec/13f/htz.zip differ diff --git a/output/alternative/sec/13f/managers.csv b/output/alternative/sec/13f/managers.csv new file mode 100644 index 0000000..9e674f2 --- /dev/null +++ b/output/alternative/sec/13f/managers.csv @@ -0,0 +1,7057 @@ +2230,ADAMS DIVERSIFIED EQUITY FUND INC. +3520,FRED ALGER MANAGEMENT LLC +7195,ARGUS INVESTORS' COUNSEL INC. +7789,Associated Banc-Corp +9015,BARINGS LLC +9631,BANK OF NOVA SCOTIA +9634,BOKF NA +10742,BECK MACK & OLIVER LLC +11544,BERKLEY W R CORP +12600,PRINCIPAL SECURITIES INC. +14213,BRIGHTON SECURITIES CORP. +14661,BROWN BROTHERS HARRIMAN & CO +14745,BROWN LISLE/CUMMINGS INC. +18748,Central Securities Corp +19475,CHASE INVESTMENT COUNSEL CORP +19481,Virtus Investment Advisers LLC +19617,JPMORGAN CHASE & CO +20286,CINCINNATI FINANCIAL CORP +21175,CNA FINANCIAL CORP +22657,LOWE BROCKENBROUGH & CO INC +24386,COOKE & BIELER LP +33250,EQUITABLE TRUST CO +35442,Fiduciary Trust Co +35527,FIFTH THIRD BANCORP +36029,First Financial Bankshares Inc +36066,FIRST AMERICAN TRUST FSB +36104,US BANCORP \DE\ +36270,M&T Bank Corp +36644,FIRST NATIONAL BANK OF OMAHA +36966,FIRST HORIZON CORP +38777,FRANKLIN RESOURCES INC +39263,Cullen/Frost Bankers Inc. +40417,GENERAL AMERICAN INVESTORS CO INC +40729,Ally Financial Inc. +44365,CENTRAL TRUST Co +45319,MEYER HANDELMAN CO +46392,HAZLETT BURT & WATSON INC. +49205,HUNTINGTON NATIONAL BANK +49969,CIGNA INVESTMENTS INC /NEW +51762,RNC CAPITAL MANAGEMENT LLC +51812,STONEBRIDGE CAPITAL MANAGEMENT INC +52024,INVESTMENT MANAGEMENT ASSOCIATES INC /ADV +52234,WINMILL & CO. INC +53417,JENNISON ASSOCIATES LLC +60086,Loews Corp +61227,MacKay Shields LLC +62039,MANNING & NAPIER ADVISORS LLC +62061,V. M. MANNING & CO. INC. +70858,BANK OF AMERICA CORP /DE/ +71210,NEVILLE RODIE & SHAW INC +72971,WELLS FARGO & COMPANY/MN +73124,NORTHERN TRUST CORP +80255,PRICE T ROWE ASSOCIATES INC /MD/ +84616,ROCKLAND TRUST CO +85338,ROTHSCHILD INVESTMENT LLC +90108,RICHARDS MERRILL & PETERSON INC. +92230,TRUIST FINANCIAL CORP +93751,STATE STREET CORP +96223,Jefferies Financial Group Inc. +98758,Torray Investment Partners LLC +102212,UNIVEST FINANCIAL Corp +105495,Welch & Forbes LLC +107136,&PARTNERS +108572,WRIGHT INVESTORS SERVICE INC +108634,WULFF HANSEN & CO. +109380,ZIONS BANCORPORATION NATIONAL ASSOCIATION /UT/ +200217,Dodge & Cox +200648,ROMANO BROTHERS AND COMPANY +200724,SMITH MOORE & CO. +201772,ESSEX INVESTMENT MANAGEMENT CO LLC +225816,ROWLANDMILLER & PARTNERS.ADV +275484,INDEPENDENT FINANCIAL GROUP LLC +276101,BRISTOL JOHN W & CO INC /NY/ +310051,KING LUTHER CAPITAL MANAGEMENT CORP +312069,BARCLAYS PLC +312272,COMMONWEALTH EQUITY SERVICES LLC +312348,LOOMIS SAYLES & CO L P +313028,BARROW HANLEY MEWHINNEY & STRAUSS LLC +314169,Terril Brothers Inc. +314949,GLENMEDE TRUST CO NA +314969,NEW YORK STATE TEACHERS RETIREMENT SYSTEM +314984,Thrivent Financial for Lutherans +315032,STATE FARM MUTUAL AUTOMOBILE INSURANCE CO +315054,REGENTS OF THE UNIVERSITY OF CALIFORNIA +315066,FMR LLC +315080,BANK OF HAWAII +315297,PUBLIC EMPLOYEES RETIREMENT ASSOCIATION OF COLORADO +318989,FIL Ltd +319933,THURSTON SPRINGER MILLER HERD & TITAK INC. +320376,MCRAE CAPITAL MANAGEMENT INC +350894,SEI INVESTMENTS CO +354201,MH & ASSOCIATES SECURITIES MANAGEMENT CORP /ADV +354204,DIMENSIONAL FUND ADVISORS LP +354923,SEARCY FINANCIAL SERVICES INC /ADV +355429,PROTECTIVE LIFE CORP +356264,1ST SOURCE BANK +357301,TRUSTCO BANK CORP N Y +700529,ATALANTA SOSNOFF CAPITAL LLC +701059,MML INVESTORS SERVICES LLC +701516,MEANS INVESTMENT CO. INC. +702007,CALDWELL SUTTER CAPITAL INC. +706129,HORIZON BANCORP INC /IN/ +707179,OLD NATIONAL BANCORP /IN/ +709089,TOWNSEND ASSET MANAGEMENT CORP /NC/ /ADV +709428,COZAD ASSET MANAGEMENT INC +709447,CITY NATIONAL BANK OF FLORIDA /MSD +710127,SEARLE & CO. +711089,BENEDICT FINANCIAL ADVISORS INC +711987,SOUTHERN CAPITAL SERVICES INC /ADV +712011,PLANNING ALTERNATIVES LTD /ADV +712050,ALTFEST L J & CO INC +712534,FIRST MERCHANTS CORP +712537,First Commonwealth Financial Corp /PA/ +713676,PNC Financial Services Group Inc. +714142,TEACHERS RETIREMENT SYSTEM OF THE STATE OF KENTUCKY +714364,OGOREK ANTHONY JOSEPH /NY/ /ADV +714395,GERMAN AMERICAN BANCORP INC. +714562,FIRST FINANCIAL CORP /IN/ +715113,MJP ASSOCIATES INC /ADV +716851,MONEY CONCEPTS CAPITAL CORP +717538,Arrow Financial Corp +719245,WESTPAC BANKING CORP +720005,RAYMOND JAMES FINANCIAL INC +720672,STIFEL FINANCIAL CORP +723204,Laird Norton Wetherby Trust Company LLC +726854,CITY HOLDING CO +727117,BUILDER INVESTMENT GROUP INC /ADV +728083,FIRST MANHATTAN CO. LLC. +728100,LORD ABBETT & CO. LLC +729563,GILL CAPITAL PARTNERS LLC +732847,FIRST WILSHIRE SECURITIES MANAGEMENT INC +733020,ARS Investment Partners LLC +733444,ROMAN BUTLER FULLERTON & CO +740272,HUDSON EDGE INVESTMENT PARTNERS INC. +741073,STOCK YARDS BANK & TRUST CO +743127,PINNACLE ASSOCIATES LTD +743482,ECLECTIC ASSOCIATES INC /ADV +748054,AMERICAN CENTURY COMPANIES INC +749044,RESOURCES MANAGEMENT CORP /CT/ /ADV +749763,COMPASS CAPITAL CORP /MA/ /ADV +750577,HANCOCK WHITNEY CORP +750641,BAILARD INC. +752365,WALTER & KEENAN WEALTH MANAGEMENT LLC /IN/ /ADV +753967,FIG FINANCIAL ADVISORY SERVICES INC /ADV +754811,U S GLOBAL INVESTORS INC +757657,STEPHENS INC /AR/ +759458,CANANDAIGUA NATIONAL CORP +759944,CITIZENS FINANCIAL GROUP INC/RI +760639,DEMMING FINANCIAL SERVICES CORP /ADV +762152,STATE OF MICHIGAN RETIREMENT SYSTEM +763212,PRIMECAP MANAGEMENT CO/CA/ +764038,SouthState Bank Corp +764068,Legal & General Group Plc +764106,FIRST HAWAIIAN BANK +764112,KIRR MARBACH & CO LLC /IN/ +764529,PEREGRINE CAPITAL MANAGEMENT LLC +764611,ARKANSAS FINANCIAL GROUP INC. +764739,SYM FINANCIAL Corp +765207,First Bancorp Inc /ME/ +769317,SIT INVESTMENT ASSOCIATES INC +769954,ACCOUNT MANAGEMENT LLC +769963,HOWE & RUSLING INC +771118,WOODARD & CO ASSET MANAGEMENT GROUP INC /ADV +771572,ASHTON THOMAS SECURITIES LLC +773411,VALLEY NATIONAL ADVISERS INC +778963,AUGUSTINE ASSET MANAGEMENT INC +789307,INVESTMENT MANAGEMENT CORP /VA/ /ADV +790354,CHEMUNG CANAL TRUST CO +791191,ANDERSON HOAGLAND & CO +791490,CARRET ASSET MANAGEMENT LLC +791540,LANARK FINANCIAL INC. +796848,TEACHER RETIREMENT SYSTEM OF TEXAS +797203,CULLEN INVESTMENT GROUP LTD. +799003,MOUNT VERNON ASSOCIATES INC /MD/ +799004,BECKER CAPITAL MANAGEMENT INC +800177,DANSKE BANK A/S +801051,CONNING INC. +803016,CALIFORNIA FIRST LEASING CORP +805676,PARK NATIONAL CORP /OH/ +805870,VAUGHAN & Co SECURITIES INC. +806097,MERIDIAN MANAGEMENT CO +807249,GAMCO INVESTORS INC. ET AL +809339,L. Roy Papp & Associates LLP +809443,MEEDER ASSET MANAGEMENT INC +810121,HARBOUR INVESTMENTS INC. +810265,NEW YORK STATE COMMON RETIREMENT FUND +810384,JAMES INVESTMENT RESEARCH INC. +810386,Howard Hughes Medical Institute +810672,AFFINITY WEALTH MANAGEMENT LLC +810958,CITIZENS & NORTHERN CORP +811360,Capital Investment Services of America Inc. +811407,ASSET PLANNING SERVICES INC /LA/ /ADV +811454,WESTPORT ASSET MANAGEMENT INC +812291,MIZUHO SECURITIES USA LLC +813917,HARRIS ASSOCIATES L P +813933,ANCHOR CAPITAL ADVISORS LLC +814133,WASATCH ADVISORS LP +815917,JONES FINANCIAL COMPANIES LLLP +816788,TANAKA CAPITAL MANAGEMENT INC +819535,Cornerstone Capital Inc. +819864,CREATIVE FINANCIAL DESIGNS INC /ADV +820027,AMERIPRISE FINANCIAL INC +820124,Sound Shore Management Inc /CT/ +820434,PVG ASSET MANAGEMENT CORP +820478,STRS OHIO +820743,CRAMER ROSENTHAL MCGLYNN LLC +821103,PLANNING DIRECTIONS INC +821197,JOHNSON INVESTMENT COUNSEL INC +822581,OPPENHEIMER & CO INC +822648,CALTON & ASSOCIATES INC. +823621,CAMBIAR INVESTORS LLC +825217,Whitener Capital Management Inc. +825293,CAPITAL MANAGEMENT ASSOCIATES /NY/ +826000,POTOMAC FUND MANAGEMENT INC /ADV +826154,ORRSTOWN FINANCIAL SERVICES INC +826794,CAMPBELL CAPITAL MANAGEMENT INC +829407,MONTAG A & ASSOCIATES INC +831001,CITIGROUP INC +831571,ST GERMAIN D J CO INC +836372,OAK ASSOCIATES LTD /OH/ +837592,CRAWFORD INVESTMENT COUNSEL INC +838618,PDS Planning Inc +842180,BANCO BILBAO VIZCAYA ARGENTARIA S.A. +842766,ADVISORS MANAGEMENT GROUP INC /ADV +842775,Clearstead Advisors LLC +842782,ZWJ INVESTMENT COUNSEL INC +842941,Haverford Trust Co +844150,NatWest Group plc +846797,DEAN INVESTMENT ASSOCIATES LLC +850401,TCW GROUP INC +850529,Fisher Asset Management LLC +850601,FELL CAPITAL MANAGEMENT /ADV +852933,COMMERZBANK AKTIENGESELLSCHAFT /FI +853758,MERCER GLOBAL ADVISORS INC /ADV +854157,STATE OF WISCONSIN INVESTMENT BOARD +857508,AMICA MUTUAL INSURANCE CO +859139,Delphi Financial Group Inc. +859804,WEDGEWOOD PARTNERS INC +859872,KLINGENSTEIN FIELDS & CO LP +860176,MARK ASSET MANAGEMENT LP +860486,HIGHLAND CAPITAL MANAGEMENT LLC +860561,EDGEWOOD MANAGEMENT LLC +860643,GARDNER RUSSO & QUINN LLC +860644,Aristotle Capital Management LLC +860828,CAPITAL ADVISORS INC/OK +860857,DELTA ASSET MANAGEMENT LLC/TN +861176,TRUSTMARK BANK TRUST DEPARTMENT +861177,UBS AM a distinct business unit of UBS ASSET MANAGEMENT AMERICAS LLC +861787,Washington Trust Bank +862469,NEW MEXICO EDUCATIONAL RETIREMENT BOARD +863748,ROYAL LONDON ASSET MANAGEMENT LTD +866590,MANCHESTER FINANCIAL INC +866780,TOTH FINANCIAL ADVISORY CORP +867626,MUFG SECURITIES AMERICAS INC. +867926,INVESTORS ASSET MANAGEMENT OF GEORGIA INC /GA/ /ADV +869178,VAN ECK ASSOCIATES CORP +869179,MONETTA FINANCIAL SERVICES INC +869304,NORTHSTAR ASSET MANAGEMENT INC +869353,FERGUSON WELLMAN CAPITAL MANAGEMENT INC +869367,ROFFMAN MILLER ASSOCIATES INC /PA/ +869589,NISSAY ASSET MANAGEMENT CORP /JAPAN +872080,BRAUN STACEY ASSOCIATES INC +872098,CHAPIN DAVIS INC. +872162,BLACKHILL CAPITAL INC +872163,NAVELLIER & ASSOCIATES INC +872259,Bahl & Gaynor LLC +872359,LINCOLN CAPITAL CORP +872786,BNP PARIBAS +873630,HSBC HOLDINGS PLC +873759,TANDEM CAPITAL MANAGEMENT CORP /ADV +874791,CAMPBELL NEWMAN ASSET MANAGEMENT INC +874816,Alexander Randolph Advisory Inc. +877134,WESBANCO BANK INC +877338,WS MANAGEMENT LLLP +878228,WENDELL DAVID ASSOCIATES INC +878770,WORLD EQUITY GROUP INC. +882928,TIAA-CREF INDIVIDUAL & INSTITUTIONAL SERVICES LLC +883511,DUDLEY & SHANLEY INC. +883634,CONTINENTAL INVESTORS SERVICES INC. +883677,PANAGORA ASSET MANAGEMENT INC +883734,THOR INVESTMENT MANAGEMENT INC /OH/ /ADV +883782,Fulton Bank N.A. +883803,GRIFFIN ASSET MANAGEMENT INC. +883948,Atlantic Union Bankshares Corp +883961,TOCQUEVILLE ASSET MANAGEMENT L.P. +883965,WEITZ INVESTMENT MANAGEMENT INC. +884300,PERKINS CAPITAL MANAGEMENT INC +884414,JACOBS LEVY EQUITY MANAGEMENT INC +884423,SALEM INVESTMENT COUNSELORS INC +884541,TRILLIUM ASSET MANAGEMENT LLC +884546,CHARLES SCHWAB INVESTMENT MANAGEMENT INC +884548,CONNORS INVESTOR SERVICES INC +884566,Fenimore Asset Management Inc +885062,BROWN CAPITAL MANAGEMENT LLC +885118,AMERICAN ASSETS INC +885415,FOLGER NOLAN FLEMING DOUGLAS CAPITAL MANAGEMENT INC +886982,GOLDMAN SACHS GROUP INC +887402,GODSEY & GIBB INC +887602,BARRETT & COMPANY INC. +887748,FSB PREMIER WEALTH MANAGEMENT INC. +887777,DAVENPORT & Co LLC +887818,FACTORY MUTUAL INSURANCE CO +890203,MIRAE ASSET SECURITIES (USA) INC. +891287,DAVIS R M INC +891478,Banco Santander S.A. +891943,CENTAURUS FINANCIAL INC. +893738,DELTA CAPITAL MANAGEMENT LLC +894205,PROFESSIONAL ADVISORY SERVICES INC +894300,BENDER ROBERT & ASSOCIATES +894309,SECURITY NATIONAL BANK OF SIOUX CITY IOWA /IA/ +895213,Capital International Inc./CA/ +895421,MORGAN STANLEY +897070,ASHFORD CAPITAL MANAGEMENT INC +897378,CONGRESS ASSET MANAGEMENT CO +897599,LOCKHEED MARTIN INVESTMENT MANAGEMENT CO +898286,Caisse de depot et placement du Quebec +898358,KORNITZER CAPITAL MANAGEMENT INC /KS +898382,COOPERMAN LEON G +898413,NBT BANK N.A. +898419,PRUDENTIAL PLC +898427,AXA S.A. +899051,ALLSTATE CORP +900529,ARDSLEY ADVISORY PARTNERS LP +900973,Winslow Capital Management LLC +900974,Wilmington Savings Fund Society FSB +902219,WELLINGTON MANAGEMENT GROUP LLP +902367,BLAIR WILLIAM & CO/IL +902464,GILDER GAGNON HOWE & CO LLC +902528,BANK HAPOALIM BM +902584,ADVISORY RESEARCH INC +903064,GRACE & WHITE INC /NY +903783,OLD SECOND NATIONAL BANK OF AURORA +903949,NICHOLAS COMPANY INC. +905567,YACKTMAN ASSET MANAGEMENT LP +905790,Onex Canada Asset Management Inc. +906304,ROYCE & ASSOCIATES LP +906396,MOSELEY INVESTMENT MANAGEMENT INC +908195,SHUFRO ROSE & CO LLC +909151,SCHWARTZ INVESTMENT COUNSEL INC +909661,FARALLON CAPITAL MANAGEMENT L.L.C. +911270,GLYNN CAPITAL MANAGEMENT LLC +911274,BEESE FULMER INVESTMENT MANAGEMENT INC. +911927,AMERICAN FINANCIAL & TAX STRATEGIES INC +912938,MASSACHUSETTS FINANCIAL SERVICES CO /MA/ +913760,StoneX Group Inc. +913990,LANDAAS & CO /WI /ADV +914208,Invesco Ltd. +914933,CONNABLE OFFICE INC +914973,HANOVER ADVISORS INC +916542,ACADIAN ASSET MANAGEMENT LLC +918504,BIRMINGHAM CAPITAL MANAGEMENT CO INC/AL +918893,ROSENBLUM SILVERMAN SUTTON S F INC /CA +919079,California Public Employees Retirement System +919185,HIGHBRIDGE CAPITAL MANAGEMENT LLC +919192,AMALGAMATED BANK +919219,PAYDEN & RYGEL +919447,CASCADE INVESTMENT GROUP INC. +919458,ALERUS FINANCIAL NA +919497,MIDDLETON & CO INC/MA +919530,JOHN G ULLMAN & ASSOCIATES INC +919538,CORBYN INVESTMENT MANAGEMENT INC/MD +919859,MACKENZIE FINANCIAL CORP +919864,Finward Bancorp +920441,KELLY LAWRENCE W & ASSOCIATES INC/CA +920655,VALLEY FORGE INVESTMENT CONSULTANTS INC ADV +921739,NOMURA ASSET MANAGEMENT INTERNATIONAL INC. +922127,MUTUAL OF AMERICA CAPITAL MANAGEMENT LLC +922372,DIVERSIFIED MANAGEMENT INC +922439,HARTFORD INVESTMENT MANAGEMENT CO +922940,M.D. Sass LLC +923093,TUDOR INVESTMENT CORP ET AL +923116,GROESBECK INVESTMENT MANAGEMENT CORP /NJ/ +923469,CADINHA & CO LLC +924166,VALLEY WEALTH MANAGERS INC. +924171,OAKMONT Corp +924181,LEAVELL INVESTMENT MANAGEMENT INC. +925953,INVESTMENT ADVISORY SERVICES INC /TX /ADV +926171,NATIONAL BANK OF CANADA /FI/ +926833,WISCONSIN CAPITAL MANAGEMENT LLC +926834,AVITY INVESTMENT MANAGEMENT INC. +927971,BANK OF MONTREAL /CAN/ +928047,MANUFACTURERS LIFE INSURANCE COMPANY THE +928052,HM PAYSON & CO +928196,Harding Loevner LP +928400,ELKHORN PARTNERS LIMITED PARTNERSHIP +928566,WASHINGTON CAPITAL MANAGEMENT INC +928568,GUYASUTA INVESTMENT ADVISORS INC +929607,CONTINENTAL GRAIN CO +931097,CCM INVESTMENT ADVISERS LLC +932000,TOEWS CORP /ADV +932024,SKBA CAPITAL MANAGEMENT LLC +932540,GROUP ONE TRADING LLC +932724,GABELLI & Co INVESTMENT ADVISERS INC. +932929,WESTBOURNE INVESTMENTS INC. +932974,LORING WOLCOTT & COOLIDGE FIDUCIARY ADVISORS LLP/MA +933429,UMB Bank n.a. +933478,VANGUARD FIDUCIARY TRUST CO +934040,PERRYMAN FINANCIAL ADVISORY INC /AD +934639,MAVERICK CAPITAL LTD +934745,WEATHERLY ASSET MANAGEMENT L. P. +934866,PEOPLES BANK /OH +934999,DF DENT & CO INC +935359,Matthew Goff Investment Advisor LLC +935570,ELLERSON GROUP INC /ADV +936753,ARIEL INVESTMENTS LLC +936936,DOHENY ASSET MANAGEMENT /CA +936938,CAPE ANN SAVINGS BANK +936941,Moody Aldrich Partners LLC +936944,MARTINGALE ASSET MANAGEMENT L P +937394,HEARTLAND ADVISORS INC +937522,SCHWERIN BOYLE CAPITAL MANAGEMENT INC +937567,ONTARIO TEACHERS PENSION PLAN BOARD +937589,HERITAGE INVESTORS MANAGEMENT CORP +937615,NISA INVESTMENT ADVISORS LLC +937729,Fayez Sarofim & Co +937760,SUMITOMO LIFE INSURANCE CO +937886,OBERWEIS ASSET MANAGEMENT INC/ +938076,STATE BOARD OF ADMINISTRATION OF FLORIDA RETIREMENT SYSTEM +938077,DOLIVER ADVISORS LP +938206,Driehaus Capital Management LLC +938487,SSI INVESTMENT MANAGEMENT LLC +938592,MOODY LYNN LIEBERSON & WALKER LLC +938759,MONARCH CAPITAL MANAGEMENT INC +939219,LEE DANNER & BASS INC +939334,RIT CAPITAL PARTNERS PLC +940445,BURNEY CO/ +941519,STEPH & CO +941560,GARDNER LEWIS ASSET MANAGEMENT L P +943442,Orange Investment Advisors Inc. +944234,RENAISSANCE GROUP LLC +944317,SMITH SHELLNUT WILSON LLC /ADV +944361,CLIFTONLARSONALLEN WEALTH ADVISORS LLC +944388,1832 Asset Management L.P. +944733,HUTNER CAPITAL MANAGEMENT INC +944804,Homestead Advisers Corp +945625,HARTLINE INVESTMENT CORP/ +945631,EAGLE CAPITAL MANAGEMENT LLC +946310,WESTCO ADVISORY SERVICES INC /ADV +946431,Swedbank AB +946626,GREENBERG FINANCIAL GROUP +946629,LEIGH BALDWIN & CO. LLC +947263,TORONTO DOMINION BANK +947517,Pacific Sun Financial Corp +947529,VANGUARD ADVISERS INC +947996,Olstein Capital Management L.P. +948046,DEUTSCHE BANK AG\ +948669,PARNASSUS INVESTMENTS LLC +949012,BERKSHIRE ASSET MANAGEMENT LLC/PA +949853,P.A.W. CAPITAL CORP +1000275,ROYAL BANK OF CANADA +1000742,SANDLER CAPITAL MANAGEMENT +1001011,CLARK FINANCIAL SERVICES GROUP INC /BD +1001085,Brookfield Corp /ON/ +1002152,COMPASS CAPITAL MANAGEMENT INC +1002672,Bell Bank +1002784,SHELTON CAPITAL MANAGEMENT +1003518,AGF MANAGEMENT LTD +1004140,PENNSYLVANIA CAPITAL MANAGEMENT INC /ADV +1004244,NEW ENGLAND ASSET MANAGEMENT INC +1005186,RHODES INVESTMENT ADVISORS INC /ADV +1005354,VIRGINIA RETIREMENT SYSTEMS ET Al +1005441,Avantax Planning Partners Inc. +1005607,OSBORNE PARTNERS CAPITAL MANAGEMENT LLC +1005817,TOMPKINS FINANCIAL CORP +1006364,Hardman Johnston Global Advisors LLC +1006378,SEGALL BRYANT & HAMILL LLC +1006407,FISHMAN JAY A LTD/MI +1006435,PIONEER TRUST BANK N A/OR +1006938,NORTHLAND SECURITIES INC. +1007280,PUBLIC EMPLOYEES RETIREMENT SYSTEM OF OHIO +1007295,BRIDGES INVESTMENT MANAGEMENT INC +1007524,OSTERWEIS CAPITAL MANAGEMENT INC +1008322,THOMPSON SIEGEL & WALMSLEY LLC +1008868,MARTIN & CO INC /TN/ +1008877,WOODSTOCK CORP +1008894,DEPRINCE RACE & ZOLLO INC +1008895,SCHMIDT P J INVESTMENT MANAGEMENT INC +1008937,TRUST CO OF TOLEDO NA /OH/ +1009003,EXCALIBUR MANAGEMENT CORP +1009005,SHAKER INVESTMENTS LLC/OH +1009006,PALISADE CAPITAL MANAGEMENT LP +1009016,FORT WASHINGTON INVESTMENT ADVISORS INC /OH/ +1009076,COMMERCE BANK +1009198,STERLING INVESTMENT ADVISORS LLC /ADV +1009207,D. E. Shaw & Co. Inc. +1009209,Sand Hill Global Advisors LLC +1009232,GENEVA CAPITAL MANAGEMENT LLC +1009254,EVERETT HARRIS & CO /CA/ +1009262,TRAN CAPITAL MANAGEMENT L.P. +1010873,FRANKLIN STREET ADVISORS INC /NC +1010911,S&T Bank/PA +1011443,HBK INVESTMENTS L P +1013234,ORLEANS CAPITAL MANAGEMENT CORP/LA +1013287,LYNX CAPITAL GROUP LTD /ADV +1013701,LAWSON KROEKER INVESTMENT MANAGEMENT INC/NE +1014315,CORTLAND ASSOCIATES INC/MO +1015083,EMERALD ADVISERS LLC +1015086,BRADLEY FOSTER & SARGENT INC/CT +1015247,COURIER CAPITAL LLC +1015308,WEDGE CAPITAL MANAGEMENT L L P/NC +1015877,MCINTYRE FREEDMAN & FLYNN INVESTMENT ADVISERS INC +1016021,EDMP INC. +1016150,MORGENS WATERFALL VINTIADIS & CO INC +1016287,MATRIX ASSET ADVISORS INC/NY +1016683,CABOT WEALTH MANAGEMENT INC +1016972,ARCADIA INVESTMENT MANAGEMENT CORP/MI +1017115,ROBERTS GLORE & CO INC /IL/ +1017284,THOMPSON DAVIS & CO. INC. +1017918,BAMCO INC /NY/ +1018331,NATIXIS ADVISORS LLC +1018561,ACORN FINANCIAL ADVISORY SERVICES INC /ADV +1018674,PARSONS CAPITAL MANAGEMENT INC/RI +1019231,UNIVERSITY OF TEXAS/TEXAS AM INVESTMENT MANAGEMENT CO +1019754,Smithfield Trust Co +1020066,SANDS CAPITAL MANAGEMENT LLC +1020317,Pekin Hardy Strauss Inc. +1020580,KEATING INVESTMENT COUNSELORS INC +1020585,DUNCKER STREETT & CO INC +1021117,D L CARLSON INVESTMENT GROUP INC +1021223,KAYNE ANDERSON RUDNICK INVESTMENT MANAGEMENT LLC +1021258,BALDWIN WEALTH PARTNERS LLC/MA +1021296,BROOKTREE CAPITAL MANAGEMENT +1021642,VAUGHAN NELSON INVESTMENT MANAGEMENT L.P. +1021926,CIBC Asset Management Inc +1021944,Temasek Holdings (Private) Ltd +1022837,SUMITOMO MITSUI FINANCIAL GROUP INC. +1024896,CANTOR FITZGERALD L. P. +1025038,ARVEST INVESTMENTS INC. +1025835,Enterprise Financial Services Corp +1026720,WELLCOME TRUST LTD (THE) as trustee of the WELLCOME TRUST +1027570,ARBOR CAPITAL MANAGEMENT INC /ADV +1027796,PZENA INVESTMENT MANAGEMENT LLC +1028874,Roof Eidam Maycock Peralta LLC +1029160,SOROS FUND MANAGEMENT LLC +1030618,CYPRESS ASSET MANAGEMENT INC/TX +1031972,ACADEMY CAPITAL MANAGEMENT +1032814,CHECK CAPITAL MANAGEMENT INC/CA +1033225,MCDONALD CAPITAL INVESTORS INC/CA +1033324,FUKOKU MUTUAL LIFE INSURANCE Co +1033427,IRIDIAN ASSET MANAGEMENT LLC/CT +1033475,AVENIR CORP +1033974,SCHAPER BENZ & WISE INVESTMENT COUNSEL INC/WI +1034196,Advent International L.P. +1034524,POLEN CAPITAL MANAGEMENT LLC +1034546,CITY OF LONDON INVESTMENT MANAGEMENT CO LTD +1034549,PARADIGM ASSET MANAGEMENT CO LLC +1034642,CLIFFORD SWAN INVESTMENT COUNSEL LLC +1034771,INTRUST BANK NA +1034886,PITTENGER & ANDERSON INC +1035344,UHLMANN PRICE SECURITIES LLC +1035350,SECURIAN ASSET MANAGEMENT INC +1035463,BAR HARBOR WEALTH MANAGEMENT +1035912,EADS & HEALD WEALTH MANAGEMENT +1036215,PROFESSIONAL FINANCIAL SOLUTIONS LLC /ADV +1036248,QUEST INVESTMENT MANAGEMENT LLC +1036288,HILLMAN CO +1036325,Davis Selected Advisers +1037389,RENAISSANCE TECHNOLOGIES LLC +1037766,CALEDONIA INVESTMENTS PLC +1038661,ABNER HERRMAN & BROCK LLC +1039128,HARBOR CAPITAL ADVISORS INC. +1039565,KAHN BROTHERS GROUP INC +1039765,ING GROEP NV +1039807,BOSTON FAMILY OFFICE LLC +1040188,VICTORY CAPITAL MANAGEMENT INC +1040190,Nippon Life Global Investors Americas Inc. +1040197,MAI Capital Management +1040210,BARR E S & CO +1040273,Third Point LLC +1040410,MSA Advisors LLC +1041283,SPIRIT OF AMERICA MANAGEMENT CORP/NY +1041773,DIMENSION CAPITAL MANAGEMENT LLC +1041885,INGALLS & SNYDER LLC +1042046,AMERICAN FINANCIAL GROUP INC +1042063,WELCH CAPITAL PARTNERS LLC/NY +1044797,NEW SOUTH CAPITAL MANAGEMENT INC +1044905,PATTEN & PATTEN INC/TN +1044924,SAYBROOK CAPITAL /NC +1044929,ATWOOD & PALMER INC +1046192,CANADA LIFE ASSURANCE Co +1047142,COMMUNITY TRUST & INVESTMENT CO +1047339,BURNS J W & CO INC/NY +1048462,WEXFORD CAPITAL LP +1048703,Karpus Management Inc. +1048921,MITCHELL SINKLER & STARR/PA +1050068,INVESTMENT PARTNERS LTD. +1050463,MORGAN DEMPSEY CAPITAL MANAGEMENT LLC +1050464,PECONIC PARTNERS LLC +1050470,LSV ASSET MANAGEMENT +1050743,PEAPACK GLADSTONE FINANCIAL CORP +1051359,PAR CAPITAL MANAGEMENT INC +1053013,AMARILLO NATIONAL BANK +1053054,LVM CAPITAL MANAGEMENT LTD/MI +1053292,DROMS STRAUSS ADVISORS INC /MO/ /ADV +1053321,OMERS ADMINISTRATION Corp +1053914,PUZO MICHAEL J +1053994,SANDERS MORRIS HARRIS LLC +1054074,BESSEMER GROUP INC +1054257,ADIRONDACK TRUST CO +1054425,DILLON & ASSOCIATES INC +1054554,OAK RIDGE INVESTMENTS LLC +1054587,Sculptor Capital LP +1054646,CAPE COD FIVE CENTS SAVINGS BANK +1054677,ALBION FINANCIAL GROUP /UT +1055290,APPLETON PARTNERS INC/MA +1055544,JACOBS & CO/CA +1055963,Tufton Capital Management +1055964,NOMURA ASSET MANAGEMENT CO LTD +1055966,MARSICO CAPITAL MANAGEMENT LLC +1055969,GROUPAMA ASSET MANAGMENT +1055980,Ameritas Investment Partners Inc. +1056053,TD Asset Management Inc +1056288,FEDERATED HERMES INC. +1056466,CLARK ESTATES INC/NY +1056488,GARRISON BRADFORD & ASSOCIATES INC +1056516,Palouse Capital Management Inc. +1056549,FAIRVIEW CAPITAL INVESTMENT MANAGEMENT LLC +1056559,NORTH STAR ASSET MANAGEMENT INC +1056823,HORIZON KINETICS ASSET MANAGEMENT LLC +1056825,PRAXIS INVESTMENT MANAGEMENT INC +1056827,ARMSTRONG HENRY H ASSOCIATES INC +1056859,CHILTON CAPITAL MANAGEMENT LLC +1056943,PEOPLES FINANCIAL SERVICES CORP. +1056947,CAISSE DES DEPOTS ET CONSIGNATIONS +1056973,Carlson Capital L.P. +1058022,SHIKIAR ASSET MANAGEMENT INC +1058231,MORGUARD LINCLUDEN GLOBAL INVESTMENTS Ltd +1058470,ICON ADVISERS INC/CO +1059187,TWIN CAPITAL MANAGEMENT INC +1061165,LONE PINE CAPITAL LLC +1061186,WCM INVESTMENT MANAGEMENT LLC +1061555,CAPITAL FINANCIAL GROUP INC\CO\ /ADV +1061768,BAUPOST GROUP LLC/MA +1063497,Pembroke Management LTD +1063571,VAUGHAN DAVID INVESTMENTS LLC/IL +1065349,CAPITAL INTERNATIONAL SARL +1065350,CAPITAL INTERNATIONAL LTD /CA/ +1066816,EVERMAY WEALTH MANAGEMENT LLC +1067324,MOUNTAIN PACIFIC INVESTMENT ADVISERS LLC +1067532,FORBES J M & CO LLP +1068804,TOTAL INVESTMENT MANAGEMENT INC +1068837,VOYA INVESTMENT MANAGEMENT LLC +1068851,Prosperity Bancshares Inc +1068855,Asset Management One Co. Ltd. +1068979,LIGHTHOUSE FINANCIAL SERVICES INC /ADV +1070134,MAIRS & POWER INC +1071061,HANTZ FINANCIAL SERVICES INC. +1071483,TODD ASSET MANAGEMENT LLC +1071640,PARK AVENUE SECURITIES LLC +1072843,First Fiduciary Investment Counsel Inc. +1074266,CANANDAIGUA NATIONAL BANK & TRUST CO +1074902,LCNB CORP +1075444,Advanced Asset Management Advisors Inc +1076964,Provident Investment Management Inc. +1077583,BOWEN HANES & CO INC +1078013,GREAT LAKES ADVISORS LLC +1078246,HUTCHINSON CAPITAL MANAGEMENT/CA +1078635,WINDWARD CAPITAL MANAGEMENT CO /CA +1078841,BRUNI J V & CO /CO +1079112,HOWLAND CAPITAL MANAGEMENT LLC +1079397,CHESAPEAKE ASSET MANAGEMENT LLC +1079398,BAKER BOYER NATIONAL BANK +1079736,WASHINGTON TRUST Co +1079738,BIRINYI ASSOCIATES INC +1079935,SIGNATURE ESTATE & INVESTMENT ADVISORS LLC +1080107,D.A. DAVIDSON & CO. +1080132,Peloton Wealth Strategists +1080166,AR ASSET MANAGEMENT INC +1080171,ARISTEIA CAPITAL L.L.C. +1080173,VAN STRUM & TOWNE INC. +1080197,SCHULHOFF & CO INC +1080201,VALICENTI ADVISORY SERVICES INC +1080351,MITCHELL CAPITAL MANAGEMENT CO +1080369,GW HENSSLER & ASSOCIATES LTD +1080374,JAG CAPITAL MANAGEMENT LLC +1080382,EUBEL BRADY & SUTTMAN ASSET MANAGEMENT INC +1080493,MARCO INVESTMENT MANAGEMENT LLC +1080576,SILVER OAK SECURITIES INCORPORATED +1081019,CALIFORNIA STATE TEACHERS RETIREMENT SYSTEM +1081198,CYPRESS FUNDS LLC +1081407,GABELLI FUNDS LLC +1081668,PORTLAND INVESTMENT COUNSEL INC. +1082215,NORTHEAST INVESTMENT MANAGEMENT +1082327,FULLER & THALER ASSET MANAGEMENT INC. +1082339,COLDSTREAM CAPITAL MANAGEMENT INC +1082461,S&CO INC +1082491,CONDOR CAPITAL MANAGEMENT +1082509,SMITH CHAS P & ASSOCIATES PA CPAS +1082621,HARVARD MANAGEMENT CO INC +1082917,GW&K Investment Management LLC +1083190,COMMONWEALTH OF PENNSYLVANIA PUBLIC SCHOOL EMPLS RETRMT SYS +1083323,TRUST CO OF OKLAHOMA +1084207,HENDLEY & CO INC +1084683,GREENWOOD GEARHART LLC +1085041,FIRST NATIONAL BANK SIOUX FALLS +1085163,SIGMA INVESTMENT COUNSELORS INC +1085227,JLB & ASSOCIATES INC +1085867,BRAVE ASSET MANAGEMENT INC +1085936,SYSTEMATIC FINANCIAL MANAGEMENT LP +1086318,INTECH INVESTMENT MANAGEMENT LLC +1086416,SCOGGIN MANAGEMENT LP +1086483,ZACKS INVESTMENT MANAGEMENT +1086619,SCHRODER INVESTMENT MANAGEMENT GROUP +1086762,COBALT CAPITAL MANAGEMENT INC. +1086763,FIRST FOUNDATION ADVISORS +1088388,EAGLE HARBOR ASSET MANAGEMENT INC /ADV +1088731,BOURGEON CAPITAL MANAGEMENT LLC +1088859,PARTHENON LLC +1088875,BAILLIE GIFFORD & CO +1089707,LEDERER & ASSOCIATES INVESTMENT COUNSEL/CA +1089877,KEYBANK NATIONAL ASSOCIATION/OH +1091370,THOROUGHBRED FINANCIAL SERVICES LLC +1091860,C M BIDWELL & ASSOCIATES LTD +1091923,PENINSULA ASSET MANAGEMENT INC +1091961,GOULD ASSET MANAGEMENT LLC /CA/ +1092203,FIRST CITIZENS BANK & TRUST CO +1092290,NORRIS PERNE & FRENCH LLP/MI +1092838,INTREPID CAPITAL MANAGEMENT INC +1092903,MOODY NATIONAL BANK TRUST DIVISION +1093219,FORMULA GROWTH LTD +1093276,NOTTINGHAM ADVISORS INC. +1093589,ALTA CAPITAL MANAGEMENT LLC/ +1093908,CHESAPEAKE CAPITAL CORP /IL/ +1094429,SIG BROKERAGE LP +1094517,TOYOTA MOTOR CORP/ +1094584,PIN OAK INVESTMENT ADVISORS INC +1095836,CAPITAL CITY TRUST CO/FL +1096343,Markel Group Inc. +1096783,BAXTER BROS INC +1097278,ADVENT CAPITAL MANAGEMENT /DE/ +1097833,OPUS INVESTMENT MANAGEMENT INC +1098151,FIDELITY D & D BANCORP INC +1099281,THIRD AVENUE MANAGEMENT LLC +1099762,MARINO STRAM & ASSOCIATES LLC +1100710,HARBOR ADVISORY CORP /MA/ +1101250,BURKE & HERBERT BANK & TRUST CO +1102062,AmeriServ Wealth Advisors +1102256,Towne Trust Company N.A +1102578,EARNEST PARTNERS LLC +1103245,SMITHBRIDGE ASSET MANAGEMENT INC/DE +1103646,CSS LLC/IL +1103653,MassMutual Private Wealth & Trust FSB +1103738,Gruss & Co. LLC +1103804,VIKING GLOBAL INVESTORS LP +1103882,Paloma Partners Management Co +1103887,NWI MANAGEMENT LP +1104186,MASTERS CAPITAL MANAGEMENT LLC +1104366,MCDANIEL TERRY & CO +1104883,SIRIOS CAPITAL MANAGEMENT L P +1105344,NORTH POINT PORTFOLIO MANAGERS CORP/OH +1105467,SAWGRASS ASSET MANAGEMENT LLC +1105471,BONNESS ENTERPRISES INC +1105837,WEBSTER BANK N. A. +1105863,AUXIER ASSET MANAGEMENT LLC +1105907,BARD ASSOCIATES INC +1105909,SECURITY NATIONAL BANK OF SO DAK +1106129,JENSEN INVESTMENT MANAGEMENT INC +1106191,GMT Capital Corp +1106500,JOHO CAPITAL LLC +1106565,STALEY CAPITAL ADVISERS INC +1106832,CASTLEARK MANAGEMENT LLC +1107261,BRIDGEWAY CAPITAL MANAGEMENT LLC +1107310,EMINENCE CAPITAL LP +1107314,OREGON PUBLIC EMPLOYEES RETIREMENT FUND +1108831,STONERIDGE INVESTMENT PARTNERS LLC +1108893,PENN DAVIS MCFARLAND INC +1108965,LYNCH & ASSOCIATES/IN +1108969,CHATHAM CAPITAL GROUP INC. +1109147,AXIOM INVESTORS LLC /DE +1109228,KCM INVESTMENT ADVISORS LLC +1109448,ALLIANCEBERNSTEIN L.P. +1109767,NATIONS FINANCIAL GROUP INC /IA/ /ADV +1110806,PHILADELPHIA TRUST CO +1112325,RIVERBRIDGE PARTNERS LLC +1112520,AKRE CAPITAL MANAGEMENT LLC +1113000,KENSICO CAPITAL MANAGEMENT CORP +1113629,VILLERE ST DENIS J & CO LLC +1114618,GATEWAY INVESTMENT ADVISERS LLC +1114702,MARIETTA INVESTMENT PARTNERS LLC +1114739,SENTINEL TRUST CO LBA +1114928,CAPITAL COUNSEL LLC/NY +1115373,SEMPER AUGUSTUS INVESTMENTS GROUP LLC +1115418,RHUMBLINE ADVISERS +1116247,FAIRFIELD BUSH & CO. +1119032,HARVEY CAPITAL MANAGEMENT INC +1119254,FULTON BREAKEFIELD BROENNIMAN LLC +1119376,COURAGE CAPITAL MANAGEMENT LLC +1120926,ARGENT CAPITAL MANAGEMENT LLC +1120927,MONETA GROUP INVESTMENT ADVISORS LLC +1121330,LOGAN CAPITAL MANAGEMENT INC +1121477,BOYD WATTERSON ASSET MANAGEMENT LLC/OH +1121908,INDEPENDENT INVESTORS INC +1121914,AZZAD ASSET MANAGEMENT INC /ADV +1122241,AMI INVESTMENT MANAGEMENT INC +1122490,HALL CAPITAL MANAGEMENT CO INC +1123274,Bank Pictet & Cie (Europe) AG +1123320,Family Capital Trust Co +1123803,BRANDYWINE TRUST CO +1123812,PROFFITT & GOODSON INC +1124841,LATHROP INVESTMENT MANAGEMENT CO +1125243,WEDGEWOOD INVESTORS INC /PA/ +1125725,GAGNON SECURITIES LLC +1125727,First Interstate Bank +1125816,FIRST TRUST ADVISORS LP +1126328,PRINCIPAL FINANCIAL GROUP INC +1126395,EASTERN BANK +1126735,MOGY JOEL R INVESTMENT COUNSEL INC +1127508,Allianz SE +1127612,MAINSTAY CAPITAL MANAGEMENT LLC /ADV +1127761,IRONWOOD INVESTMENT MANAGEMENT LLC +1127799,Zurich Insurance Group Ltd/FI +1128066,KELLY FINANCIAL GROUP LLC +1128074,HANSEATIC MANAGEMENT SERVICES INC +1128213,PETTYJOHN WOOD & WHITE INC +1128251,DUPONT CAPITAL MANAGEMENT CORP +1128286,BOURNE LENT ASSET MANAGEMENT INC +1129770,CHARTIST INC /CA/ +1129919,PROFUND ADVISORS LLC +1130344,FIRST COMMUNITY TRUST NA +1130787,EAGLE GLOBAL ADVISORS LLC +1131181,Iron Gate Global Advisors LLC +1132597,Itau Unibanco Holding S.A. +1132651,AMES NATIONAL CORP +1132699,VESTOR CAPITAL LLC +1132708,NORTHSTAR ASSET MANAGEMENT Co +1132897,RAMSEY QUANTITATIVE SYSTEMS +1133014,MONETARY MANAGEMENT GROUP INC +1133119,DAVIDSON TRUST CO +1133219,MUHLENKAMP & CO INC +1133639,New York Life Investment Management LLC +1133653,WOODMONT INVESTMENT COUNSEL LLC +1133742,BERKSHIRE CAPITAL HOLDINGS INC +1133999,BUCKHEAD CAPITAL MANAGEMENT LLC +1134007,NEXT CENTURY GROWTH INVESTORS LLC +1134008,TRUST CO OF VERMONT +1134152,ADELL HARRIMAN & CARPENTER INC +1134283,SEIZERT CAPITAL PARTNERS LLC +1134288,DE BURLO GROUP INC +1134621,RBF Capital LLC +1134687,PRENTISS SMITH & CO INC +1134813,STEVENS CAPITAL MANAGEMENT LP +1135121,BRANDYWINE MANAGERS LLC +1135439,TCV Trust & Wealth Management Inc. +1135730,COATUE MANAGEMENT LLC +1135778,Miller Value Partners LLC +1137429,WHITE PINE CAPITAL LLC +1137774,PRUDENTIAL FINANCIAL INC +1137881,WHITTIER TRUST CO +1138486,1834 INVESTMENT ADVISORS CO +1138995,GLENVIEW CAPITAL MANAGEMENT LLC +1140022,AVIVA PLC +1140334,FLPUTNAM INVESTMENT MANAGEMENT CO +1140436,PROVIDENT TRUST CO +1140771,DAVIDSON INVESTMENT ADVISORS +1141455,COOKSON PEIRCE & CO INC +1141781,NICHOLS & PRATT ADVISERS LLP /MA +1141802,NORTHWESTERN MUTUAL WEALTH MANAGEMENT CO +1142031,PRIVATE MANAGEMENT GROUP INC +1142062,VAN DEN BERG MANAGEMENT I INC +1142495,WEDBUSH SECURITIES INC +1142941,DENALI ADVISORS LLC +1143261,EQUITEC PROPRIETARY MARKETS LLC +1143565,RATIONAL ADVISORS INC. +1144208,BLUEFIN CAPITAL MANAGEMENT LLC +1144492,Meiji Yasuda Life Insurance Co +1145020,THORNBURG INVESTMENT MANAGEMENT INC +1145255,HENNESSY ADVISORS INC +1157436,SHEETS SMITH WEALTH MANAGEMENT +1157519,OKABENA INVESTMENT SERVICES INC +1158202,Penn Capital Management Company LLC +1158970,SPINNAKER TRUST +1161670,CULLINAN ASSOCIATES INC +1161722,HOLLENCREST CAPITAL MANAGEMENT +1161822,GREENWOOD CAPITAL ASSOCIATES LLC +1162170,GREENLEAF TRUST +1162777,Partners Value Investments L.P. +1162781,HARVEY INVESTMENT CO LLC +1162827,RBO & CO LLC +1163648,CI INVESTMENTS INC. +1163653,NOMURA HOLDINGS INC +1163668,SOUTH PLAINS FINANCIAL INC. +1163744,Conestoga Capital Advisors LLC +1163902,FOSTER & MOTLEY INC +1164062,TEALWOOD ASSET MANAGEMENT INC +1164478,KANAWHA CAPITAL MANAGEMENT LLC +1164508,ARROWSTREET CAPITAL LIMITED PARTNERSHIP +1164632,Chesley Taft & Associates LLC +1164833,HOTCHKIS & WILEY CAPITAL MANAGEMENT LLC +1165002,WESTWOOD HOLDINGS GROUP INC +1165408,ADAGE CAPITAL PARTNERS GP L.L.C. +1165797,CAUSEWAY CAPITAL MANAGEMENT LLC +1165805,WOODLEY FARRA MANION PORTFOLIO MANAGEMENT INC +1166152,Prescott Group Capital Management L.L.C. +1166308,STEEL GROVE CAPITAL ADVISORS LLC +1166309,BRIDGER MANAGEMENT LLC +1166402,FCA CORP /TX +1166588,BNP PARIBAS FINANCIAL MARKETS +1166620,KLCM Advisors Inc. +1166716,STRATEGY ASSET MANAGERS LLC +1166928,WEST BANCORPORATION INC +1167026,PRINCETON CAPITAL MANAGEMENT LLC +1167212,NEEDHAM INVESTMENT MANAGEMENT LLC +1167483,TIGER GLOBAL MANAGEMENT LLC +1167487,LONGFELLOW INVESTMENT MANAGEMENT CO LLC +1167557,AQR CAPITAL MANAGEMENT LLC +1168889,CARDEROCK CAPITAL MANAGEMENT INC +1169069,VIKING FUND MANAGEMENT LLC +1169318,ALBERT D MASON INC +1169683,OAKLAND FINANCIAL CORP +1169883,STEINBERG ASSET MANAGEMENT LLC +1170152,LEUTHOLD GROUP LLC +1172036,SONATA CAPITAL GROUP INC +1172779,DOCK STREET ASSET MANAGEMENT INC +1173227,SOUTHERN MICHIGAN BANK & TRUST +1173889,SANDER CAPITAL ADVISORS INC +1174850,NICOLET BANKSHARES INC +1175954,CENTRAL BANK & TRUST CO +1177206,LOS ANGELES CAPITAL MANAGEMENT LLC +1177244,SHAPIRO CAPITAL MANAGEMENT LLC +1177416,LANE BROTHERS & CO INC +1177719,WESTFIELD CAPITAL MANAGEMENT CO LP +1179392,TWO SIGMA INVESTMENTS LP +1179475,HUSSMAN STRATEGIC ADVISORS INC. +1179791,CIM INVESTMENT MANAGEMENT INC +1184820,HOLDERNESS INVESTMENTS CO +1191672,CREDIT AGRICOLE S A +1206792,DEARBORN PARTNERS LLC +1207017,LAZARD ASSET MANAGEMENT LLC +1209324,COUNTRY TRUST BANK +1213206,MARATHON CAPITAL MANAGEMENT +1214183,FIRST UNITED BANK & TRUST +1214639,DSM CAPITAL PARTNERS LLC +1214717,GEODE CAPITAL MANAGEMENT LLC +1214822,STEADFAST CAPITAL MANAGEMENT LP +1215208,SOMERVILLE KURT F +1215838,JUPITER ASSET MANAGEMENT LTD +1217541,Diamond Hill Capital Management LLC (Investment Advisor) +1218210,NORDEA INVESTMENT MANAGEMENT AB +1218254,BOYAR ASSET MANAGEMENT INC. +1218583,SHEPHERD KAPLAN KROCHUK LLC +1218663,OBERNDORF WILLIAM E +1218710,Balyasny Asset Management L.P. +1222993,WATERS PARKERSON & CO. LLC +1224324,GUARDIAN CAPITAL LP +1224890,CULBERTSON A N & CO INC +1226886,ALPINE WOODS CAPITAL INVESTORS LLC +1228242,BRITISH COLUMBIA INVESTMENT MANAGEMENT Corp +1230239,ALKEON CAPITAL MANAGEMENT LLC +1230765,RICE HALL JAMES & ASSOCIATES LLC +1232395,SILVERCREST ASSET MANAGEMENT GROUP LLC +1234074,SPRUCEGROVE INVESTMENT MANAGEMENT LTD +1238990,PUNCH & ASSOCIATES INVESTMENT MANAGEMENT INC. +1245862,OXFORD FINANCIAL GROUP LTD. LLC +1252007,GRAYBILL WEALTH MANAGEMENT LTD. +1255435,CUMBERLAND ADVISORS INC +1256071,PLATINUM INVESTMENT MANAGEMENT LTD +1257391,WHITEBOX ADVISORS LLC +1259261,BOSTON RESEARCH & MANAGEMENT INC +1259671,WBH ADVISORY INC +1259887,LONDON CO OF VIRGINIA +1259969,FARMERS & MERCHANTS INVESTMENTS INC +1260468,Bernzott Capital Advisors +1260824,EVERGREEN CAPITAL MANAGEMENT LLC +1262677,NOESIS CAPITAL MANGEMENT CORP +1263548,WHITTIER TRUST CO OF NEVADA INC +1265131,Hilltop Holdings Inc. +1265376,George Kaiser Family Foundation +1265905,GWN SECURITIES INC. +1266014,WRAPMANAGER INC +1266227,STONEHILL CAPITAL MANAGEMENT LLC +1269119,OPPENHEIMER ASSET MANAGEMENT INC. +1269786,Overbrook Management Corp +1269950,DELTEC ASSET MANAGEMENT LLC +1269978,Alesco Advisors LLC An ESL Co +1271347,TIFF ADVISORY SERVICES LLC +1272164,AMERICAN NATIONAL BANK & TRUST +1272544,MONTGOMERY INVESTMENT MANAGEMENT INC +1273087,MILLENNIUM MANAGEMENT LLC +1274173,JANUS HENDERSON GROUP PLC +1274419,CREDIT INDUSTRIEL ET COMMERCIAL +1274981,NATIXIS +1275218,ALGERT GLOBAL LLC +1275880,HARBOUR INVESTMENT MANAGEMENT LLC +1276144,CLOUGH CAPITAL PARTNERS L P +1276460,CHICKASAW CAPITAL MANAGEMENT LLC +1276470,CORSAIR CAPITAL MANAGEMENT L.P. +1276525,DIKER MANAGEMENT LLC +1276755,CHELSEA COUNSEL CO +1276853,WILKINS INVESTMENT COUNSEL INC +1276918,ZEVENBERGEN CAPITAL INVESTMENTS LLC +1277279,THOMPSON INVESTMENT MANAGEMENT INC. +1277303,First National Trust Co +1277557,PRIVATE TRUST CO NA +1277779,CONTINENTAL ADVISORS LLC +1278249,TOWER BRIDGE ADVISORS +1278573,Destination Wealth Management +1278678,DEROY & DEVEREAUX PRIVATE INVESTMENT COUNSEL INC +1278793,WEIK CAPITAL MANAGEMENT +1279030,BRANT POINT INVESTMENT MANAGEMENT LLC +1279150,SCOPIA CAPITAL MANAGEMENT LP +1279708,MENLO ADVISORS LLC +1279885,CINCINNATI INSURANCE CO +1279888,CINCINNATI CASUALTY CO +1279891,WOLVERINE ASSET MANAGEMENT LLC +1279936,Cantillon Capital Management LLC +1280043,SUMMITRY LLC +1280487,FIDEURAM - INTESA SANPAOLO PRIVATE BANKING S.P.A. +1280604,WEYBOSSET RESEARCH & MANAGEMENT LLC +1281761,REGIONS FINANCIAL CORP +1282189,VALUEWORKS LLC +1283072,GUGGENHEIM CAPITAL LLC +1283718,CANADA PENSION PLAN INVESTMENT BOARD +1284208,NTV Asset Management LLC +1284812,COHEN & STEERS INC. +1285973,CUTLER INVESTMENT COUNSEL LLC +1286478,UNITED BANK +1286534,WINSLOW ASSET MANAGEMENT INC +1287618,DUMONT & BLAKE INVESTMENT ADVISORS LLC +1287978,BASSO CAPITAL MANAGEMENT L.P. +1290162,QVT Financial LP +1290668,Sustainable Growth Advisers LP +1291318,OVERSEA-CHINESE BANKING Corp Ltd +1291422,FIRST AMERICAN BANK +1291424,American Trust Investment Advisors LLC +1294588,Weaver C. Barksdale & Associates Inc. +1295044,Strategic Point Investment Advisors LLC +1297376,Advisors Asset Management Inc. +1297496,LETKO BROSSEAU & ASSOCIATES INC +1299351,Madison Asset Management LLC +1299910,Skylands Capital LLC +1301540,Rock Point Advisors LLC +1302404,Channing Capital Management LLC +1303159,SCS Capital Management LLC +1304229,SeaBridge Investment Advisors LLC +1305473,Insight Holdings Group LLC +1305707,ProVise Management Group LLC +1305841,Epoch Investment Partners Inc. +1306333,MOTCO +1307878,LAFFER TENGLER INVESTMENTS INC. +1308016,GRANDFIELD & DODD LLC +1308290,MeadowBrook Investment Advisors LLC +1308331,Boit C F David +1308377,Lafayette Investments Inc. +1308527,Douglass Winthrop Advisors LLC +1308555,Mountain Lake Investment Management LLC +1308685,Stack Financial Management Inc +1308778,America First Investment Advisors LLC +1309148,Hills Bank & Trust Co +1310658,Ledyard National Bank +1310929,Willis Investment Counsel +1313294,Linscomb Wealth Inc. +1313360,SG Americas Securities LLC +1313473,Canal Insurance CO +1313756,Owl Creek Asset Management L.P. +1313792,Edgemoor Investment Advisors Inc. +1313871,First National Bank of Hutchinson +1313893,Maple Capital Management Inc. +1313978,Permit Capital LLC +1313998,Bristlecone Value Partners LLC +1314273,Avalon Global Asset Management LLC +1314376,Broderick Brian C +1314377,Kidder Stephen W +1314404,JMG Financial Group Ltd. +1314440,Stephens Investment Management Group LLC +1314620,Hillman Capital Management Inc. +1315059,Suncoast Equity Management +1315269,Scott & Selber Inc. +1315339,FIRST FINANCIAL BANK - TRUST DIVISION +1315421,Graham Capital Management L.P. +1315785,Essex Financial Services Inc. +1315926,Watershed Asset Management L.L.C. +1316507,Calamos Advisors LLC +1316580,Luxor Capital Group LP +1316617,SATURNA CAPITAL CORP +1317253,Minneapolis Portfolio Management Group LLC +1317267,Kamunting Street Capital Management L.P. +1317348,BOK Financial Private Wealth Inc. +1317583,SCOPUS ASSET MANAGEMENT L.P. +1317724,Mondrian Investment Partners LTD +1317784,SFE Investment Counsel +1317802,Guild Investment Management Inc. +1317961,Latash Investments LLC +1318011,Weil Company Inc. +1318055,FARMERS TRUST CO +1318103,Kimelman & Baird LLC +1318259,White Pine Investment CO +1318601,Acropolis Investment Management LLC +1318757,MARSHALL WACE LLP +1319998,Southpoint Capital Advisors LP +1321194,Argyle Capital Management LLC +1321993,GRIMES & Co WEALTH MANAGEMENT LLC +1322853,Foyston Gordon & Payne Inc +1323414,Pacific Heights Asset Management LLC +1323645,CAPITAL FUND MANAGEMENT S.A. +1324279,Coastline Trust Co +1324290,Altshuler Shaham Ltd +1325447,First Eagle Investment Management LLC +1326234,Allen Investment Management LLC +1326389,Polar Asset Management Partners Inc. +1326766,Violich Capital Management Inc. +1327055,Bragg Financial Advisors Inc +1327944,Town & Country Bank & Trust CO dba First Bankers Trust CO +1328062,Cornerstone Investment Partners LLC +1328785,Senvest Management LLC +1329883,Sterling Capital Management LLC +1329948,Janney Montgomery Scott LLC +1330325,Opus Capital Group LLC +1330387,Amundi +1330463,Ironwood Investment Counsel LLC +1331074,MCDONALD PARTNERS LLC +1331875,Fidelity National Financial Inc. +1331997,Westend Capital Management LLC +1332342,Texas Yale Capital Corp. +1332632,CHILTON INVESTMENT CO INC. +1332811,KNIGHTSBRIDGE ASSET MANAGEMENT LLC +1332905,Curi Capital LLC +1333792,Sky Investment Group LLC +1333986,Equitable Holdings Inc. +1334199,Cambridge Financial Group Inc. +1334952,INSIGHT 2811 INC. +1335325,HighVista Strategies LLC +1335382,BANK OF NOVA SCOTIA TRUST CO +1335644,SCOTIA CAPITAL INC. +1335730,MIZUHO FINANCIAL GROUP INC +1335851,Private Wealth Partners LLC +1336528,Pershing Square Capital Management L.P. +1337263,Hodges Capital Management Inc. +1339270,SOL Capital Management CO +1339908,FIRST NATIONAL BANK & TRUST CO OF NEWTOWN +1341401,River Road Asset Management LLC +1341748,Cypress Capital Group +1342396,FOUNDERS FINANCIAL SECURITIES LLC +1342857,North Star Investment Management Corp. +1344551,ASSETMARK INC +1344717,Estabrook Capital Management +1345576,Advisors Capital Management LLC +1345929,BROWN ADVISORY INC +1346378,Baldwin Investment Management LLC +1347683,Haverford Financial Services Inc. +1348883,Clearbridge Investments LLC +1349353,United American Securities Inc. (d/b/a UAS Asset Management) +1349434,LOCUST WOOD CAPITAL ADVISERS LLC +1349654,Obermeyer Wealth Partners +1350290,Portolan Capital Management LLC +1350660,PHILLIPS FINANCIAL MANAGEMENT LLC +1350694,Bridgewater Associates LP +1350780,Private Capital Advisors Inc. +1351431,Matthew 25 Management Corp +1351731,WestEnd Advisors LLC +1351917,MENORA MIVTACHIM HOLDINGS LTD. +1351950,Findlay Park Partners LLP +1351991,Rathbones Group PLC +1352122,Robertson Opportunity Capital LLC +1352187,Clark Capital Management Group Inc. +1352260,INTEGRITY ALLIANCE LLC. +1352272,Miura Global Management LLC +1352449,Norman Fields Gottscho Capital Management LLC +1352467,BBR PARTNERS LLC +1352526,Hartford Financial Management Inc. +1352547,Legacy Private Trust Co. +1352662,Grantham Mayo Van Otterloo & Co. LLC +1352675,Bangor Savings Bank +1352851,Magnetar Financial LLC +1352860,BLUE BELL PRIVATE WEALTH MANAGEMENT LLC +1352864,Forvis Mazars Wealth Advisors LLC +1352871,Benin Management CORP +1352895,EMERALD MUTUAL FUND ADVISERS TRUST +1353098,Cutler Capital Management LLC +1353110,Aldebaran Financial Inc. +1353311,Marathon Partners Equity Management LLC +1353312,TREMBLANT CAPITAL GROUP +1353316,Hound Partners LLC +1353318,Progressive Investment Management Corp +1353394,Gladstone Capital Management LLP +1353395,Fulcrum Asset Management LLP +1353570,Campbell & CO Investment Adviser LLC +1353651,Leith Wheeler Investment Counsel Ltd. +1354821,LEVIN CAPITAL STRATEGIES L.P. +1356202,Point Windward Advisors Inc. +1356407,Indiana Trust & Investment Management Co +1356783,Phocas Financial Corp. +1357550,Weiss Asset Management LP +1357955,ProShare Advisors LLC +1358706,ABRAMS CAPITAL MANAGEMENT L.P. +1358828,Financial Sense Advisors Inc. +1359262,MAKENA CAPITAL MANAGEMENT LLC +1360533,Orion Porfolio Solutions LLC +1360710,Hirtle & Co. LLC +1360798,BRIGHTON JONES LLC +1361974,Beutel Goodman & Co Ltd. +1362033,GSA CAPITAL PARTNERS LLP +1362535,Cullen Capital Management LLC +1364725,CIM LLC +1365167,DIVIDEND ASSETS CAPITAL LLC +1365474,GUARDIAN INVESTMENT MANAGEMENT +1365559,Baker Ellis Asset Management LLC +1365707,CONTRAVISORY INVESTMENT MANAGEMENT INC. +1366838,Liberty Capital Management Inc. +1367401,VAN LANSCHOT KEMPEN INVESTMENT MANAGEMENT N.V. +1367653,Huntleigh Advisors Inc. +1368163,Zurcher Kantonalbank (Zurich Cantonalbank) +1368465,Hillsdale Investment Management Inc. +1369702,AMI ASSET MANAGEMENT CORP +1369913,American Investment Services Inc. +1370102,State of Alaska Department of Revenue +1371726,MYCIO WEALTH PARTNERS LLC +1372130,HEADINVEST LLC +1373017,BCM ADVISORS LLC +1373442,Mariner LLC +1374170,NORGES BANK +1374384,Intesa Sanpaolo S.p.A. +1374889,Ballentine Partners LLC +1375534,GENERATION INVESTMENT MANAGEMENT LLP +1376113,ALPS ADVISORS INC +1376192,Clal Insurance Enterprises Holdings Ltd +1376772,Dorsey Wright & Associates +1376879,AKO CAPITAL LLP +1377167,Financial Gravity Companies Inc. +1377581,First Pacific Advisors LP +1378145,VISION FINANCIAL MARKETS LLC +1378559,First Dallas Securities Inc. +1379995,HBK Sorce Advisory LLC +1380137,HUNTER ASSOCIATES INVESTMENT MANAGEMENT LLC +1380443,Valmark Advisers Inc. +1381055,Penbrook Management LLC +1381296,Aureus Asset Management LLC +1382303,Goodman Financial Corp +1382646,Ifrah Financial Services Inc. +1383782,Merrion Investment Management Co LLC +1384042,Dorsey & Whitney Trust CO LLC +1384416,Investment Partners Asset Management Inc. +1384484,MAD RIVER INVESTORS +1384943,Bath Savings Trust Co +1384982,Columbus Hill Capital Management L.P. +1385925,Horrell Capital Management Inc. +1386060,Boston Partners +1386364,Harrington Investments INC +1386462,Ionic Capital Management LLC +1386928,Park West Asset Management LLC +1386929,Adams Asset Advisors LLC +1386935,Keel Point LLC +1387130,Marble Harbor Investment Counsel LLC +1387304,Richard C. Young & CO. LTD. +1387322,Whale Rock Capital Management LLC +1387369,KETTLE HILL CAPITAL MANAGEMENT LLC +1387386,MORRIS FINANCIAL CONCEPTS INC. +1387458,LaFleur & Godfrey LLC +1387508,PRELUDE CAPITAL MANAGEMENT LLC +1387615,Aull & Monroe Investment Management Corp +1387723,BANK OF STOCKTON +1387761,TWIN FOCUS CAPITAL PARTNERS LLC +1387818,Barton Investment Management +1387921,Harber Asset Management LLC +1388028,REIK & CO. LLC +1388142,Bedell Frazier Investment Counselling LLC +1388168,Atlas Brown Inc. +1388325,NOVO HOLDINGS A/S +1388382,HALL LAURIE J TRUSTEE +1388391,Walleye Trading LLC +1388409,NOVARE CAPITAL MANAGEMENT LLC +1388437,Paragon Capital Management LLC +1388443,Palisades Hudson Asset Management L.P. +1388829,AMG National Trust Bank +1389059,BLB&B Advisors LLC +1389082,Arrow Capital Management LLC +1389223,Mathes Company Inc. +1389234,SYMMETRY PEAK MANAGEMENT LLC +1389256,American Assets Investment Management LLC +1389400,Stockman Wealth Management Inc. +1389426,Rafferty Asset Management LLC +1389507,DISCOVERY CAPITAL MANAGEMENT LLC / CT +1389544,ALLEN OPERATIONS LLC +1389574,Nicholas Investment Partners LP +1389709,WHALEROCK POINT PARTNERS LLC +1389848,Arlington Partners LLC +1389958,PEAK6 LLC +1390003,Southeast Asset Advisors LLC +1390043,First Washington CORP +1390063,Westchester Capital Management Inc. +1390202,Laurion Capital Management LP +1390777,Bank of New York Mellon Corp +1391166,Lee Financial Co +1392364,L & S Advisors Inc +1393389,Manchester Capital Management LLC +1393818,Blackstone Inc. +1393825,Hudson Bay Capital Management LP +1393944,Hanson & Doremus Investment Management +1394096,ZEVIN ASSET MANAGEMENT LLC +1394866,Penobscot Investment Management Company Inc. +1395055,Callahan Advisors LLC +1395067,Marquette Asset Management LLC +1396318,Public Sector Pension Investment Board +1397290,Rodgers Brothers Inc. +1397424,WBI INVESTMENTS LLC +1397960,Woodbridge Co Ltd +1398318,Handelsbanken Fonder AB +1398346,MEITAV INVESTMENT HOUSE LTD +1398739,Employees Retirement System of Texas +1399706,South Street Advisors LLC +1399794,Choate Investment Advisors +1401459,RS CRUM INC. +1401561,GHP Investment Advisors Inc. +1403438,LPL Financial LLC +1404574,683 Capital Management LLC +1404652,Jaffetilchin Investment Partners LLC +1404763,J.W. COLE ADVISORS INC. +1404780,CASTLEKEEP INVESTMENT ADVISORS LLC +1404784,GRANVILLE CAPITAL INC. +1407382,Miller Investment Management LP +1407543,ENVESTNET ASSET MANAGEMENT INC +1409362,AdvisorNet Financial Inc +1409427,Boston Common Asset Management LLC +1409661,Guinness Asset Management LTD +1409765,Guinness Atkinson Asset Management Inc +1410833,Night Owl Capital Management LLC +1411133,KBC Group NV +1411530,Sumitomo Mitsui DS Asset Management Company Ltd +1411784,Pinnacle Holdings LLC +1412741,J. Goldman & Co LP +1415912,Migdal Insurance & Financial Holdings Ltd. +1416856,Fruth Investment Management +1417889,Vision Capital Management Inc. +1418329,Ninety One UK Ltd +1418333,MACQUARIE GROUP LTD +1418342,Seascape Capital Management +1418359,K.J. Harrison & Partners Inc +1418421,Capital Investment Counsel Inc +1418773,Robeco Institutional Asset Management B.V. +1418814,ValueAct Holdings L.P. +1419099,INTERACTIVE FINANCIAL ADVISORS INC. +1419186,Cambridge Investment Research Advisors Inc. +1419999,MAR VISTA INVESTMENT PARTNERS LLC +1420473,Financial Engines Advisors L.L.C. +1420816,Telemark Asset Management LLC +1421097,SAMLYN CAPITAL LLC +1421224,CIBC WORLD MARKET INC. +1421669,3G Capital Partners LP +1422508,Montecito Bank & Trust +1422848,Capital Research Global Investors +1422849,Capital World Investors +1423045,First National Bank of Mount Dora Trust Investment Services +1423053,CITADEL ADVISORS LLC +1423296,Zuckerman Investment Group LLC +1423442,O'SHAUGHNESSY ASSET MANAGEMENT LLC +1423673,Hikari Tsushin Inc. +1423686,CADIAN CAPITAL MANAGEMENT LP +1424177,Birch Hill Investment Advisors LLC +1424367,Voya Financial Advisors Inc. +1424381,LAKEWOOD CAPITAL MANAGEMENT LP +1424717,Plante Moran Financial Advisors LLC +1425165,Apriem Advisors +1425930,AdvisorShares Investments LLC +1425949,WealthTrust Axiom LLC +1426092,Longview Partners (Guernsey) LTD +1426196,CAPSTONE INVESTMENT ADVISORS LLC +1426318,PCJ Investment Counsel Ltd. +1426319,Cannell & Spears LLC +1426327,Scheer Rowlett & Associates Investment Management Ltd. +1426398,Focused Investors LLC +1426588,Barnett & Company Inc. +1426748,Lazard Freres Gestion S.A.S. +1426754,Wallington Asset Management LLC +1426755,J.P. Marvel Investment Advisors LLC +1426763,Cincinnati Specialty Underwriters Insurance CO +1426774,R.M.SINCERBEAUX CAPITAL MANAGEMENT LLC +1426851,Argent Advisors Inc. +1426853,Crestwood Advisors Group LLC +1426859,Ruffer LLP +1426940,Horizon Investment Services LLC +1426960,BANTA ASSET MANAGEMENT LP +1427099,GreatBanc Trust CO +1427119,Meritage Group LP +1427147,Cheviot Value Management LLC +1427196,Diversified Investment Strategies LLC +1427202,Busey Bank +1427263,GFS Advisors LLC +1427350,First City Capital Management Inc. +1427351,MERIDIAN INVESTMENT COUNSEL INC. +1427372,Gibson Capital LLC +1427514,Dana Investment Advisors Inc. +1427748,Truepoint Inc. +1428569,Holowesko Partners Ltd. +1428793,First Heartland Consultants Inc. +1429390,Harel Insurance Investments & Financial Services Ltd. +1430022,Harold Davidson & Associates Inc. +1430681,Hayek Kallen Investment Management +1432529,Baker Avenue Asset Management LP +1432539,Strategic Financial Services Inc. +1433541,ASPIRIANT LLC +1434165,ATLAS CAPITAL ADVISORS INC. +1434323,Palisade Asset Management LLC +1434819,APG Asset Management N.V. +1434845,Cardinal Capital Management +1435028,Fisher Funds Management LTD +1438284,OXFORD ASSET MANAGEMENT LLP +1438574,Sterneck Capital Management LLC +1438848,GAM Holding AG +1439743,Mechanics Bank Trust Department +1439805,Greenwich Wealth Management LLC +1441689,Korea Investment CORP +1441888,Tributary Capital Management LLC +1442056,CONFLUENCE INVESTMENT MANAGEMENT LLC +1442273,ACT CAPITAL MANAGEMENT LLC +1442573,HOURGLASS CAPITAL LLC +1442641,Legato Capital Management LLC +1443077,INTACT INVESTMENT MANAGEMENT INC. +1443095,Welch Group LLC +1443689,Senator Investment Group LP +1444949,SUSQUEHANNA ADVISORS GROUP INC. +1445893,CTC LLC +1445911,Quantitative Investment Management LLC +1446114,Ancora Advisors LLC +1446179,SUSQUEHANNA FUNDAMENTAL INVESTMENTS LLC +1446194,SUSQUEHANNA INTERNATIONAL GROUP LLP +1447578,RIVERNORTH CAPITAL MANAGEMENT LLC +1447884,Fosun International Ltd +1448430,Aldebaran Capital LLC +1448574,MOORE CAPITAL MANAGEMENT LP +1449088,MILLINGTON SECURITIES LLC +1449126,Sigma Planning Corp +1450144,TWO SIGMA SECURITIES LLC +1450935,B & T Capital Management DBA Alpha Capital Management +1451623,BENJAMIN EDWARDS INC +1452208,CACTI ASSET MANAGEMENT LLC +1452689,Valiant Capital Management L.P. +1452765,GTS SECURITIES LLC +1452861,IMC-Chicago LLC +1453072,Alyeska Investment Group L.P. +1453381,Jasper Ridge Partners L.P. +1453526,Private Harbour Investment Management & Counsel LLC +1453620,Cohen Klingenstein LLC +1454027,Verition Fund Management LLC +1454308,Tanglewood Wealth Management Inc. +1454424,Archon Partners LLC +1454502,Triple Frond Partners LLC +1454984,Ensign Peak Advisors Inc +1455176,Biondo Investment Advisors LLC +1455251,Hollow Brook Wealth Management LLC +1455253,HS Management Partners LLC +1455258,Calamos Wealth Management LLC +1455267,Parkside Financial Bank & Trust +1455288,Shelter Mutual Insurance Co +1455452,Tiptree Advisors LLC +1455495,IMA Advisory Services Inc. +1455845,LS Investment Advisors LLC +1455915,OLD MISSION CAPITAL LLC +1455969,Reynders McVeigh Capital Management LLC +1456048,SIGNATUREFD LLC +1456133,Convergence Investment Partners LLC +1456228,DekaBank Deutsche Girozentrale +1456670,DNB Asset Management AS +1457005,PRIVATE CLIENT SERVICES LLC +1457320,EXCHANGE TRADED CONCEPTS LLC +1459270,Freshford Capital Management LLC +1459754,Wallace Capital Management Inc. +1461287,Narwhal Capital Management +1461790,K2 PRINCIPAL FUND L.P. +1462020,Chevy Chase Trust Holdings LLC +1462160,Mitsubishi UFJ Trust & Banking Corp +1462245,HighTower Advisors LLC +1463217,UNITED CAPITAL FINANCIAL ADVISORS LLC +1463559,Alberta Investment Management Corp +1463746,Scharf Investments LLC +1463753,Ipswich Investment Management Co. Inc. +1464811,B. Riley Wealth Advisors Inc. +1465109,NEUBERGER BERMAN GROUP LLC +1466153,Artisan Partners Limited Partnership +1466546,Mitsubishi UFJ Asset Management Co. Ltd. +1466697,FLOW TRADERS U.S. LLC +1467517,Freedom Day Solutions LLC +1468792,HARBOR INVESTMENT ADVISORY LLC +1469026,HMI Capital Management L.P. +1469219,YHB Investment Advisors Inc. +1469475,Mesirow Financial Investment Management Inc. +1469589,Q Global Advisors LLC +1469751,RiverFront Investment Group LLC +1470876,Freestone Capital Holdings LLC +1470944,LMCG INVESTMENTS LLC +1471085,Bronte Capital Management Pty Ltd. +1471265,Northwest Bancshares Inc. +1471384,Canandaigua National Trust Co of Florida +1471474,Avidian Wealth Enterprises LLC +1472190,PGGM Investments +1472800,Eastover Investment Advisors LLC +1473429,Petrus Trust Company LTA +1475045,CARY STREET PARTNERS FINANCIAL LLC +1475150,Randolph Co Inc +1475271,MOLLER WEALTH PARTNERS +1475365,Sumitomo Mitsui Trust Group Inc. +1475597,HRT FINANCIAL LP +1475896,Asset Dedication LLC +1475933,Minot DeBlois Advisors LLC +1475940,MONTRUSCO BOLTON INVESTMENTS INC. +1476179,Firsthand Capital Management Inc. +1476329,Nexus Investment Management ULC +1476804,Roundview Capital LLC +1477024,Johnson Financial Group Inc. +1477872,Saratoga Research & Investment Management +1479465,FARLEY CAPITAL L.P. +1479844,DIVERSIFIED TRUST CO +1479847,Voleon Capital Management LP +1480751,Amova Asset Management Americas Inc. +1481045,Daiwa Securities Group Inc. +1481669,EdgePoint Investment Group Inc. +1481714,Verus Financial Partners Inc. +1481986,DRW Securities LLC +1482012,Smith Salley Wealth Management +1482106,Courage Miller Partners LLC +1482171,DAVIS-REA LTD. +1482611,CORRADO ADVISORS LLC +1482688,Sageworth Trust Co +1482689,Evercore Wealth Management LLC +1482880,Savant Capital LLC +1482935,Van Cleef Asset Management Inc +1482970,Stillwater Capital Advisors LLC +1483065,Supplemental Annuity Collective Trust of NJ +1483066,State of New Jersey Common Pension Fund D +1483467,Westover Capital Advisors LLC +1483472,ArchPoint Investors +1483503,TB Alternative Assets Ltd. +1483824,TD Capital Management LLC +1483859,ArrowMark Colorado Holdings LLC +1483864,JBF Capital Inc. +1483870,TCTC Holdings LLC +1484043,ITHAKA GROUP LLC +1484047,BloombergSen Inc. +1484085,SHAYNE & JACOBS LLC +1484205,HALBERT HARGROVE GLOBAL ADVISORS LLC +1484256,RiverPark Advisors LLC +1484265,McKinley Carter Wealth Services Inc. +1484429,Alecta Tjanstepension Omsesidigt +1484540,WESPAC Advisors LLC +1486083,Corundum Group Inc. +1486946,VISTA CAPITAL PARTNERS INC. +1487438,DONALDSON CAPITAL MANAGEMENT LLC +1488542,SIMPLEX TRADING LLC +1488921,Legacy Wealth Management LLC +1490429,First Long Island Investors LLC +1491072,Anson Funds Management LP +1491685,Meiji Yasuda Asset Management Co Ltd. +1491719,Lombard Odier Asset Management (USA) Corp +1491998,Ninety One SA (Pty) Ltd +1492040,JCIC Asset Management Inc. +1492162,Lesa Sroufe & Co +1494234,Hemenway Trust Co LLC +1496201,SPHERA FUNDS MANAGEMENT LTD. +1496228,SRB CORP +1496637,Norinchukin Bank The +1497637,Accuvest Global Advisors +1498383,Asset Allocation & Management Company LLC +1500605,NEW VERNON INVESTMENT MANAGEMENT LLC +1502149,Transamerica Financial Advisors LLC +1503174,SRS Investment Management LLC +1503269,Heathbridge Capital Management Ltd. +1504169,TOKIO MARINE ASSET MANAGEMENT CO LTD +1504941,Portland Global Advisors LLC +1505183,Stockbridge Partners LLC +1505207,THOMAS STORY & SON LLC +1505817,Fiera Capital Corp +1505961,Decatur Capital Management Inc. +1506071,Wellington Shields Capital Management LLC +1506073,Wellington Shields & Co. LLC +1507683,RWWM INC. +1507971,KELLEHER FINANCIAL ADVISORS +1508097,Sanders Capital LLC +1508120,Cohen Capital Management Inc. +1508195,Retirement Planning Group LLC +1508512,Drexel Morgan & Co. +1508755,AXON CAPITAL LP +1508822,ACR Alpine Capital Research LLC +1509508,Renaissance Investment Group LLC +1509510,Thomas J. Herzfeld Advisors Inc. +1509550,Sequent Asset Management LLC +1509842,PointState Capital LP +1509873,STERLING INVESTMENT MANAGEMENT LLC +1509973,BRIGHT ROCK CAPITAL MANAGEMENT LLC +1509974,Summit Asset Management LLC +1510281,Saba Capital Management L.P. +1510387,Gotham Asset Management LLC +1510434,Empirical Financial Services LLC d.b.a. Empirical Wealth Management +1510481,Sarasin & Partners LLP +1510668,Crystal Rock Capital Management +1510669,Contour Asset Management LLC +1510677,Consulta Ltd +1510809,AVALON CAPITAL MANAGEMENT +1510848,Timber Creek Capital Management LLC +1510870,Carlton Hofferkamp & Jenks Wealth Management LLC +1510912,Ulysses Management LLC +1511037,Patton Albertson Miller Group LLC +1511098,Baskin Financial Services Inc. +1511229,Orca Investment Management LLC +1511506,Carnegie Investment Counsel +1511550,Asset Management Group Inc. +1511697,Huber Capital Management LLC +1511739,VestGen Investment Management +1511857,Clear Harbor Asset Management LLC +1511881,Theleme Partners LLP +1511888,Martin Investment Management LLC +1512022,HERALD INVESTMENT MANAGEMENT Ltd +1512024,CAPTRUST FINANCIAL ADVISORS +1512026,SFMG LLC +1512073,AFT FORSYTH & COMPANY INC. +1512162,COOPER CREEK PARTNERS MANAGEMENT LLC +1512171,Route One Investment Company L.P. +1512173,MAPLELANE CAPITAL LLC +1512237,Global Endowment Management LP +1512367,SeaTown Holdings Pte. Ltd. +1512397,Knighthead Capital Management LLC +1512538,New Vernon Capital Holdings II LLC +1512601,BSW Wealth Partners +1512611,Nelson Capital Management LLC +1512779,SRH ADVISORS LLC +1512780,Private Wealth Group LLC +1512814,Motley Fool Asset Management LLC +1512857,Brevan Howard Capital Management LP +1512858,NFC Investments LLC +1512920,SPROTT INC. +1512978,Brookmont Capital Management +1512991,Quantbot Technologies LP +1513038,Perkins Coie Trust Co +1513126,Wharton Business Group LLC +1513189,First Western Trust Bank +1513193,Arbiter Partners Capital Management LLC +1513211,Main Street Research LLC +1513227,Smart Portfolios LLC +1513300,TRB Advisors LP +1513703,Axiom Investment Management LLC +1516450,WEALTHCARE CAPITAL MANAGEMENT LLC +1517429,Reliant Investment Management LLC +1517796,Rangeley Capital LLC +1517857,Soroban Capital Partners LP +1518235,Cardinal Capital Management Inc. +1518364,Accredited Investors Inc. +1518934,Stanley Capital Management LLC +1519676,Stelac Advisory Services LLC +1519921,SWISS RE LTD +1520309,Mizuho Securities Co. Ltd. +1520354,BNP PARIBAS ASSET MANAGEMENT Holding S.A. +1520478,RWC Asset Management LLP +1520601,AFFINITY INVESTMENT ADVISORS LLC +1520683,CWC Advisors LLC. +1520710,Uniplan Investment Counsel Inc. +1521001,Parallax Volatility Advisers L.P. +1521951,First Business Financial Services Inc. +1522877,AIA Group Ltd +1525865,Clean Yield Group +1525947,Kessler Investment Group LLC +1527488,TOBAM +1527641,MATHER GROUP LLC. +1527781,Gallagher Fiduciary Advisors LLC +1528214,Richard Bernstein Advisors LLC +1528593,Black Creek Investment Management Inc. +1529090,AKUNA SECURITIES LLC +1529389,Cambria Investment Management L.P. +1529735,MetLife Investment Management LLC +1531593,SUMMIT SECURITIES GROUP LLC +1531611,Covalent Partners LLC +1531809,CapWealth Advisors LLC +1532262,Greenbrier Partners Capital Management LLC +1532385,Genus Capital Management Inc. +1532842,BTS Asset Management Inc. +1532943,Palogic Value Management L.P. +1533421,Tower Research Capital LLC (TRC) +1533457,FOX RUN MANAGEMENT L.L.C. +1533504,Sompo Asset Management Co. Ltd. +1533950,Zweig-DiMenna Associates LLC +1533954,Institute for Wealth Management LLC. +1533964,Virtu Financial LLC +1534259,Partners Group Holding AG +1534358,USA FINANCIAL FORMULAS +1534450,Ground Swell Capital LLC +1534561,Vantage Investment Partners LLC +1534653,Skandinaviska Enskilda Banken AB (publ) +1534866,Boston Trust Walden Corp +1534949,MKP Capital Management L.L.C. +1535061,Edgestream Partners L.P. +1535110,Parametrica Management Ltd +1535128,SBI Okasan Asset Management Co.Ltd. +1535172,Berkshire Money Management Inc. +1535202,Comprehensive Wealth Management LLC +1535227,Peregrine Asset Advisers Inc. +1535293,J.Safra Asset Management Corp +1535323,Allianz Asset Management GmbH +1535385,Artemis Investment Management LLP +1535387,Dynamic Technology Lab Private Ltd +1535392,MANGROVE PARTNERS IM LLC +1535452,Andra AP-fonden +1535472,Corvex Management LP +1535588,Twin Tree Management LP +1535602,BANQUE PICTET & CIE SA +1535611,Silverleafe Capital Partners LLC +1535630,ELEMENT CAPITAL MANAGEMENT LLC +1535631,PICTET BANK & TRUST Ltd +1535660,Lombard Odier Asset Management (Switzerland) SA +1535784,Lombard Odier Asset Management (Europe) Ltd +1535811,Redhawk Wealth Advisors Inc. +1535839,Donor Advised Charitable Giving Inc. +1535845,HEALTHCARE OF ONTARIO PENSION PLAN TRUST FUND +1535847,CWM LLC +1535859,Rezny Wealth Management Inc. +1535865,Atria Investments Inc +1535943,Alphadyne Asset Management LP +1535950,Leonteq Securities AG +1536006,Progeny 3 Inc. +1536029,Advance Capital Management Inc. +1536080,Banco BTG Pactual S.A. +1536105,Magellan Asset Management Ltd +1536114,Alta Advisers Ltd +1536186,LANDSCAPE CAPITAL MANAGEMENT L.L.C. +1536230,HEFFERNAN ADVISORY INC +1536411,Duquesne Family Office LLC +1536430,HENGEHOLD CAPITAL MANAGEMENT LLC +1536444,Camarda Financial Advisors LLC +1536446,PINKERTON WEALTH LLC +1536549,MKD WEALTH COACHES LLC +1536557,Crabel Capital Management LLC +1536592,Krane Funds Advisors LLC +1536630,Potrero Capital Research LLC +1536755,Commonwealth Financial Services LLC +1536799,Jackson Wealth Management LLC +1536890,BAROMETER CAPITAL MANAGEMENT INC. +1536924,LBMC INVESTMENT ADVISORS LLC +1536925,Vestcor Inc +1537014,Candriam S.C.A. +1537191,Louisiana State Employees Retirement System +1537319,Community Financial Services Group LLC +1537530,SCGE MANAGEMENT L.P. +1537621,DT Investment Partners LLC +1537783,Kentucky Retirement Systems +1538052,Ocean Park Asset Management LLC +1538383,Westside Investment Management Inc. +1538449,Mawer Investment Management Ltd. +1538846,South Dakota Investment Council +1538853,MIRABELLA FINANCIAL SERVICES LLP +1539041,PICTON MAHONEY ASSET MANAGEMENT +1539204,Crossmark Global Holdings Inc. +1539436,Standard Investments LLC +1539919,Waratah Capital Advisors Ltd. +1539947,Family Manage LLC +1539948,United Asset Strategies Inc. +1539994,AEGON ASSET MANAGEMENT UK Plc +1540235,Creative Planning +1540358,a16z Capital Management L.L.C. +1540462,Richmond Brothers Inc. +1540569,EP Wealth Advisors LLC +1540656,Gladius Capital Management LP +1540826,EULAV Asset Management +1540867,New England Professional Planning Group Inc. +1540880,FJARDE AP-FONDEN /FOURTH SWEDISH NATIONAL PENSION FUND +1540944,GREATMARK INVESTMENT PARTNERS INC. +1541211,Hilton Capital Management LLC +1541353,Triangle Securities Wealth Management +1541399,Ramsay Stattman Vela & Price Inc. +1541448,Veritas Asset Management LLP +1541496,Hendershot Investments Inc. +1541596,Aspen Investment Management Inc +1541617,Altimeter Capital Management LP +1541625,Prospera Financial Services Inc +1541743,Copeland Capital Management LLC +1541787,Nadler Financial Group Inc. +1541910,Illinois Municipal Retirement Fund +1542108,Verity & Verity LLC +1542143,Modera Wealth Management LLC +1542153,Focus Partners Wealth +1542161,Glaxis Capital Management LLC +1542165,BCWM LLC +1542265,Gradient Investments LLC +1542266,Granite Investment Partners LLC +1542284,Madden Advisory Services Inc. +1542287,Annex Advisory Services LLC +1542300,GoodHaven Capital Management LLC +1542302,LYRICAL ASSET MANAGEMENT LP +1542324,Mraz Amerine & Associates Inc. +1542383,Aviance Capital Partners LLC +1542629,Baader Bank Aktiengesellschaft +1542927,National Mutual Insurance Federation of Agricultural Cooperatives +1543100,Windsor Capital Management LLC +1543536,Hudock Inc. +1543568,Connective Capital Management LLC +1543991,Smith & Howard Wealth Management LLC +1544204,Liontrust Investment Partners LLP +1544366,Samalin Investment Counsel LLC +1544576,RWA WEALTH PARTNERS LLC +1544599,Bank Julius Baer & Co. Ltd Zurich +1544806,VICUS CAPITAL +1545545,Mitsubishi UFJ Morgan Stanley Securities Co. Ltd. +1545812,CORDA Investment Management LLC. +1546007,Assenagon Asset Management S.A. +1546587,Hartford Funds Management Co LLC +1546865,Ascent Wealth Partners LLC +1546967,Iowa State Bank +1546989,NewSquare Capital LLC +1547007,Dorsal Capital Management LP +1547926,Livforsakringsbolaget Skandia Omsesidigt +1548059,TCFG WEALTH MANAGEMENT LLC +1548392,Utah Retirement Systems +1548577,New Harbor Financial Group LLC +1548882,Eos Management L.P. +1549042,Diligent Investors LLC +1549230,BOOTHBAY FUND MANAGEMENT LLC +1549275,Catalyst Capital Advisors LLC +1549738,JW Asset Management LLC +1550057,Goelzer Investment Management Inc. +1550100,Vanguard Investments Australia Ltd. +1550191,Stonehearth Capital Management LLC +1551017,TD PRIVATE CLIENT WEALTH LLC +1551727,MACROVIEW INVESTMENT MANAGEMENT LLC +1551969,Gilman Hill Asset Management LLC +1552247,Weaver Capital Management LLC +1552999,HT Partners LLC +1553562,AMF Tjanstepension AB +1553733,Brave Warrior Advisors LLC +1554308,Seven Post Investment Office LP +1554427,U S WEALTH GROUP LLC. +1554656,Raub Brock Capital Management LP +1554871,Spark Investment Management LLC +1554913,Pamplona Capital Management LLC +1554961,Spectrum Strategic Capital Management LLC +1555170,Allworth Financial LP +1555283,Kemnay Advisory Services Inc. +1555486,Baltimore-Washington Financial Advisors Inc. +1555512,PRAGMA GESTAO DE PATRIMONIO LTD +1555623,Trivest Advisors Ltd +1556168,Consolidated Investment Group LLC +1556218,Bollard Group LLC +1556245,Sandhill Capital Partners LLC +1556785,Vulcan Value Partners LLC +1556921,VOLORIDGE INVESTMENT MANAGEMENT LLC +1557017,Capula Management Ltd +1557406,Sippican Capital Advisors +1557485,Bluespring Wealth Management LLC +1558481,ARIZONA STATE RETIREMENT SYSTEM +1559789,Regal Investment Advisors LLC +1560717,Horizon Investments LLC +1561082,ABN AMRO Bank N.V. +1561330,Saxony Capital Management LLC +1561383,Financial Designs Corp +1561728,COOPER INVESTORS PTY LTD +1561790,Thomasville National Bank +1562087,Thiel Macro LLC +1562230,Capital International Investors +1562855,Clarkston Capital Partners LLC +1563525,Chicago Partners Investment Group LLC +1563634,Pachira Investments Inc. +1563690,Boltwood Capital Management +1564396,Powerhouse Assets LLC +1564702,PDT Partners LLC +1564770,Oxbow Advisors LLC +1564835,ASAHI LIFE ASSET MANAGEMENT CO. LTD. +1565432,Selkirk Management LLC +1565854,Zimmer Partners LP +1565951,DC Investments Management LLC +1566030,INVESTMENT HOUSE LLC +1566307,Ramiah Investment Group +1566475,Cerity Partners LLC +1566493,Newman Dignan & Sheerar Inc. +1566531,Spectrum Financial Alliance Ltd LLC +1566601,FDx Advisors Inc. +1566653,Freedom Investment Management Inc. +1566728,Capital Wealth Planning LLC +1566801,Aft Forsyth & Sober LLC +1566887,Ratan Capital Management LP +1567013,Bolthouse Investments LLC +1567163,Edge Wealth Management LLC +1567195,Incline Global Management LLC +1567247,Commerce Advisors LLC +1567755,Private Advisor Group LLC +1567889,Telos Capital Management Inc. +1567890,Redmond Asset Management LLC +1567912,Somerset Group LLC +1567993,Colonial Trust Advisors +1568068,JFS WEALTH ADVISORS LLC +1568069,Idaho Trust Co +1568132,Financial Management Professionals Inc. +1568235,King Wealth Management Group +1568280,BW Gestao de Investimentos Ltda. +1568303,West Family Investments Inc. +1568540,Sather Financial Group Inc +1568621,Broad Run Investment Management LLC +1568787,Waverly Advisors LLC +1568788,Palestra Capital Management LLC +1568839,PRING TURNER CAPITAL GROUP INC +1568991,Alpha Family Trust +1569036,Covington Investment Advisors Inc. +1569049,LIGHT STREET CAPITAL MANAGEMENT LLC +1569064,MONTANOVA CAPITAL LLC +1569102,A. D. Beadell Investment Counsel Inc. +1569118,JNBA Financial Advisors +1569119,Strategic Advisors LLC +1569148,Murphy Pohlad Asset Management LLC +1569205,Fundsmith LLP +1569356,NINE MASTS CAPITAL Ltd +1569395,Mirae Asset Global Investments Co. Ltd. +1569411,Addenda Capital Inc. +1569452,Advocacy Wealth Management LLC +1569453,F&V Capital Management LLC +1569454,AMERICAN ASSET MANAGEMENT INC. +1569518,Kintegral Advisory LLC +1569550,CTC Alternative Strategies Ltd. +1569579,BTG Pactual Asset Management US LLC +1569638,M Holdings Securities Inc. +1569650,BANK OZK +1569667,OARSMAN CAPITAL INC. +1569688,Kerrisdale Advisers LLC +1569709,ICONIQ Capital LLC +1569758,Carmignac Gestion +1569765,Paragon Capital Management Ltd +1569766,Camelot Portfolios LLC +1569833,Rothschild Capital Partners LLC +1569863,Bernicke Wealth Management Ltd. +1569884,RPg Family Wealth Advisory LLC +1570253,QV Investors Inc. +1570271,Headlands Technologies LLC +1570284,Gator Capital Management LLC +1570396,Lipe & Dalton +1571075,Cunning Capital Partners LP +1571556,Pure Financial Advisors LLC +1571727,Oakum Bay Capital LLC +1572748,Marathon Trading Investment Management LLC +1572838,Empirical Finance LLC +1573263,Wiser Wealth Management Inc +1573876,Advisory Services Network LLC +1573947,BURRUS FINANCIAL SERVICES INC. +1574408,LVZ Inc. +1574850,Tsai Capital Corp +1574886,Teewinot Capital Advisers L.L.C. +1574947,COMGEST GLOBAL INVESTORS S.A.S. +1575151,TIEMANN INVESTMENT ADVISORS LLC +1575239,Perigon Wealth Management LLC +1575581,Segment Wealth Management LLC +1575662,Lionstone Capital Management LLC +1575677,FLOSSBACH VON STORCH SE +1576053,LVW Advisors LLC +1576102,Blue Fin Capital Inc. +1576151,EXENCIAL WEALTH ADVISORS LLC +1576704,Measured Risk Portfolios Inc. +1576762,Advisory Alpha LLC +1577001,W.G. Shaheen & Associates DBA Whitney & Co +1577774,Regal Partners Ltd +1578242,Circle Wealth Management LLC +1578299,DLD Asset Management LP +1578370,Madrona Financial Services LLC +1578621,LMR Partners LLP +1578985,Cumberland Partners Ltd +1579254,East Coast Asset Management LLC. +1580212,Strategic Global Advisors LLC +1580415,Kazazian Asset Management LLC +1580677,Sustainable Insight Capital Management LLC +1581465,Addison Capital Co +1581641,Tuttle Capital Management LLC +1581655,Alpine Global Management LLC +1581794,Northern Capital Management Inc. +1581811,Egerton Capital (UK) LLP +1582112,Rather & Kittrell Inc. +1582151,NAPLES GLOBAL ADVISORS LLC +1582202,Swiss National Bank +1582272,Battery Global Advisors LLC +1582561,Blackhawk Capital Partners LLC +1582633,Kiltearn Partners LLP +1582732,Capital Investment Advisors LLC +1583751,TCI Wealth Advisors Inc. +1584087,1492 Capital Management LLC +1584258,DUMAC INC. +1584686,Kentucky Retirement Systems Insurance Trust Fund +1584801,YCG LLC +1585047,Columbia Asset Management +1585822,Winch Advisory Services LLC +1585828,FOUNDERS CAPITAL MANAGEMENT LLC +1585859,Composition Wealth LLC +1586052,Focused Wealth Management Inc +1586678,Balentine LLC +1586767,Arax Advisory Partners +1586882,J2 Capital Management Inc +1587192,Blume Capital Management Inc. +1587281,VIRTUS ADVISERS LLC +1587381,USS Investment Management Ltd +1587643,Nepsis Inc. +1587867,Cornerstone Management Inc. +1587973,State of Tennessee Department of Treasury +1588340,Vontobel Holding Ltd. +1588456,Private Capital Management LLC +1588871,OAKWORTH CAPITAL INC. +1588959,Black Maple Capital Management LP +1589176,SPX Gestao de Recursos Ltda +1589282,Fort Point Capital Partners LLC +1589689,MYDA Advisors LLC +1590073,Arbor Investment Advisors LLC +1590144,PURA VIDA INVESTMENTS LLC +1590214,BIP Wealth LLC +1590228,Interval Partners LP +1590495,Yelin Lapidot Holdings Management Ltd. +1590531,Foxhaven Asset Management LP +1591097,Harvest Investment Services LLC +1591122,Heritage Wealth Advisors +1591379,BEACON FINANCIAL GROUP +1591505,Swan Global Investments LLC +1591744,Shellback Capital LP +1592178,Chicago Wealth Management Inc. +1592413,Strategy Capital LLC +1592579,Trinity Street Asset Management LLP +1592613,Formidable Asset Management LLC +1592614,Dempze Nancy E +1592616,Notis-McConarty Edward +1592643,Select Equity Group L.P. +1592746,Fullerton Fund Management Co Ltd. +1592828,Empowered Funds LLC +1593038,Coyle Financial Counsel LLC +1593051,Retirement Systems of Alabama +1593324,Penserra Capital Management LLC +1593387,GeoWealth Management LLC +1593410,Claar Advisors LLC +1593600,Riverview Trust Co +1593688,M. Kraus & Co +1594320,Coronation Fund Managers Ltd. +1594417,Beta Wealth Group Inc. +1594492,Field & Main Bank +1594916,Metis Global Partners LLC +1595082,Davidson Kempner Capital Management LP +1595509,IRON Financial LLC +1595521,Aristides Capital LLC +1595880,Junto Capital Management LP +1595888,JANE STREET GROUP LLC +1595932,GUARDIAN POINT CAPITAL LP +1596055,Silver Lake Advisory LLC +1596077,New England Research & Management Inc. +1596355,Asset Management Advisors LLC +1596510,PARK PRESIDIO CAPITAL LLC +1596800,Connor Clark & Lunn Investment Management Ltd. +1596901,Pettee Investors Inc. +1596906,Keystone Financial Planning Inc. +1596957,FAS Wealth Partners Inc. +1597089,Loudon Investment Management LLC +1597099,Somerset Trust Co +1597200,PECAUT & CO. +1597298,Verity Asset Management Inc. +1597484,John Boyer Inc. +1597690,Trust Investment Advisors +1597694,MUFG Securities EMEA plc +1597823,Parkwood LLC +1597843,PEDDOCK CAPITAL ADVISORS LLC +1597857,Hartree Partners LP +1597878,Capital Advisors Ltd. LLC +1598102,Biltmore Wealth Management LLC +1598176,Bayshore Capital Advisors LLC +1598177,HOERTKORN RICHARD CHARLES +1598180,Waldron Private Wealth LLC +1598186,Checchi Capital Advisers LLC +1598304,GM Advisory Group LLC +1598352,R.H. Dinel Investment Counsel Inc. +1598379,WILSEY ASSET MANAGEMENT INC +1598550,E&G Advisors LLC +1598611,Friedenthal Financial +1598697,Gulf International Bank (UK) Ltd +1598841,Advisors Preferred LLC +1599016,Rench Wealth Management Inc. +1599054,Bouchey Financial Group Ltd. +1599084,Cranbrook Wealth Management LLC +1599217,Capstone Financial Advisors Inc. +1599330,Joel Isaacson & Co. LLC +1599383,WINDACRE PARTNERSHIP LLC +1599390,HWG Holdings LP +1599511,Tortoise Investment Management LLC +1599576,Pictet North America Advisors SA +1599579,Plancorp LLC +1599584,Brookstone Capital Management +1599603,Tarbox Family Office Inc. +1599620,Blue Edge Capital LLC +1599623,Shoker Investment Counsel Inc. +1599637,Permanens Capital L.P. +1599670,Barbara Oil Co. +1599719,Mivtachim The Workers Social Insurance Fund Ltd. (Under Special Management) +1599731,Atika Capital Management LLC +1599746,Hamilton Point Investment Advisors LLC +1599747,Dynamic Advisor Solutions LLC +1599760,HAMEL ASSOCIATES INC. +1599852,Holt Capital Advisors L.L.C. dba Holt Capital Partners L.P. +1599868,Avior Wealth Management LLC +1599900,Sequoia Financial Advisors LLC +1599923,Community Bank of Raymore +1600035,Stonebridge Capital Advisors LLC +1600064,Tidal Investments LLC +1600085,American Money Management LLC +1600136,Clearline Capital LP +1600145,Van Hulzen Asset Management LLC +1600151,Arete Wealth Advisors LLC +1600152,Bellecapital International Ltd. +1600177,Employees Provident Fund Board +1600285,BlueSpruce Investments LP +1600307,SIGNET FINANCIAL MANAGEMENT LLC +1600319,Bridgewater Advisors Inc. +1600327,CAHILL FINANCIAL ADVISORS INC +1600344,Lighthouse Investment Partners LLC +1600403,CKW FINANCIAL GROUP +1600435,Wakefield Asset Management LLLP +1600585,Mendel Money Management +1600636,Select Asset Management & Trust +1600746,Eagle Capital Management LLC +1600944,SAM Advisors LLC +1600999,MANAGED ASSET PORTFOLIOS LLC +1601086,ARMISTICE CAPITAL LLC +1601348,Riggs Asset Managment Co. Inc. +1601384,Allen Capital Group LLC +1601407,Troy Asset Management Ltd +1601489,Stanley-Laman Group Ltd. +1601539,CHICAGO TRUST Co NA +1601622,LBJ Family Wealth Advisors Ltd. +1601904,BECK CAPITAL MANAGEMENT LLC +1602020,Anfield Capital Management LLC +1602189,Dragoneer Investment Group LLC +1602198,Capital Impact Advisors LLC +1602224,Wealthstar Advisors LLC +1602237,IPG Investment Advisors LLC +1602476,Lumbard & Kellner LLC +1602716,Long Focus Capital Management LLC +1602730,Abacus Planning Group Inc. +1602905,MCF Advisors LLC +1603001,Malaga Cove Capital LLC +1603466,Point72 Asset Management L.P. +1604723,RKL Wealth Management LLC +1604873,Summer Road LLC +1605070,Bienville Capital Management LLC +1605401,PKS Advisory Services LLC +1605522,St. Johns Investment Management Company LLC +1606134,SCHOLTZ & COMPANY LLC +1606152,Hikari Power Ltd +1606430,Two Creeks Capital Management LP +1606477,Tikvah Management LLC +1606507,Arlington Capital Management Inc. +1606588,One Capital Management LLC +1606609,Archford Capital Strategies LLC +1606666,Fort Sheridan Advisors LLC +1607239,Moors & Cabot Inc. +1607278,Whetstone Capital Advisors LLC +1607355,MARK SHEPTOFF FINANCIAL PLANNING LLC +1607636,EagleClaw Capital Managment LLC +1607825,Gemmer Asset Management LLC +1607866,FAGAN ASSOCIATES INC. +1607978,Motley Fool Wealth Management LLC +1608034,Lunt Capital Management Inc. +1608046,National Pension Service +1608057,Curbstone Financial Management Corp +1608126,Finepoint Capital LP +1608376,Trust Asset Management LLC +1608485,LANSDOWNE PARTNERS (UK) LLP +1608826,INTEGRATED CAPITAL MANAGEMENT INC. +1609098,Darsana Capital Partners LP +1609120,Freemont Management S.A. +1609674,Mengis Capital Management Inc. +1610520,UBS Group AG +1610580,Del-Sette Capital Management LLC +1610769,Caprock Group LLC +1610880,BlueCrest Capital Management Ltd +1611518,Wealth Architects LLC +1611519,PARUS FINANCE (UK) Ltd +1611848,BTC Capital Management Inc. +1612063,WINTON GROUP Ltd +1612865,Stratos Wealth Partners LTD. +1613331,Fragasso Financial Advisors Inc +1615135,Ironsides Asset Advisors LLC +1615423,Compagnie Lombard Odier SCmA +1615717,Triad Investment Management +1616004,S. R. Schill & Associates +1616026,Tradewinds Capital Management LLC +1616034,HighPoint Advisor Group LLC +1616328,Garde Capital Inc. +1616336,One River Asset Management LLC +1616664,Raab & Moskowitz Asset Management LLC +1616667,Pacer Advisors Inc. +1616882,Alden Global Capital LLC +1619083,OGBORNE CAPITAL MANAGEMENT LLC +1619124,Cardano Risk Management B.V. +1619390,Graticule Asia Macro Advisors LLC +1619532,M.E. ALLISON & CO. INC. +1619779,RiverGlades Family Offices LLC +1619844,Hyperion Asset Management Ltd +1619899,Bramshill Investments LLC +1620081,Long Road Investment Counsel LLC +1620220,Engineers Gate Manager LP +1620943,IQ EQ FUND MANAGEMENT (IRELAND) Ltd +1621100,Compass Ion Advisors LLC +1621225,Merit Financial Group LLC +1621646,Richardson Capital Management LLC +1621802,INFRASTRUCTURE CAPITAL ADVISORS LLC +1621855,Alta Park Capital LP +1621915,CIDEL ASSET MANAGEMENT INC +1622346,SHANDA ASSET MANAGEMENT HOLDINGS Ltd +1622431,McNamara Financial Services Inc. +1622610,Absolute Gestao de Investimentos Ltda. +1622757,Elite Wealth Management Inc. +1623678,PLIMOTH TRUST CO LLC +1623707,Alliance Wealth Management Group +1623781,Morgan Creek Capital Management LLC +1624095,Tenzing Global Management LLC +1624510,FINANCIAL CONSULATE INC +1624758,Capital Analysts LLC +1625008,AG Asset Advisory LLC +1625244,C WorldWide Group Holding A/S +1625246,Summit Financial Strategies Inc. +1625292,Argent Trust Co +1625800,Strid Group LLC +1625959,Arvest Bank Trust Division +1625986,Gibraltar Capital Management Inc. +1626116,SVB WEALTH LLC +1626379,Evanson Asset Management LLC +1626757,NIXON PEABODY TRUST CO +1627003,Insight Wealth Partners LLC +1628818,Aegis Wealth Management LLC +1628896,Capital Advantage Inc. +1629271,PALLADIEM LLC +1629290,Verde Servicos Internacionais S.A. +1629649,NORTHWESTERN MUTUAL INVESTMENT MANAGEMENT COMPANY LLC +1629931,Teamwork Financial Advisors LLC +1629984,Crescent Park Management L.P. +1629996,Alken Asset Management Ltd +1630360,Ironwood Financial llc +1630365,AIMZ Investment Advisors LLC +1630936,FORESIGHT CAPITAL MANAGEMENT ADVISORS INC. +1630939,Boston Standard Wealth Management LLC +1631014,ALTAROCK PARTNERS LP +1631052,Northside Capital Management LLC +1631073,RAFFA WEALTH MANAGEMENT LLC +1631208,Credit Capital Investments LLC +1631353,Dakota Wealth Management +1631408,Northstar Group Inc. +1631507,Leisure Capital Management +1631627,RW Investment Management LLC +1631639,Barry Investment Advisors LLC +1631773,Concorde Asset Management LLC +1631775,Godshalk Welsh Capital Management Inc. +1631864,Pinnacle Wealth Management Advisory Group LLC +1631930,Neumann Capital Management LLC +1631941,Capital Planning Advisors LLC +1631943,Point Break Capital Management LLC +1632078,Spectrum Asset Management Inc. (NB/CA) +1632096,JACOBSON & SCHMITT ADVISORS LLC +1632097,NorthRock Partners LLC +1632105,MITCHELL MCLEOD PUGH & WILLIAMS INC +1632108,Lagoda Investment Management L.P. +1632118,BONTEMPO OHLY CAPITAL MGMT LLC +1632187,Community Bank N.A. +1632253,Brick & Kyle Associates +1632283,Summit Financial Wealth Advisors LLC +1632341,Belvedere Trading LLC +1632368,Prospect Capital Advisors LLC +1632512,Peak Asset Management LLC +1632551,Bayesian Capital Management LP +1632553,Eukles Asset Management +1632554,Trust Co +1632801,Blue Chip Partners LLC +1632802,BUTENSKY & COHEN FINANCIAL SECURITY INC +1632812,BDF-GESTION +1632813,CMT Capital Markets Trading GmbH +1632844,Hurley Capital LLC +1632866,JUNCTURE WEALTH STRATEGIES LLC +1632932,Sunbelt Securities Inc. +1632965,Private Advisory Group LLC +1632966,Farmers National Bank +1632968,Wealthquest Corp +1632972,WEALTH ENHANCEMENT ADVISORY SERVICES LLC +1633024,KWMG LLC +1633037,Rehmann Capital Advisory Group +1633046,Maven Securities LTD +1633207,Patten Group Inc. +1633227,McGowan Group Asset Management Inc. +1633275,Quinn Opportunity Partners LLC +1633288,Towercrest Capital Management +1633343,Ninety One North America Inc. +1633366,MV CAPITAL MANAGEMENT INC. +1633387,RFG Advisory LLC +1633389,Koshinski Asset Management Inc. +1633445,Trexquant Investment LP +1633446,Mirador Capital Partners LP +1633448,INSPIRION WEALTH ADVISORS LLC +1633516,NewEdge Advisors LLC +1633573,Colony Family Offices LLC +1633603,All-Stars Investment Ltd +1633625,IAT REINSURANCE CO LTD. +1633648,Quaker Capital Investments LLC +1633695,Cambridge Advisors Inc. +1633697,Sowell Financial Services LLC +1633716,USA FINANCIAL FORMULAS +1633799,Nishkama Capital LLC +1633857,AlphaStar Capital Management LLC +1633862,Lincoln Capital LLC +1633896,Cypress Capital Management LLC (WY) +1633901,Betterment LLC +1633910,Hedeker Wealth LLC +1633969,Cryder Capital Partners LLP +1634083,C Partners Holding GmbH +1634149,Anchor Investment Management LLC +1634208,Analyst IMS Investment Management Services Ltd. +1634212,Diversified Portfolios Inc. +1634556,True North Advisors LLC +1635007,Mason Investment Advisory Services Inc. +1635342,ETF Portfolio Partners Inc. +1635523,CLEAR INVESTMENT RESEARCH LLC +1635633,TRUST POINT INC. +1635663,Steamboat Capital Partners LLC +1635999,Think Investments LP +1636948,Covea Finance +1637241,Genesis Wealth Advisors LLC +1637246,Pensionfund Sabic +1637460,Man Group plc +1637541,XPONANCE LLC +1637689,Swiss Life Asset Management Ltd +1637946,General Catalyst Group Management LLC +1638022,Elgethun Capital Management +1638520,GFG Capital LLC +1638555,SUMMIT PARTNERS PUBLIC ASSET MANAGEMENT LLC +1639375,Sendero Wealth Management LLC +1639666,ISTHMUS PARTNERS LLC +1639695,TruNorth Capital Management LLC +1639753,Kranot Hishtalmut Le Morim Ve Gananot Havera Menahelet LTD +1639754,Kranot Hishtalmut Le Morim Tichoniim Havera Menahelet LTD +1639943,WealthPLAN Partners LLC +1639997,Westhampton Capital LLC +1640335,Pointe Capital Management LLC +1640951,Empire Life Investments Inc. +1641043,Ehrenkranz Partners L.P. +1641296,Intrepid Financial Planning Group LLC +1641438,Financial Enhancement Group LLC +1641447,Lavaca Capital LLC +1641761,1 NORTH WEALTH SERVICES LLC +1641864,Giverny Capital Inc. +1641866,IFP Advisors Inc +1641992,LGT CAPITAL PARTNERS LTD. +1642058,Financial Insights Inc. +1642216,Ayalon Insurance Comp Ltd. +1642246,Sharkey Howes & Javer +1642274,Empirical Capital Management LLC +1642305,Roble Belko & Company Inc +1642570,River Wealth Advisors LLC +1642575,Squarepoint Ops LLC +1643833,BRASADA CAPITAL MANAGEMENT LP +1644128,Ellevest Inc. +1644187,Governors Lane LP +1644956,WILLIAM BLAIR INVESTMENT MANAGEMENT LLC +1645382,MISSION WEALTH MANAGEMENT LP +1645890,Fiduciary Group LLC +1646247,Wealthspire Advisors LLC +1646639,Hamilton Capital LLC +1646695,Steele Capital Management Inc. +1646821,D'Orazio & Associates Inc. +1647251,TCI FUND MANAGEMENT LTD +1647273,Perpetual Ltd +1647363,Keenan Capital LLC +1648711,Baird Financial Group Inc. +1649107,Moisand Fitzgerald Tamayo LLC +1649147,Harfst & Associates Inc. +1649186,Prostatis Group LLC +1649451,KESTRA PRIVATE WEALTH SERVICES LLC +1649647,EDMOND DE ROTHSCHILD HOLDING S.A. +1650092,Westbourne Investment Advisors Inc. +1650135,Hosking Partners LLP +1650142,Bristol Gate Capital Partners Inc. +1650150,Lido Advisors LLC +1650258,Avanda Investment Management Pte. Ltd. +1650290,Tredje AP-fonden +1650300,Jackson Grant Investment Advisers Inc. +1650717,QUADRANT CAPITAL GROUP LLC +1651023,Florida Trust Wealth Management Co +1651424,Quadrature Capital Ltd +1651473,Alight Capital Management LP +1651960,Arcus Capital Partners LLC +1652062,Tairen Capital Ltd +1652174,Turim 21 Investimentos Ltda. +1652327,Harspring Capital Management LLC +1652348,Genesee Capital Advisors LLC +1652442,ANTIPODES PARTNERS Ltd +1652529,Trinity Legacy Partners LLC +1652594,World Investment Advisors +1653169,Sciencast Management LP +1653199,FFT WEALTH MANAGEMENT LLC +1653202,LGL PARTNERS LLC +1653443,Avant Capital LLC +1653926,First PREMIER Bank +1654033,COOPER/HAIMS ADVISORS LLC +1654111,Pelham Capital Ltd. +1654175,Proficio Capital Partners LLC +1654599,BEACON INVESTMENT ADVISORY SERVICES INC. +1654648,CAT ROCK CAPITAL MANAGEMENT LP +1654847,Community Bank & Trust Waco Texas +1655006,NWAM LLC +1655543,Cavalier Investments LLC +1655982,NorthLanding Financial Partners LLC +1656150,Piedmont Capital Management LLC +1656167,RENASANT BANK +1656187,MUFG SECURITIES (CANADA) LTD. +1656282,First Affirmative Financial Network +1656456,APPALOOSA LP +1657111,NS Partners Ltd +1657428,Manitou Investment Management Ltd. +1657516,CMH Wealth Management LLC +1657980,Denver PWM LLC +1658020,Railway Pension Investments Ltd +1658363,Maple Rock Capital Partners Inc. +1658509,Shakespeare Wealth Management LLC +1658535,Orgel Wealth Management LLC +1658652,Terra Nova Asset Management LLC +1659047,Krilogy Financial LLC +1659171,BigSur Wealth Management LLC +1659196,DJE Kapital AG +1659203,Capital Investment Advisory Services LLC +1659346,Deane Retirement Strategies Inc. +1659380,Sender Co & Partners Inc. +1659718,Econ Financial Services Corp +1659978,Quartz Partners LLC +1660177,Integrated Advisors Network LLC +1660328,Freestate Advisors LLC +1660531,FengHe Fund Management Pte. Ltd. +1660694,Grey Ledge Advisors LLC +1660708,ABS Investment Management LLC +1661140,Oribel Capital Management LP +1661144,St. Louis Trust Co +1661245,Moss Adams Wealth Advisors LLC +1661535,Elefante Mark B +1661536,Delaney Dennis R +1661580,Ilmarinen Mutual Pension Insurance Co +1661762,Oak Grove Capital LLC +1662212,Grove Bank & Trust +1662449,Klingman & Associates LLC +1663224,Darwin Wealth Management LLC +1663649,Tiller Private Wealth Inc. +1663865,Orbis Allan Gray Ltd +1664147,Colorado Capital Management Inc. +1664193,Aptus Capital Advisors LLC +1664324,Mandatum Life Insurance Co Ltd +1664656,Curtis Advisory Group LLC +1664713,Intermede Investment Partners Ltd +1664771,Sitrin Capital Management LLC +1664847,Armbruster Capital Management Inc. +1664999,Longitude (Cayman) Ltd. +1665097,Index Fund Advisors Inc. +1665198,NorthCoast Asset Management LLC +1665241,Schonfeld Strategic Advisors LLC +1665302,Cottage Street Advisors LLC +1665337,Integrated Investment Consultants LLC +1665359,Schneider Downs Wealth Management Advisors LP +1665446,HOME FEDERAL BANK OF TENNESSEE +1665518,PHYSICIANS FINANCIAL SERVICES INC. +1665590,Engine Capital Management LP +1665633,Alpha Omega Wealth Management LLC +1665642,Cedar Wealth Management LLC +1665751,BRILLIANCE ASSET MANAGEMENT LTD +1665976,Coastal Bridge Advisors LLC +1666024,Capital Asset Advisory Services LLC +1666231,Union Square Park Capital Management LLC +1666239,Oliver Luxxe Assets LLC +1666256,Exane Asset Management +1666335,Rokos Capital Management LLP +1666363,Venturi Wealth Management LLC +1666470,Baker Chad R +1666504,Fiduciary Wealth Partners LLC +1666582,DURANTE & WATERS LLC +1666606,Mint Tower Capital Management B.V. +1666613,CONSOLIDATED CAPITAL MANAGEMENT LLC +1666624,CHARTER RESEARCH & INVESTMENT GROUP INC. +1666664,RPG Investment Advisory LLC +1666733,Canal Capital Management LLC +1666736,Gerber Kawasaki Wealth & Investment Management +1666741,Cetera Investment Advisers +1666786,Traynor Capital Management Inc. +1666905,GARDA CAPITAL PARTNERS LP +1666910,Compass Financial Group Inc. +1666940,Mn Services Vermogensbeheer B.V. +1667019,WIMMER ASSOCIATES 1 LLC +1667074,TRUE Private Wealth Advisors +1667102,Whitegate Investment Counselors Inc. +1667132,Well Done LLC +1667134,CWA Asset Management Group LLC +1667146,Ausdal Financial Partners Inc. +1667694,Berkeley Capital Partners LLC +1668188,WESPAC Advisors SoCal LLC +1668189,Cornerstone Advisory LLC +1668256,Matisse Capital +1668527,Numerai GP LLC +1669162,Kinsale Capital Group Inc. +1669662,Peak Financial Management Inc. +1670139,Inspire Investing LLC +1670627,AGUR PROVIDENT & TRAINING FUNDS MANAGEMENT LTD +1671657,Dorsey Asset Management LLC +1671754,INTRINSIC EDGE CAPITAL MANAGEMENT LLC +1672067,Kelman-Lazarov Inc. +1672070,Parker Investment Management LLC +1672142,DYMON ASIA CAPITAL (SINGAPORE) PTE. LTD. +1672355,RIPOSTE CAPITAL LLC +1672594,Douglas Lane & Associates LLC +1672681,RETIREMENT CAPITAL STRATEGIES +1673385,Morningstar Investment Management LLC +1673633,Silicon Valley Capital Partners +1673907,Premier Fund Managers Ltd +1673954,SevenBridge Financial Group LLC +1674020,HORIZON FINANCIAL SERVICES LLC +1674117,Cornerstone Wealth Management LLC +1674486,Simmons Bank +1674546,Bellevue Group AG +1674836,LEE JOHNSON CAPITAL MANAGEMENT LLC +1675762,Gilbert & Cook Inc. +1675884,Skye Global Management LP +1676603,Probity Advisors Inc. +1677044,OSAIC HOLDINGS INC. +1677253,Intellectus Partners LLC +1677501,PERSONAL CFO SOLUTIONS LLC +1677560,Members Trust Co +1678953,Nicholas Hoffman & Company LLC. +1679031,CHOREO LLC +1679064,Pinnacle Family Advisors LLC +1679543,Phoenix Financial Ltd. +1679688,DigitalBridge Group Inc. +1680208,VANGUARD ASSET MANAGEMENT Ltd +1680365,SL ADVISORS LLC +1680493,Mirabaud Asset Management Ltd +1680613,Almanack Investment Partners LLC. +1680843,NIGHTVIEW CAPITAL LLC +1680964,SOMA EQUITY PARTNERS LP +1681004,Bell Asset Management Ltd +1681372,Fairbanks Capital Management Inc. +1681490,Cascade Investment Advisors Inc. +1681614,Kanen Wealth Management LLC +1682021,DEBUSSY CAPITAL MANAGEMENT LP +1682057,Syverson Strege & Co +1682501,Harbour Capital Advisors LLC +1682576,TTP Investments Inc. +1682598,Sapience Investments LLC +1682733,Riverpoint Wealth Management Holdings LLC +1683059,Wealthcare Advisory Partners LLC +1683182,Front Street Capital Management Inc. +1683689,Kopp Family Office LLC +1684868,Engle Capital Management L.P. +1685364,Trinity Wealth Management LLC +1685676,STOREBRAND ASSET MANAGEMENT AS +1686242,IBEX WEALTH ADVISORS +1686343,RED CRANE WEALTH MANAGEMENT LLC +1686444,LYELL WEALTH MANAGEMENT LP +1686970,ODDO BHF ASSET MANAGEMENT SAS +1686988,L2 Asset Management LLC +1687156,Harbor Advisors LLC +1687241,Castle Hook Partners LP +1687509,Rubric Capital Management LP +1688184,MFA WEALTH ADVISORS LLC +1688511,ThornTree Capital Partners LP +1688666,Knights of Columbus Asset Advisors LLC +1688774,GFI Investment Counsel Ltd. +1688931,REDW Wealth LLC +1689013,JT Stratford LLC +1689144,Legacy Bridge LLC +1689227,Ascension Capital Advisors Inc. +1689232,PATTON FUND MANAGEMENT INC. +1689470,HMS Capital Management LLC +1689646,Jacobi Capital Management LLC +1689829,Connecticut Wealth Management LLC +1689933,Per Stirling Capital Management LLC. +1690010,Parallel Advisors LLC +1690295,Sloy Dahl & Holst LLC +1690370,OneDigital Investment Advisors LLC +1690531,Clearwater Capital Advisors LLC +1690717,ELCO Management Co. LLC +1691766,FLAGSHIP HARBOR ADVISORS LLC +1691827,Glenview Trust Co +1691919,Symmetry Investments LP +1691982,Bowie Capital Management LLC +1692038,SIMON QUICK ADVISORS LLC +1692227,Norway Savings Bank +1692234,Russell Investments Group Ltd. +1692252,ARTHUR M. COHEN & ASSOCIATES LLC +1692507,Centiva Capital LP +1692632,First Bank & Trust +1692751,Sassicaia Capital Advisers LLC +1693636,Sagewood Asset Management LP +1693672,Share Andrew L. +1693838,Ursa Fund Management LLC +1694079,Armor Investment Advisors LLC +1694080,Mutual Advisors LLC +1694126,Harvest Fund Management Co. Ltd +1694164,AustralianSuper Pty Ltd +1694217,DZ BANK AG Deutsche Zentral Genossenschafts Bank Frankfurt am Main +1694283,Robinson Value Management Ltd. +1694284,Tandem Investment Advisors Inc. +1694435,PFG Advisors +1694592,Pettinga Financial Advisors LLC +1694663,CROBAN +1694870,PARTNERS CAPITAL INVESTMENT GROUP LLP +1694883,Patriot Financial Group Insurance Agency LLC +1694895,Mitsubishi UFJ Asset Management (UK) Ltd. +1695078,Faithward Advisors LLC +1695320,Periscope Capital Inc. +1695344,NORTHWEST WEALTH MANAGEMENT LLC +1695490,McAdam LLC +1695582,RB Capital Management LLC +1695664,Mascoma Wealth Management LLC +1695818,Unison Advisors LLC +1695959,Hapanowicz & Associates Financial Services Inc +1696136,Omnia Family Wealth LLC +1696209,Fusion Family Wealth LLC +1696438,Round Hill Asset Management +1696494,Sicart Associates LLC +1696497,Stone House Investment Management LLC +1696615,Kohmann Bosshard Financial Services LLC +1696628,Shepherd Financial Partners LLC +1696715,LEVEL FOUR ADVISORY SERVICES LLC +1696731,New Capital Management LP +1696867,Radnor Capital Management LLC +1696899,Independent Advisor Alliance +1697110,GenTrust LLC +1697162,Chescapmanager LLC +1697228,University of Wisconsin Foundation +1697233,GQG Partners LLC +1697267,Aristotle Atlantic Partners LLC +1697274,NVWM LLC +1697300,Meridian Wealth Management LLC +1697303,Beirne Wealth Consulting Services LLC +1697323,Greenline Partners LLC +1697360,Perennial Advisors LLC +1697375,Farmers & Merchants Trust Co of Chambersburg PA +1697398,Hunting Hill Global Capital LLC +1697478,Fortis Advisors LLC +1697490,IHT Wealth Management LLC +1697493,Symmetry Partners LLC +1697591,CAS Investment Partners LLC +1697715,HCR Wealth Advisors +1697716,LWM Advisory Services LLC +1697717,Covenant Asset Management LLC +1697723,AE Wealth Management LLC +1697725,Ullmann Wealth Partners Group LLC +1697728,Daiichi Life Insurance Co. Ltd. +1697740,Sanchez Wealth Management Group +1697748,ARK Investment Management LLC +1697765,Achmea Investment Management B.V. +1697767,Northwest Quadrant Wealth Management LLC +1697790,WITTENBERG INVESTMENT MANAGEMENT INC. +1697791,United Bank +1697796,WP Advisors LLC +1697847,Marietta Wealth Management LLC +1697848,Woodson Capital Management LP +1697850,ICICI Prudential Asset Management Co Ltd +1697855,CAMDEN NATIONAL BANK +1697856,Financial Advisors Network Inc. +1697882,Flaharty Asset Management LLC +1697953,Summit Global Investments +1698060,Gobi Capital LLC +1698068,Capco Asset Management LLC +1698091,Narus Financial Partners LLC +1698218,RITHOLTZ WEALTH MANAGEMENT +1698222,Pacific Center for Financial Services +1698246,IFM Investors Pty Ltd +1698461,Counterpoint Mutual Funds LLC +1698478,Summit Trail Advisors LLC +1698484,Varma Mutual Pension Insurance Co +1698607,Main Management ETF Advisors LLC +1698750,TFO Wealth Partners LLC +1698777,OmniStar Financial Group Inc. +1698810,Local Pensions Partnership Investment Ltd +1698926,Adalta Capital Management LLC +1699080,Synergy Asset Management LLC +1699506,Ackerman Capital Advisors LLC +1699575,Cable Car Capital LP +1699622,ACCESS FINANCIAL SERVICES INC. +1700481,Bryn Mawr Trust Advisors LLC +1700574,Holocene Advisors LP +1701132,Sterling Investment Advisors Ltd. +1701714,McIlrath & Eck LLC +1701879,Acorns Advisers LLC +1702435,Donoghue Forlines LLC +1703080,MONEYWISE INC. +1703081,Tamar Securities LLC +1703208,Arjuna Capital +1703383,Pinnacle Bancorp Inc. +1703496,Warren Street Wealth Advisors LLC +1703556,Ridgewood Investments LLC +1704107,BlueSky Wealth Advisors LLC +1704212,Black Swift Group LLC +1704300,Founders Capital Management +1704404,Avestar Capital LLC +1705339,MIRAE ASSET GLOBAL ETFS HOLDINGS Ltd. +1705399,Stamos Capital Partners L.P. +1705594,Lake Hills Wealth Management LLC +1705655,Seelaus Asset Management LLC +1705711,Morse Asset Management Inc +1705716,Conservest Capital Advisors Inc. +1705929,QUATTRO FINANCIAL ADVISORS LLC +1706016,Family Legacy Inc. +1706028,Heritage Trust Co +1706164,PenderFund Capital Management Ltd. +1706248,Parkside Investments LLC +1706327,Johnson Financial Group LLC +1706351,CFO4Life Group LLC +1706511,Israel Discount Bank of New York +1706669,TPG Financial Advisors LLC +1706766,Caption Management LLC +1706836,BFSG LLC +1706915,AUA CAPITAL MANAGEMENT LLC +1707202,Stratos Wealth Advisors LLC +1707206,Stratos Investment Management LLC +1707856,Franklin Parlapiano Turner & Welch LLC +1707975,Jordan Park Group LLC +1708001,Clearstead Trust LLC +1708139,Milestone Resources Group Ltd +1708237,Innovator Capital Management LLC +1708759,Maytus Capital Management LLC +1708872,Blankinship & Foster LLC +1709632,Vest Financial LLC +1710207,Lattice Capital Management LLC +1710477,CANTOR FITZGERALD INVESTMENT ADVISORS L.P. +1710537,EverSource Wealth Advisors LLC +1710539,BHK Investment Advisors LLC +1710593,Savoir Faire Capital Management L.P. +1711360,Fidato Wealth LLC +1711924,CIBC Bancorp USA Inc. +1712533,Brickley Wealth Management +1712671,Geneva Partners LLC +1712686,Broadleaf Partners LLC +1712892,ONCE CAPITAL MANAGEMENT LLC +1712901,Melqart Asset Management (UK) Ltd +1713112,Mokosak Advisory Group LLC +1713286,SFAM LLC +1713520,Crescent Grove Advisors LLC +1713558,Peterson Wealth Advisors LLC +1713662,AlphaCore Capital LLC +1713678,GenWealth Group Inc. +1713697,FLC Capital Advisors +1713735,Providence First Trust Co +1714093,Garner Asset Management Corp +1714107,Sage Capital Advisors llc +1714267,THAMES CAPITAL MANAGEMENT LLC +1714341,Abbot Financial Management Inc. +1714506,Mountain Capital Investment Advisors Inc. +1714590,GS Investments Inc. +1714678,Beaton Management Co. Inc. +1715228,Wealth Advisors of Tampa Bay LLC +1715593,Csenge Advisory Group +1715635,Ninepoint Partners LP +1715740,Bogart Wealth LLC +1715783,Grace & Mercy Foundation Inc. +1715862,Cordatus Wealth Management LLC +1716180,MPS Loria Financial Planners LLC +1716399,CAHABA WEALTH MANAGEMENT INC. +1716539,Arlington Financial Advisors LLC +1716607,Union Bancaire Privee UBP SA +1716659,Parisi Gray Wealth Management +1716774,Aberdeen Group plc +1716984,Slow Capital Inc. +1717027,Prism Advisors Inc. +1717443,KLP KAPITALFORVALTNING AS +1717479,Redwood Investment Management LLC +1717658,DeDora Capital Inc. +1717977,AMS Capital Ltda +1718251,FORTRESS PRIVATE LEDGER LLC +1718570,TrinityBridge Ltd +1718858,QVR LLC +1719087,Diametric Capital LP +1719165,Capital Markets Trading UK LLP +1719303,Entruity Wealth LLC +1719305,Verdence Capital Advisors LLC +1719739,Security National Bank +1720235,Measured Wealth Private Client Group LLC +1720292,Compass Advisory Group LLC +1720350,7G CAPITAL MANAGEMENT LLC +1720777,Strategic Wealth Partners Ltd. +1720792,Ruane Cunniff & Goldfarb L.P. +1720969,Legacy Advisors LLC +1720980,KADENSA CAPITAL Ltd +1721168,Truxt Investmentos Ltda. +1721242,Belpointe Asset Management LLC +1721527,Cypress Wealth Services LLC +1721757,DV Trading LLC +1721780,Successful Portfolios LLC +1722053,CenterStar Asset Management LLC +1722283,Fluent Financial LLC +1722436,Sawyer & Company Inc +1722439,Ceeto Capital Group LLC +1722512,Trilogy Capital Inc. +1722641,Valeo Financial Advisors LLC +1723115,Demars Financial Group LLC +1723223,KILEY JUERGENS WEALTH MANAGEMENT LLC +1723397,Steward Partners Investment Advisory LLC +1723514,Guardian Financial Partners LLC +1723643,Shay Capital LLC +1723681,Affiance Financial LLC +1723925,Legacy Financial Strategies LLC +1724090,Fulcrum Capital LLC +1724140,Gemsstock Ltd. +1724729,Refined Wealth Management +1724910,Layline Advisors LLC +1725247,WoodTrust Financial Corp +1725297,One Wealth Advisors LLC +1725362,Bell & Brown Wealth Advisors LLC +1725394,Spectrum Planning & Advisory Services Inc. +1725690,Bridgefront Capital LLC +1725888,Lester Murray Antman dba SimplyRich +1725910,STANSBERRY ASSET MANAGEMENT LLC +1726041,Milestones Private Investment Advisors LLC +1726375,Oak Asset Management LLC +1726609,AAF Wealth Management LLC +1726752,Pinnacle Wealth Planning Services Inc. +1726948,Soapstone Management L.P. +1727269,Columbus Macro LLC +1727336,First Command Advisory Services Inc. +1727342,Hudson Capital Management LLC +1727353,Bluegrass Capital Partners LP +1727407,17 CAPITAL PARTNERS LLC +1727454,Quadrant Private Wealth Management LLC +1727514,Francis Financial Inc. +1727573,McCollum Christoferson Group LLC +1727593,White Lighthouse Investment Management Inc. +1727599,Dynasty Wealth Management LLC +1727605,Castle Rock Wealth Management LLC +1727612,HELIOS CAPITAL MANAGEMENT PTE. LTD +1727642,WJ Interests LLC +1727783,Neo Ivy Capital Management +1727827,Blueshift Asset Management LLC +1727862,Neumann Advisory Hong Kong Ltd +1727917,Gradient Capital Advisors LLC +1727993,Eldridge Investment Advisors Inc. +1728031,A&I FINANCIAL SERVICES LLC +1728121,SeaCrest Wealth Management LLC +1728278,One Madison Group LLC +1728319,IFG Advisory LLC +1728321,Kovack Advisors Inc. +1728355,RAINEY & RANDALL WEALTH ADVISORS INC. +1728657,Wagner Wealth Management LLC +1728681,SYCOMORE ASSET MANAGEMENT +1728778,Landmark Wealth Management LLC +1728850,LSP Investment Advisors LLC +1728866,Smart Money Group LLC +1729045,Tower View Wealth Management LLC +1729048,Nicollet Investment Management Inc. +1729049,Pegasus Asset Management Inc. +1729093,Stordahl Capital Management Inc. +1729094,Gryphon Financial Partners LLC +1729096,Ballew Advisors Inc +1729212,Port Capital LLC +1729254,Front Row Advisors LLC +1729269,Westwind Capital +1729299,Keystone Financial Group +1729300,X-Square Capital LLC +1729303,WealthShield Partners LLC +1729304,Holistic Financial Partners +1729347,ABS Direct Equity Fund LLC +1729359,Townsend & Associates Inc +1729428,Monument Capital Management +1729443,Garrett Wealth Advisory Group LLC +1729457,Nova R Wealth Inc. +1729515,Aries Wealth Management +1729516,Bigelow Investment Advisors LLC +1729672,Global Trust Asset Management LLC +1729673,DDD Partners LLC +1729677,LexAurum Advisors LLC +1729754,Uncommon Cents Investing LLC +1729755,Strategic Family Wealth Counselors L.L.C. +1729829,Qube Research & Technologies Ltd +1729847,Nikulski Financial Inc. +1729854,MainStreet Investment Advisors LLC +1729866,FIDUCIENT ADVISORS LLC +1729869,Allied Investment Advisors LLC +1729939,Cypress Capital LLC +1729985,Virtue Capital Management LLC +1730033,Crewe Advisors LLC +1730073,PFA Pension Forsikringsaktieselskab +1730126,Buckley Wealth Management LLC +1730145,Voss Capital LP +1730149,Clarus Wealth Advisors +1730299,Banco de Sabadell S.A +1730383,Chesapeake Wealth Management +1730386,RETIREMENT INCOME SOLUTIONS INC +1730456,CAPITAL WEALTH MANAGEMENT LLC +1730464,Nan Shan Life Insurance Co. Ltd. +1730467,LONE PEAK GLOBAL INVESTORS LLC +1730469,Georgetown University +1730477,Ballast Inc. +1730478,Ironvine Capital Partners LLC +1730479,Independence Bank of Kentucky +1730511,Bedel Financial Consulting Inc. +1730521,Murphy Middleton Hinkle & Parker Inc. +1730525,Maj Invest Holding A/S +1730546,Luken Investment Analytics LLC +1730565,UNIO CAPITAL LLC +1730573,Requisite Capital Management LLC +1730575,Midwest Professional Planners LTD. +1730578,Vanguard Capital Wealth Advisors +1730580,Harbor Island Capital LLC +1730610,Acorn Wealth Advisors LLC +1730630,Investors Research Corp +1730660,Keeler Thomas Management LLC +1730765,Triumph Capital Management +1730769,Solstein Capital LLC +1730774,Rinkey Investments +1730810,Legacy Financial Advisors Inc. +1730813,FINANCIAL LIFE ADVISORS +1730814,Providence Capital Advisors LLC +1730815,Poehling Capital Management INC. +1730817,180 WEALTH ADVISORS LLC +1730818,Baugh & Associates LLC +1730889,Vigilare Wealth Management +1730896,United Super Pty Ltd in its capacity as Trustee for the Construction & Building Unions Superannuation Fund +1730942,Waterloo Capital L.P. +1730945,Congress Park Capital LLC +1730959,Santori & Peters Inc. +1730960,Sound Income Strategies LLC +1730961,SCP Investment LP +1730962,ODonnell Financial Services LLC +1731012,Alphinity Investment Management Pty Ltd +1731061,Larson Financial Group LLC +1731123,Biltmore Family Office LLC +1731124,DIAMANT ASSET MANAGEMENT INC. +1731132,Game Creek Capital LP +1731134,MayTech Global Investments LLC +1731152,James Hambro & Partners LLP +1731169,Financial Partners Group Inc +1731216,Martin Capital Partners LLC +1731221,FSA WEALTH PARTNERS INC. +1731260,Aljian Capital Management LLC +1731358,All Terrain Financial Advisors LLC +1731359,Seneca House Advisors +1731372,Kendall Capital Management +1731444,MGO ONE SEVEN LLC +1731445,Towerpoint Wealth LLC +1731446,Prime Capital Investment Advisors LLC +1731447,Leonard Rickey Investment Advisors P.L.L.C. +1731448,G&S Capital LLC +1731497,Ellenbecker Investment Group +1731601,Alaska Wealth Advisors LLC +1731671,Altman Advisors Inc. +1731717,D.B. Root & Company LLC +1731731,SWS Partners +1731732,Certified Advisory Corp +1731795,Inlight Wealth Management LLC +1731876,Steigerwald Gordon & Koch Inc. +1731878,Paulson Wealth Management Inc. +1731927,Elmwood Wealth Management Inc. +1732008,GUARDCAP ASSET MANAGEMENT Ltd +1732074,CNB Bank +1732537,TLWM +1732539,Optimus Prime Fund Management Co. Ltd. +1732541,Defiance ETFs LLC +1732768,Lingotto Investment Management LLP +1732949,MRWM Advisors LLC +1733082,Rossmore Private Capital +1733173,Howard Capital Management LLC +1733194,Sharp Wealth Advisory LLC +1733219,Stewardship Advisors LLC +1733472,Quaker Wealth Management LLC +1733755,Arnhold LLC +1733788,Centerpoint Advisors LLC +1734109,Winthrop Partners - WNY LLC +1734398,RED CEDAR INVESTMENT MANAGEMENT LLC +1734460,Stokes Capital Advisors LLC +1734493,NICOLA WEALTH MANAGEMENT LTD. +1735057,Versant Capital Management Inc +1735201,Leeward Financial Partners LLC +1735445,Rheos Capital Works Inc. +1735513,Claret Asset Management Corp +1735605,USAdvisors Wealth Management LLC +1735734,Wealth Alliance Advisory Group LLC +1736079,WALLER FINANCIAL PLANNING GROUP INC +1736225,ExodusPoint Capital Management LP +1736260,Cornell Pochily Investment Advisors Inc. +1736535,Sherman Asset Management Inc. +1736666,Signature Wealth Management Group +1736736,Rice Partnership LLC +1736982,Vestmark Advisory Solutions Inc. +1737012,Vermillion Wealth Management Inc. +1737088,Castleview Partners LLC +1737089,CX Institutional +1737090,FARMERS & MERCHANTS TRUST Co OF LONG BEACH +1737109,Integrated Wealth Concepts LLC +1737112,Lutz Financial Services LLC +1737871,LFA - Lugano Financial Advisors SA +1737888,Fulcrum Equity Management +1737917,Investment Insight Wealth Management LLC +1738560,ISLAY CAPITAL MANAGEMENT LLC +1738640,Aspiring Ventures LLC +1738720,Avondale Wealth Management +1738723,West Branch Capital LLC +1738726,Ceredex Value Advisors LLC +1738728,Silvant Capital Management LLC +1738738,Pinion Investment Advisors LLC +1738828,Pathway Financial Advisors LLC +1738902,Atom Investors LP +1739043,Winthrop Advisory Group LLC +1739439,Rockefeller Capital Management L.P. +1739485,LifePlan Financial LLC +1739728,CreativeOne Wealth LLC +1739877,Elo Mutual Pension Insurance Co +1739953,Windsor Advisory Group LLC +1740053,Chicago Capital LLC +1740063,Pflug Koory LLC +1740140,VISTA INVESTMENT MANAGEMENT +1740451,VanWeelden Wealth Management LLC +1740642,Grant Street Asset Management Inc. +1740839,GABLES CAPITAL MANAGEMENT INC. +1740842,Braun-Bostich & Associates Inc. +1741001,Distillate Capital Partners LLC +1741224,Aigen Investment Management LP +1741426,Quad-Cities Investment Group LLC +1741736,Financial Gravity Asset Management Inc. +1742315,J. L. Bainbridge & Co. Inc. +1742418,Dundas Partners LLP +1742435,FORA Capital LLC +1742647,Moerus Capital Management LLC +1742998,FNY Investment Advisers LLC +1743404,Foundations Investment Advisors LLC +1743413,Principle Wealth Partners LLC +1743859,Ariston Services Group +1743863,Peachtree Investment Partners LLC +1743937,TOMS Capital Investment Management LP +1743941,Elm Partners Management LLC +1744317,Beacon Pointe Advisors LLC +1744318,MinichMacGregor Wealth Management LLC +1744347,Vident Advisory LLC +1744348,Old Port Advisors +1744349,ELEVATION POINT WEALTH PARTNERS LLC +1744373,Machina Capital S.A.S. +1744955,Providence Wealth Advisors LLC +1745796,North Growth Management Ltd. +1745885,Dash Acquisitions Inc. +1745907,CloudAlpha Capital Management Limited/Hong Kong +1745945,Aquire Wealth Advisors LLC +1745981,WORLDQUANT MILLENNIUM ADVISORS LLC +1747057,D1 Capital Partners L.P. +1747749,Hurlow Wealth Management Group Inc. +1747799,Brio Consultants LLC +1748240,SOROS CAPITAL MANAGEMENT LLC +1748269,Sunesis Advisors LLC +1748271,Facet Wealth Inc. +1748278,JGP Wealth Management LLC +1748726,Watchman Group Inc. +1748728,Cox Capital Mgt LLC +1748766,Brand Asset Management Group Inc. +1748814,LITTLE HOUSE CAPITAL LLC +1748861,Bay Colony Advisory Group Inc d/b/a Bay Colony Advisors +1749283,Spartan Planning & Wealth Management +1749333,Infusive Asset Management Inc. +1749744,Global Retirement Partners LLC +1749768,Impax Asset Management Group plc +1749798,Boothe Investment Group Inc. +1749914,Insight Wealth Strategies LLC +1750086,HHM Wealth Advisors LLC +1750312,Hidden Lake Asset Management LP +1750405,Truvestments Capital LLC +1750557,Vectors Research Management LLC +1750585,OPTIMAS CAPITAL Ltd +1750852,Integris Wealth Management LLC +1750924,Balefire LLC +1750980,Farringdon Capital Ltd. +1751006,Chapman Investment Management LLC +1751412,Sepio Capital LP +1751581,Cooper Financial Group +1752045,ALKEME WEALTH LLC +1752212,Essex Bank +1752523,B&D White Capital Company LLC +1752758,Elwood & Goetz Wealth Advisory Group LLC +1752759,DNCA FINANCE +1752761,Centric Wealth Management +1752762,Next Capital Management LLC +1753218,MorganRosel Wealth Management LLC +1753219,United Capital Management of KS Inc. +1753271,Portfolio Strategies Inc. +1754535,DeepCurrents Investment Group LLC +1755535,One68 Global Capital LLC +1755622,Soviero Asset Management LP +1755651,Monte Financial Group LLC +1755670,Selective Wealth Management Inc. +1755784,TenCore Partners LP +1755785,Canton Hathaway LLC +1755911,Taikang Asset Management (Hong Kong) Co Ltd +1755933,Legacies Wealth LLC +1755987,Oak Thistle LLC +1756485,Independent Family Office LLC +1756543,RVW Wealth LLC +1756558,XN LP +1756695,Asset Advisors Investment Management LLC +1756759,Clarity Asset Management Inc. +1756959,McGuire Investment Group LLC +1756985,Hoya Capital Real Estate LLC +1757043,Delta Investment Management LLC +1757128,Laurel Wealth Advisors LLC +1757282,NKCFO LLC +1757605,EQ LLC +1757706,Benchmark Financial Wealth Advisors LLC +1758288,Targeted Financial Services LLC +1758440,Silphium Asset Management Ltd +1758543,Financial & Tax Architects LLC +1758720,Walleye Capital LLC +1759176,Pennant Investors LP +1759236,Black Diamond Financial LLC +1759271,Calydon Capital +1759320,Oxinas Partners LLC +1759354,KilterHowling LLC +1759395,MQS Management LLC +1759545,DWM Financial Group Inc. +1759578,Crumly & Associates Inc. +1759641,WestHill Financial Advisors Inc. +1759654,Quantinno Capital Management LP +1759751,Inlet Private Wealth LLC +1759760,H&H International Investment LLC +1759803,Graves Light Lenhart Wealth Inc. +1760076,Global Strategic Investment Solutions LLC +1760145,Costello Asset Management INC +1760263,Hamilton Wealth LLC +1760304,Great Point Wealth Advisors LLC +1760398,WELLINGTON-ALTUS USA INC. +1760401,MAIMON WEALTH MANAGEMENT LTD. +1760444,Old North State Trust LLC +1760540,Strategic Investment Advisors / MI +1760578,Pasadena Private Wealth LLC +1761013,Cresset Asset Management LLC +1761044,Compton Wealth Advisory Group LLC +1761054,Treasurer of the State of North Carolina +1761450,Valtinson Bruner Financial Planning LLC +1761755,TOWNSQUARE CAPITAL LLC +1761871,Hardy Reed LLC +1761961,ACIMA PRIVATE WEALTH LLC +1762068,MBM WEALTH CONSULTANTS LLC +1762086,Sage Financial Management Group Inc. +1762294,Evergreen Advisors LLC +1762539,Bonfire Financial +1762716,BURKETT FINANCIAL SERVICES LLC +1762718,Horiko Capital Management LLC +1763121,Evolution Wealth Advisors LLC +1763138,VERITY Wealth Advisors +1763146,Wahed Invest LLC +1763350,Main Street Financial Solutions LLC +1763404,Riverstone Advisors LLC +1763409,Cynosure Group LLC +1763454,Severin Investments LLC +1763722,Meridian Financial Partners LLC +1763844,CPV Partners LLC +1763921,Wealthfront Advisers LLC +1764000,Wilkinson Global Asset Management LLC +1764049,SlateStone Wealth LLC +1764057,Keystone Global Partners LLC +1764059,ELEMENT POINTE ADVISORS LLC +1764260,Maltin Wealth Management Inc. +1764386,Claro Advisors Inc. +1764387,Apollon Wealth Management LLC +1764581,CM WEALTH ADVISORS LLC +1764694,BRANDYWINE OAK PRIVATE WEALTH LLC +1764725,Blue Grotto Capital LLC +1764754,Geneos Wealth Management Inc. +1764756,Johns Hopkins University +1764766,SilverOak Wealth Management LLC +1764807,New Age Alpha Advisors LLC +1764968,Advisory Resource Group +1764970,Kozak & Associates Inc. +1765216,Magnus Financial Group LLC +1765387,Concentrum Wealth Management +1765388,Metropolis Capital Ltd +1765515,S.E.E.D. Planning Group LLC +1765536,Summit Financial LLC +1765590,Ellis Investment Partners LLC +1765594,CRA Financial Services LLC +1765595,Bernardo Wealth Planning LLC +1765617,IFS Advisors LLC +1765681,Thrive Capital Management LLC +1765690,MONECO ADVISORS LLC +1765774,No Street GP LP +1765876,MMBG INVESTMENT ADVISORS CO. +1765885,Allred Capital Management LLC +1766005,PRUDENT INVESTORS NETWORK INC. +1766067,TRUEFG LLC +1766150,EASTERLY INVESTMENT PARTNERS LLC +1766156,Salomon & Ludwin LLC +1766157,Sargent Investment Group LLC +1766159,Qsemble Capital Management LP +1766228,Legacy Capital Wealth Partners LLC +1766286,HOWARD WEALTH MANAGEMENT LLC +1766328,Signet Investment Advisory Group Inc. +1766504,GREENLEA LANE CAPITAL MANAGEMENT LLC +1766509,FORTEM FINANCIAL GROUP LLC +1766514,RHS Financial LLC +1766530,Gladstone Institutional Advisory LLC +1766564,Trivium Point Advisory LLC +1766571,Pacific Wealth Strategies Group Inc. +1766596,RV Capital AG +1766791,Grandview Asset Management LLC +1766806,Shaolin Capital Management LLC +1766883,Lantz Financial LLC +1766904,CARY STREET PARTNERS INVESTMENT ADVISORY LLC +1766907,Act Two Investors LLC +1766908,ShawSpring Partners LLC +1766918,Opes Wealth Management LLC +1766929,Defender Capital LLC. +1766995,SUMMIT WEALTH & RETIREMENT PLANNING INC. +1767040,BROOKS MOORE & ASSOCIATES INC. +1767049,Jackson Hole Capital Partners LLC +1767062,Hobart Private Capital LLC +1767070,Shulman DeMeo Asset Management LLC +1767080,Abundance Wealth Counselors +1767107,GARRISON POINT ADVISORS LLC +1767121,Hillcrest Wealth Advisors - NY LLC +1767151,Outlook Wealth Advisors LLC +1767217,TLW Wealth Management LLC +1767297,VeraBank N.A. +1767306,Vanguard Personalized Indexing Management LLC +1767307,Robertson Stephens Wealth Management LLC +1767313,WealthBridge Capital Management LLC +1767340,Aspire Private Capital LLC +1767343,DAGCO INC. +1767349,Princeton Global Asset Management LLC +1767384,Clarity Wealth Advisors LLC +1767433,Ferguson Shapiro LLC +1767435,Artemis Wealth Advisors LLC +1767457,VIEWPOINT INVESTMENT PARTNERS CORP +1767471,OSSIAM +1767474,MA Private Wealth +1767500,Nalls Sherbakoff Group LLC +1767513,Human Investing LLC +1767559,W1M Asset Management Ltd +1767580,Advisor OS LLC +1767601,MARKET STREET WEALTH MANAGEMENT ADVISORS LLC +1767617,RESTON WEALTH MANAGEMENT LLC +1767640,PUBLIC INVESTMENT FUND +1767686,Yarbrough Capital LLC +1767699,ALTERNA WEALTH MANAGEMENT INC +1767710,Guidance Point Advisors LLC +1767724,New World Advisors LLC +1767730,Frisch Financial Group Inc. +1767735,Palmer Knight Co +1767750,Noked Israel Ltd +1767812,Miramar Capital LLC +1767821,Strategic Blueprint LLC +1767843,MIROVA +1767855,Global Wealth Management Investment Advisory Inc. +1767868,Inscription Capital LLC +1767898,American Financial Advisors LLC +1767902,Western Wealth Management LLC +1767940,Wolff Financial Management LLC +1767945,OSTRUM ASSET MANAGEMENT +1767982,Omega Financial Group LLC +1767989,Bay Harbor Wealth Management LLC +1768065,Brendel Financial Advisors LLC +1768089,Knuff & Co LLC +1768095,Meridian Wealth Advisors LLC +1768099,Qtron Investments LLC +1768130,HOWARD FINANCIAL SERVICES LTD. +1768195,Trek Financial LLC +1768302,ERn Financial LLC +1768635,O'Brien Greene & Co. Inc +1768744,Munro Partners +1768824,TLS Advisors LLC +1769060,Tull Financial Group Inc. +1769063,Foster Victor Wealth Advisors LLC +1769089,Arkos Global Advisors +1769288,Atwater Malick LLC +1769302,LIBERTY WEALTH MANAGEMENT LLC +1769578,BLUE SQUARE ASSET MANAGEMENT LLC +1769646,TWINBEECH CAPITAL LP +1769704,PERRY CREEK CAPITAL LP +1769897,GREAT VALLEY ADVISOR GROUP INC. +1770532,Core Wealth Advisors Inc. +1770632,Quilter Plc +1770940,CMC Financial Group +1770994,Occidental Asset Management LLC +1771122,Rings Capital Management LLC +1771169,CCG WEALTH MANAGEMENT LLC +1771605,SAGE RHINO CAPITAL LLC +1771687,KG Capital Management +1772031,LAKE STREET PRIVATE WEALTH LLC +1772483,Legacy Trust +1772715,Prestige Wealth Management Group LLC +1772875,Y-Intercept (Hong Kong) Ltd +1772937,Tolleson Wealth Management Inc. +1772954,Ameraudi Asset Management Inc. +1773205,KMG FIDUCIARY PARTNERS LLC +1773368,Wesleyan Assurance Society +1773830,PATRIOT INVESTMENT MANAGEMENT GROUP INC. +1774086,RDA Financial Network +1774087,McAlister Sweet & Associates Inc. +1774207,Jupiter Wealth Management LLC +1774343,Financial Strategies Group Inc. +1774437,Your Advocates Ltd. LLP +1774744,Avantra Family Wealth Inc. +1774879,Cornerstone Wealth Group LLC +1775210,Waterfront Wealth Inc. +1775391,Joseph P. Lucia & Associates LLC +1775446,Cornerstone Advisors LLC +1775715,MILFORD FUNDS LTD +1775850,MBE Wealth Management LLC +1776023,Stony Point Capital LLC +1776033,Golden State Wealth Management LLC +1776074,Hi-Line Capital Management LLC +1776082,TRANSCEND CAPITAL ADVISORS LLC +1776290,Zhang Financial LLC +1776296,RBA Wealth Management LLC +1776588,MONOGRAPH WEALTH ADVISORS LLC +1776757,WADDELL & ASSOCIATES LLC +1776792,Realta Investment Advisors +1776821,Spectrum Wealth Advisory Group LLC +1776878,Core Alternative Capital +1776910,KDK Private Wealth Management LLC +1777271,Sanctuary Advisors LLC +1777469,E Fund Management (Hong Kong) Co. Ltd. +1777734,Wishbone Management LP +1777813,Atreides Management LP +1777817,Leelyn Smith LLC +1777914,Astoria Strategic Wealth Inc. +1778131,BI Asset Management Fondsmaeglerselskab A/S +1779040,PSI Advisors LLC +1779355,PENNINGTON PARTNERS & CO. LLC +1779506,Brandywine Financial Group +1779789,IEQ CAPITAL LLC +1780055,Weaver Consulting Group +1780330,COLTON GROOME FINANCIAL ADVISORS LLC +1780365,WT Asset Management Ltd +1780507,WT Wealth Management +1780565,O'Keefe Stevens Advisory Inc. +1780570,Ethic Inc. +1780700,Osmosis Investment Management UK Ltd +1780985,OneAscent Financial Services LLC +1781284,Curated Wealth Partners LLC +1781880,NAN FUNG TRINITY (HK) LTD +1781882,Socorro Asset Management LP +1781919,Core Capital Management & Research inc. +1781942,Meridian Wealth Partners LLC +1781948,Legend Financial Advisors Inc. +1782491,Jacobsen Capital Management +1782624,HEMMING& WEALTH MANAGEMENT INC. +1783139,III Capital Management +1783412,IFG Advisors LLC +1783599,Red Door Wealth Management LLC +1783773,Tranquility Partners LLC +1784093,Beacon Harbor Wealth Advisors Inc. +1784235,Clear Creek Financial Management LLC +1784277,Matrix Trust Co +1784418,Osbon Capital Management LLC +1784547,Woodline Partners LP +1784777,Sound View Wealth Advisors Group LLC +1785144,PETERSON WEALTH MANAGEMENT +1785342,National Philanthropic Trust +1785445,tru Independence LLC +1785498,Keudell/Morrison Wealth Management +1785545,ESL Trust Services LLC +1785717,ORSER CAPITAL MANAGEMENT LLC +1786241,HILLTOP WEALTH ADVISORS LLC +1786379,Stonehage Fleming Financial Services Holdings Ltd +1786411,Carolina Wealth Advisors LLC +1787027,Private Wealth Advisors LLC +1787125,TBH Global Asset Management LLC +1787258,Cinctive Capital Management LP +1787274,Sierra Capital LLC +1787596,Aperture Investors LLC +1787663,RIVERSEDGE ADVISORS LLC +1787893,BWCP LP +1788587,Williams Jones Wealth Management LLC. +1789082,Crake Asset Management LLP +1789219,Charles Schwab Trust Co +1789310,Auour Investments LLC +1789351,Deseret Mutual Benefit Administrators +1789382,Arkfeld Wealth Strategies L.L.C. +1790295,DELTA FINANCIAL ADVISORS LLC +1790525,HBW ADVISORY SERVICES LLC +1790548,Beck Bode LLC +1790604,Solel Partners LP +1790688,Capital Planning LLC +1790837,Bull Street Advisors LLC +1791002,Xcel Wealth Management LLC +1791126,Triton Wealth Management PLLC +1791253,Praxis Capital Management LLC +1791555,Hubbell Strickland Wealth Management LLC +1791965,Kingsview Wealth Management LLC +1791996,1900 WEALTH MANAGEMENT LLC +1791998,Net Worth Advisory Group +1792167,Meeder Advisory Services Inc. +1792283,Venture Visionary Partners LLC +1792397,Gunderson Capital Management LLC +1792430,Karani Asset Management LLC +1792565,Pavion Blue Capital LLC +1792704,Avion Wealth +1792851,Schorn Wealth LLC +1793269,Ranch Capital Advisors Inc. +1793399,Winning Points Advisors LLC +1793432,EVOKE WEALTH LLC +1793691,Relyea Zuckerberg Hanson LLC +1793755,Banque Cantonale Vaudoise +1793904,WEALTH ALLIANCE LLC +1793923,WASHBURN CAPITAL MANAGEMENT INC. +1794153,SCHWARZ DYGOS WHEELER INVESTMENT ADVISORS LLC +1794467,Blueprint Investment Partners LLC +1794499,Hyperion Capital Advisors LP +1794543,Kathmere Capital Management LLC +1794820,ICW Investment Advisors LLC +1794935,Heartwood Wealth Advisors LLC +1794972,BANTAMAC CAPITAL LLC +1795097,Privium Fund Management B.V. +1795173,Physician Wealth Advisors Inc. +1795356,Red Spruce Capital LLC +1795552,Maryland State Retirement & Pension System +1795594,Kirkoswald Asset Management LLC +1795705,Brooklyn Investment Group +1795934,CBIZ Investment Advisory Services LLC +1796409,Heard Capital LLC +1796874,VERUS CAPITAL PARTNERS LLC +1797135,ARBOR TRUST WEALTH ADVISORS LLC +1797678,Americana Partners LLC +1797873,Lineweaver Wealth Advisors LLC +1798150,Avalon Trust Co +1798172,BCS Wealth Management +1798221,Professional Financial Advisors LLC +1798485,Aurora Investment Managers LLC. +1798686,Kapitalo Investimentos Ltda +1798736,Capital Square LLC +1798756,Beaumont Asset Management L.L.C. +1798923,LRT Capital Management LLC +1798924,Bay Rivers Group +1798926,John W. Brooker & Co. CPAs +1798986,Cedar Mountain Advisors LLC +1799006,Horizon Advisory Services Inc. +1799284,Modus Advisors LLC +1799367,GPM Growth Investors Inc. +1799425,WealthBridge Investment Counsel Inc. +1799435,EPIQ PARTNERS LLC +1799544,HOEY INVESTMENTS INC +1799677,Detalus Advisors LLC +1799719,Paragon Advisors LLC +1799797,Provident Wealth Management LLC +1799802,Comprehensive Financial Management LLC +1799859,Birch Capital Management LLC +1799877,Live Oak Private Wealth LLC +1799880,Emergent Wealth Advisors LLC +1799900,Pacifica Partners Inc. +1799957,PFG Private Wealth Management LLC +1799964,Firestone Capital Management +1800135,PYA Waltman Capital LLC +1800158,Sunburst Financial Group LLC +1800217,Resolute Advisors LLC +1800234,ATTICUS WEALTH MANAGEMENT LLC +1800245,NATURAL INVESTMENTS LLC +1800249,Blacksheep Fund Management Ltd +1800358,Cairn Investment Group Inc. +1800379,SkyOak Wealth LLC +1800465,XML Financial LLC +1800502,Beacon Financial Advisory LLC +1800508,General Partner Inc. +1800513,TSFG LLC +1800533,Lionsbridge Wealth Management LLC +1800556,KFA Private Wealth Group LLC +1800586,Briaud Financial Planning Inc +1800597,First Citizens Financial Corp +1800608,Laidlaw Wealth Management LLC +1800620,Sound Financial Strategies Group LLC +1800641,Bornite Capital Management LP +1800687,Symphony Financial Ltd. Co. +1800692,Secure Asset Management LLC +1800745,R. W. Roge & Company Inc. +1800752,CERTUITY LLC +1800798,Arkadios Wealth Advisors +1800911,S.A. Mason LLC +1800913,SPRENG CAPITAL MANAGEMENT INC. +1800916,Value Partners Investments Inc. +1800938,One Charles Private Wealth Services LLC +1801097,CFM WEALTH PARTNERS LLC +1801101,LPWM LLC +1801107,Meadow Creek Wealth Advisors LLC +1801112,Great Diamond Partners LLC +1801145,Bank of Marin +1801172,11 Capital Partners LP +1801184,Single Point Partners LLC +1801212,Formuepleje A/S +1801263,Parcion Private Wealth LLC +1801373,LifeSteps Financial Inc. +1801413,Summit Place Financial Advisors LLC +1801467,AXS Investments LLC +1801507,Apella Capital LLC +1801523,Perennial Investment Advisors LLC +1801547,Blue Whale Capital LLP +1801563,Ayrshire Capital Management LLC +1801573,Aufman Associates Inc +1801583,Mission Creek Capital Partners Inc. +1801585,AMJ Financial Wealth Management +1801619,BELLEVUE ASSET MANAGEMENT LLC +1801667,Great Lakes Retirement Inc. +1801674,WJ Wealth Management LLC +1801720,Key Financial Inc +1801792,Chronos Wealth Management LLC +1801846,Apeiron RIA LLC +1801868,SteelPeak Wealth LLC +1801876,Royal Harbor Partners LLC +1801892,Future Financial Wealth Managment LLC +1801926,Mayfair Advisory Group LLC +1801982,Bridgewealth Advisory Group LLC +1801989,Cardinal Strategic Wealth Guidance +1802059,Centerline Wealth Advisors LLC +1802080,TOBIAS FINANCIAL ADVISORS INC. +1802084,CVA Family Office LLC +1802091,Marotta Asset Management +1802105,Cornerstone Planning LLC +1802107,Altus Wealth Management LLC +1802119,Avalon Advisory Group +1802132,Aurora Private Wealth Inc. +1802136,Investment Research & Advisory Group Inc. +1802167,Fortis Capital Management LLC +1802195,Powell Investment Advisors LLC +1802224,JSF Financial LLC +1802244,Indie Asset Partners LLC +1802278,Stokes Family Office LLC +1802279,Hummer Financial Advisory Services Inc +1802284,TI-TRUST INC +1802290,SENTINEL PENSION ADVISORS LLC +1802324,360 Financial Inc. +1802361,MAGNOLIA CAPITAL MANAGEMENT LTD +1802365,Strategic Wealth Investment Group LLC +1802376,Mattern Wealth Management LLC +1802387,Elevated Capital Advisors LLC +1802451,HighMark Wealth Management LLC +1802459,CWS Financial Advisors LLC +1802473,Marks Group Wealth Management Inc +1802474,Lloyd Advisory Services LLC. +1802493,DCF Advisers LLC +1802494,Stonebridge Financial Planning Group LLC +1802496,QP WEALTH MANAGEMENT LLC +1802530,Soltis Investment Advisors LLC +1802533,HOHIMER WEALTH MANAGEMENT LLC +1802534,ABSHER WEALTH MANAGEMENT LLC +1802539,M&G PLC +1802611,Emerald Advisors LLC +1802635,One Wealth Map LLC +1802654,Aberdeen Wealth Management LLC +1802655,Texas Capital Bank Wealth Management Services Inc +1802691,McNaughton Wealth Management LLC +1802696,Alhambra Investment Management LLC +1802743,Element Wealth LLC +1802816,CLIENT 1ST ADVISORY GROUP LLC +1802865,Summit Wealth Group LLC +1802868,TL Private Wealth +1802879,Houlihan Financial Resource Group Ltd. +1802882,HARRELL INVESTMENT PARTNERS LLC +1802891,Verde Capital Management +1802900,Mirova US LLC +1802952,Game Plan Financial Advisors LLC +1802955,Fusion Capital LLC +1802961,Optas LLC +1802984,Visionary Wealth Advisors +1802994,Mine & Arao Wealth Creation & Management LLC. +1803005,Wealth Advisors of Iowa LLC +1803054,NWK Group Inc. +1803058,EPG Wealth Management LLC +1803084,Adams Wealth Management +1803106,Blue Zone Wealth Advisors LLC +1803140,1776 Wealth LLC +1803149,Mizuho Markets Cayman LP +1803156,Menard Financial Group LLC +1803227,MATTERN CAPITAL MANAGEMENT LLC +1803236,Resonant Capital Advisors LLC +1803253,Soundwatch Capital LLC +1803255,Petix & Botte Co +1803277,Capital Wealth Alliance LLC +1803291,Asio Capital LLC +1803295,HC Advisors LLC +1803296,WNY Asset Management LLC +1803329,Regent Peak Wealth Advisors LLC +1803386,Wealth Quarterback LLC +1803397,Sculati Wealth Management LLC +1803415,BALLAST ADVISORS LLC +1803426,BEAM WEALTH ADVISORS INC. +1803456,N.E.W. Advisory Services LLC +1803519,Aprio Wealth Management LLC +1803523,Pacific Capital Wealth Advisors Inc. +1803536,Cadent Capital Advisors LLC +1803557,Center for Financial Planning Inc. +1803566,MADDEN SECURITIES Corp +1803662,MAGNOLIA CAPITAL ADVISORS LLC +1803673,PAX Financial Group LLC +1803675,Keebeck Wealth Management +1803804,Archer Investment Corp +1803848,RMR Wealth Builders +1803898,Seven Springs Wealth Group LLC +1803916,Aquatic Capital Management LLC +1803980,Bright Futures Wealth Management LLC. +1803988,Trust Co of Kansas +1803994,Running Oak Capital LLC +1804116,KOM Wealth Management Group LLC +1804329,Procyon Advisors LLC +1804352,PING CAPITAL MANAGEMENT INC. +1804909,Total Clarity Wealth Management Inc. +1805250,CPC Advisors LLC +1805370,Cannon Advisors Inc. +1805603,TABR Capital Management LLC +1805754,swisspartners Advisors Ltd +1805824,American Institute for Advanced Investment Management LLP +1806027,Aspen Grove Capital LLC +1806366,Westshore Wealth LLC +1806425,Citizens National Bank Trust Department +1806428,Tempus Wealth Planning LLC +1806752,Hixon Zuercher LLC +1806755,Systematic Alpha Investments LLC +1806820,AWM CAPITAL LLC +1807060,SHP Wealth Management +1807270,Luminist Capital LLC +1807283,High Note Wealth LLC +1807288,SATOVSKY ASSET MANAGEMENT LLC +1807328,LIBERTY ONE INVESTMENT MANAGEMENT LLC +1807559,Amitell Capital Pte Ltd +1807909,waypoint wealth counsel +1808027,Copper Harbor Investment Advisors LLC +1808163,Zeit Capital LLC +1808179,COLUMBIA ADVISORY PARTNERS LLC +1808195,Stolper Co +1808389,Delta Accumulation LLC +1808394,Flower City Capital +1808523,Mosaic Advisors LLC +1808696,UNTITLED INVESTMENTS LP +1808748,Davidson Capital Management Inc. +1808915,U.S. Capital Wealth Advisors LLC +1808919,Hunter Perkins Capital Management LLC +1808928,Cartenna Capital LP +1808992,Revolve Wealth Partners LLC +1809154,Argos Wealth Advisors LLC +1809236,Salvus Wealth Management LLC +1809416,Main Line Retirement Advisors LLC +1809494,Standard Family Office LLC +1809525,IvyRock Asset Management (HK) Ltd +1809574,Trinity Financial Advisors LLC +1810023,Affinity Capital Advisors LLC +1810089,First Pacific Financial +1810099,Royal Fund Management LLC +1810555,RTD Financial Advisors Inc. +1810558,Inspire Advisors LLC +1810720,Charter Oak Capital Management LLC +1810873,Elequin Capital LP +1811005,Veracity Capital LLC +1811052,REBALANCE LLC +1811240,Souders Financial Advisors +1811242,Vanguard Global Advisers LLC +1811308,Wellspring Financial Advisors LLC +1811345,Rothschild Wealth LLC +1811472,I.G.Y. Ltd +1811491,Auxano Advisors LLC +1811522,Sycale Advisors (NY) LLC +1811568,Investment Management Corp of Ontario +1811739,Annandale Capital LLC +1811783,Richard P Slaughter Associates Inc +1811805,Triton Financial Group Inc +1811806,Davis Capital Management +1811827,ARQ WEALTH ADVISORS LLC +1811907,Citadel Investment Advisory Inc. +1812090,Vise Technologies Inc. +1812095,Sixth Street Partners Management Company L.P. +1812103,Rosenberg Matthew Hamilton +1812155,StrongBox Wealth LLC +1812177,WALLED LAKE PLANNING & WEALTH MANAGEMENT LLC +1812178,JACKSON SQUARE CAPITAL LLC +1812198,Aaron Wealth Advisors LLC +1812492,Disciplined Investments LLC +1812792,Intrua Financial LLC +1812853,Dynamic Wealth Strategies LLC +1813369,Financial Advisory Corp +1813454,Stone Wealth Partners +1813577,Whitcomb & Hess Inc. +1814104,THREADGILL FINANCIAL LLC +1814128,Kirkwood Financial Services +1814191,ACT WEALTH MANAGEMENT LLC +1814214,Concord Wealth Partners +1814234,Atlas Private Wealth Advisors +1815025,NovaPoint Capital LLC +1815123,Horizon Wealth Management LLC +1815183,Fortis Group Advisors LLC +1815217,NIA IMPACT ADVISORS LLC +1815355,Scarborough Advisors LLC +1816000,Northstar Advisory Group LLC +1816427,Crawford Fund Management LLC +1816444,Sara-Bay Financial +1816616,NZS Capital LLC +1817174,Prudent Man Advisors LLC +1817494,Walkner Condon Financial Advisors LLC +1817534,Anomaly Capital Management LP +1817648,ABSOLUTE CAPITAL MANAGEMENT LLC +1817693,Retirement Planning Co of New England Inc. +1817714,TrueWealth Advisors LLC +1817797,Seilern Investment Management Ltd +1818014,CYBER HORNET ETFs LLC +1818044,AF Advisors Inc. +1818160,Systrade AG +1818207,Horst & Graben Wealth Management LLC +1818386,NDWM LLC +1818391,Independent Solutions Wealth Management LLC +1818535,Carl P. Sherr & Co. LLC +1818557,Kinloch Capital LLC +1818604,INTERNATIONAL ASSETS INVESTMENT MANAGEMENT LLC +1818759,Sunflower Bank N.A. +1818897,AM INVESTMENT STRATEGIES LLC +1818940,Praetorian Wealth Management Inc. +1819476,Onyx Bridge Wealth Group LLC +1819581,CLOVERFIELDS CAPITAL GROUP LP +1819695,ONE Advisory Partners LLC +1819697,OCCUDO QUANTITATIVE STRATEGIES LP +1819815,Bellwether Advisors LLC +1819919,PACES FERRY WEALTH ADVISORS LLC +1819955,Intelligence Driven Advisers LLC +1820593,Navalign LLC +1820680,BLUESTEM FINANCIAL ADVISORS LLC +1820681,Snider Financial Group +1820879,Advisor Resource Council +1821168,LOUNTZIS ASSET MANAGEMENT LLC +1821336,Mechanics Financial Corp +1821406,Macro Advisors Inc. +1821407,WFA of San Diego LLC +1821549,BNC WEALTH MANAGEMENT LLC +1821561,INCEPTIONR LLC +1821626,State of Wyoming +1821984,Rockbridge Investment Management LCC +1822236,KWB Wealth +1822262,Live Oak Investment Partners +1822587,Cornerstone Planning Group LLC +1822632,Gleason Group Inc. +1823172,One Day In July LLC +1823823,Hiddenite Capital Partners LP +1824263,Bull Oak Capital LLC +1824539,Impact Investors Inc +1824694,Signature Wealth Management Partners LLC +1824700,Capital Advisors Wealth Management LLC +1825214,Ghisallo Capital Management LLC +1825292,IAM Advisory LLC +1825516,MIZUHO MARKETS AMERICAS LLC +1825611,Cypress Point Wealth Management LLC +1825985,Vanderbilt University +1826136,Berger Financial Group Inc +1826394,Plotkin Financial Advisors LLC +1826790,Tennessee Valley Asset Management Partners +1827261,IAG Wealth Partners LLC +1827442,Alua Capital Management LP +1827734,FERNBRIDGE CAPITAL MANAGEMENT LP +1827844,TFC Financial Management Inc. +1828064,AMIRAL GESTION +1828301,XTX Topco Ltd +1828808,Oslo Pensjonsforsikring AS +1828822,XXEC Inc. +1829231,FWL INVESTMENT MANAGEMENT LLC +1829271,Stirlingshire Investments Inc. +1830008,Capital CS Group LLC +1830103,Safir Wealth Advisors LLC +1830467,BCGM Wealth Management LLC +1830731,Attestor Capital Ltd +1830817,Platform Technology Partners +1830819,Kestra Advisory Services LLC +1830823,eCIO Inc. +1830922,CHIRON CAPITAL MANAGEMENT LLC +1830942,Cordant Inc. +1831003,Jackson Creek Investment Advisors LLC +1831132,Windmill Hill Asset Management Ltd +1831187,44 WEALTH MANAGEMENT LLC +1831193,CATALYST PRIVATE WEALTH LLC +1831263,JEPPSON WEALTH MANAGEMENT LLC +1831316,Rise Advisors LLC +1831332,Planned Solutions Inc. +1831416,MTM Investment Management LLC +1831542,Hexagon Capital Partners LLC +1831577,Jump Financial LLC +1831984,Beacon Bank & Trust +1831985,Axiom Financial Strategies LLC +1832093,Baron Financial Group LLC +1832097,WOLFF WIESE MAGANA LLC +1832158,SEAVIEW INVESTMENT MANAGERS LLC +1832190,Founders Financial Alliance LLC +1832237,Te Ahumairangi Investment Management Ltd +1832274,Sage Mountain Advisors LLC +1832439,ATMOS CAPITAL GESTAO DE RECURSOS LTDA. +1832521,Castellan Group +1833140,McLean Asset Management Corp +1833567,Volterra Technologies LP +1834011,Legacy Financial Group LLC +1834438,JAT Capital Mgmt LP +1834499,F/m Investments LLC +1834780,Tabor Asset Management LP +1834802,Wealth Advisory Solutions LLC +1834874,Audent Global Asset Management LLC +1834913,Resolute Capital Asset Partners LLC +1834985,TruWealth Advisors LLC +1835206,Gimbal Financial +1835252,EWA LLC +1835669,Crew Capital Management Ltd +1835730,Ardmore Road Asset Management LP +1835751,CastleKnight Management LP +1836110,Enclave Advisors LLC +1836266,David J Yvars Group +1836506,MITCHELL & PAHL PRIVATE WEALTH LLC +1837309,Polar Capital Holdings Plc +1837320,Prospect Hill Management LLC +1837496,Militia Capital Partners LP +1838211,Venator Management LLC +1838222,Maxi Investments CY Ltd +1838226,Accel Wealth Management +1838556,Murchinson Ltd. +1838615,AlTi Global Inc. +1838660,Marks Wealth LLC +1839122,Bouvel Investment Partners LLC +1839255,SWMG LLC +1839307,GoalVest Advisory LLC +1839421,Centennial Wealth Advisory LLC +1839430,Oak Harvest Investment Services +1839445,CMG Global Holdings LLC +1839498,DCM Advisors LLC +1839545,GraniteShares Advisors LLC +1839695,Prairiewood Capital LLC +1839735,Blossom Wealth Management +1839850,Falcon Wealth Planning +1839851,IVY LANE CAPITAL MANAGEMENT LLC +1839890,Solitude Financial Services +1840014,Schiavi & Co LLC +1840084,Brown Miller Wealth Management LLC +1840085,Journey Advisory Group LLC +1840268,Hoese & Co LLP +1840341,Birchcreek Wealth Management LLC +1840455,Kesler Norman & Wride LLC +1840486,Harbor Group Inc. +1840501,Hudson Value Partners LLC +1840565,BAKER TILLY WEALTH MANAGEMENT LLC +1840735,GREENOAKS LLC +1840740,BENNETT SELBY INVESTMENTS LP +1840750,Stenham Asset Management Ltd +1840755,Unison Asset Management LLC +1840760,Olde Wealth Management LLC +1840775,4J Wealth Management LLC +1840888,Roth Financial Partners LLC +1840945,Galvin Gaustad & Stein LLC +1841015,WD RUTHERFORD LLC +1841173,JERICHO FINANCIAL LLP +1841259,Octavia Wealth Advisors LLC +1841496,Marshall Financial Group LLC +1841506,CGN Advisors LLC +1841544,Foster Group Inc. +1841633,ForthRight Wealth Management LLC +1841659,DV EQUITIES LLC +1841757,WEBSTERROGERS FINANCIAL ADVISORS LLC +1841766,KAVAR CAPITAL PARTNERS GROUP LLC +1841768,PATRON PARTNERS LLC +1841769,Legal Advantage Investments Inc. +1841815,HUB Investment Partners LLC +1841816,MRA Advisory Group +1841857,CM Management LLC +1841979,Sightline Wealth Advisors LLC +1842010,Laurel Wealth Planning LLC +1842013,Kaizen Financial Strategies +1842015,Sterling Manor Financial LLC +1842054,Rede Wealth LLC +1842089,STABLEFORD CAPITAL II LLC +1842149,Heron Bay Capital Management +1842296,E Fund Management Co. Ltd. +1842357,Choice Wealth Advisors LLC +1842361,JONES ROAD CAPITAL MANAGEMENT L.P. +1842362,Phoenix Wealth Advisors +1842370,M. Kulyk & Associates LLC +1842509,Alcosta Capital Management Inc. +1842526,Proem Advisors LLC +1842554,ACT Advisors LLC. +1842557,Tandem Wealth Advisors LLC +1842560,Flagship Private Wealth LLC +1842572,Altus Wealth Group LLC +1842665,Halter Ferguson Financial Inc. +1842667,MMA ASSET MANAGEMENT LLC +1842669,Cerro Pacific Wealth Advisors LLC +1842702,Navera Investment Management Ltd. +1842766,Avory & Company LLC +1842787,Newton One Investments LLC +1842811,IVC Wealth Advisors LLC +1842881,Granger Management LLC +1842974,McClarren Financial Advisors Inc. +1843010,Tradition Wealth Management LLC +1843111,APEIRON CAPITAL Ltd +1843169,McCarthy Asset Management Inc. +1843253,SPG ADVISORS LLC +1843275,TKG Advisors LLC +1843292,Prosperity Planning Inc. +1843294,Wealth Management Partners LLC +1843309,PEAK FINANCIAL ADVISORS LLC +1843358,Mach-1 Financial Group LLC +1843492,Campion Asset Management +1843495,Bond & Devick Financial Network Inc. +1843553,Ellsworth Advisors LLC +1843566,Greenhouse Wealth Management LLC +1843578,Strategic Equity Management +1843581,OBSIDIAN CIO LLC +1843684,West Financial Advisors LLC +1843715,Latitude Advisors LLC +1843745,Addison Advisors LLC +1843826,Alta Wealth Advisors LLC +1843832,Skyline Advisors Inc. +1843848,TriaGen Wealth Management LLC +1843853,Brown Financial Advisory +1843867,Curran Financial Partners LLC +1844024,Oakwell Private Wealth Management LLC +1844107,Elk River Wealth Management LLC +1844108,Bluesphere Advisors LLC +1844142,Goepper Burkhardt LLC +1844147,Rye Brook Capital LLC +1844148,Allegheny Financial Group +1844197,Precision Wealth Strategies LLC +1844201,Opal Wealth Advisors LLC +1844227,Orin Green Financial LLC +1844238,Johnson Bixby & Associates LLC +1844250,Purus Wealth Management LLC +1844266,Taylor & Morgan Wealth Management LLC +1844278,Sterling Financial Planning Inc. +1844314,Montis Financial LLC +1844345,Evolutionary Tree Capital Management LLC +1844369,Destiny Wealth Partners LLC +1844375,Veery Capital LLC +1844393,Financial Avengers Inc. +1844424,Madison Wealth Partners Inc +1844444,Accretive Wealth Partners LLC +1844480,COWA LLC +1844567,Compton Financial Group LLC +1844568,Clarus Group Inc. +1844571,Rempart Asset Management Inc. +1844707,OneAscent Wealth Management LLC +1844709,San Luis Wealth Advisors LLC +1844716,DHJJ Financial Advisors Ltd. +1844731,RMG Wealth Management LLC +1844830,Lakehouse Capital Pty Ltd +1844831,FINANCIAL MANAGEMENT NETWORK INC +1844835,CORRECT CAPITAL WEALTH MANAGEMENT +1844873,WAYCROSS PARTNERS LLC +1844878,Center For Asset Management LLC +1844880,Insight Advisors LLC/ PA +1844892,Sandbox Financial Partners LLC +1844893,Grand Central Investment Group +1844897,Cassia Capital Partners LLC +1844922,ShoreHaven Wealth Partners LLC +1845003,Ascentis Independent Advisors +1845031,McGinn Penninger Investment Management Inc. +1845066,COLLECTIVE FAMILY OFFICE LLC +1845081,Cohen Investment Advisors LLC +1845109,Oder Investment Management LLC +1845163,Team Financial Group LLC +1845199,CoreFirst Bank & Trust +1845210,RFG Holdings Inc. +1845250,Cadence Wealth Management LLC +1845251,Bison Wealth LLC +1845302,Greystone Financial Group LLC +1845373,Humankind Investments LLC +1845501,Long Corridor Asset Management Ltd +1845521,Urban Wealth Management LLC +1845531,Constitution Capital LLC +1845536,Kensington Investment Counsel LLC +1845635,SAGE PRIVATE WEALTH GROUP LLC +1845643,FOCUS Wealth Advisors LLC +1845675,Lifestyle Asset Management Inc. +1845688,CFS Investment Advisory Services LLC +1845698,Crown Wealth Group LLC +1845743,FIRETHORN WEALTH PARTNERS LLC +1845766,Lebenthal Global Advisors LLC +1845773,Privium Fund Management (UK) Ltd +1845785,MBL Wealth LLC +1845793,PGIM Custom Harvest LLC +1845838,COREPATH WEALTH PARTNERS LLC +1845843,VectorGlobal IAG Inc. +1845849,CONSULTIVA WEALTH MANAGEMENT CORP. +1845859,Colonial River Investments LLC +1845867,BCK Partners Inc. +1845915,Pine Ridge Advisers LLC +1845930,TITAN GLOBAL CAPITAL MANAGEMENT USA LLC +1845943,Thrive Capital Management LLC +1845950,ADE LLC +1845998,Interchange Capital Partners LLC +1846002,Oxler Private Wealth LLC +1846114,Boyd Wealth Management LLC +1846138,SITTNER & NELSON LLC +1846150,SAX WEALTH ADVISORS LLC +1846151,Legacy Wealth Asset Management LLC +1846160,AAFMAA Wealth Management & Trust LLC +1846161,OAK FAMILY ADVISORS LLC +1846175,Sovereign Financial Group Inc. +1846177,WOODWARD DIVERSIFIED CAPITAL LLC +1846236,True Link Financial Advisors LLC +1846287,Tacita Capital Inc +1846310,Palumbo Wealth Management LLC +1846311,Vancity Investment Management Ltd +1846352,Nixon Capital LLC +1846368,Simplify Asset Management Inc. +1846462,Wealth Dimensions Group Ltd. +1846493,Red Wave Investments LLC +1846503,Arthedge Capital Management LLC +1846505,OUTFITTERS FINANCIAL LLC +1846515,Rodgers & Associates LTD +1846532,Continuum Advisory LLC +1846544,RWQ Financial Management Services Inc. +1846677,Nellore Capital Management LLC +1846711,Safeguard Investment Advisory Group LLC +1846758,Orion Capital Management LLC +1846838,Ewing Morris & Co. Investment Partners Ltd. +1846923,Williams Financial LLC +1846991,Round Rock Advisors LLC +1846994,Echo Wealth Management LLC +1846995,Alamar Capital Management LLC +1847328,Nykredit A/S +1847343,InTrack Investment Management Inc +1847566,Absoluto Partners Gestao de Recursos Ltda +1847610,Thrive Wealth Management LLC +1847700,Hudson Portfolio Management LLC +1847769,Tranquilli Financial Advisor LLC +1847772,EMC Capital Management +1847794,Twin Lakes Capital Management LLC +1847820,Robbins Farley +1847838,B. Metzler seel. Sohn & Co. AG +1847921,Draper Asset Management LLC +1848138,Greenwoods Asset Management Hong Kong Ltd. +1848237,SkyView Investment Advisors LLC +1848433,Coppell Advisory Solutions LLC +1848530,Hoffman Alan N Investment Management +1848704,MBA Advisors LLC +1848831,MONIMUS CAPITAL MANAGEMENT LP +1849055,Navis Wealth Advisors LLC +1849336,Beacon Wealthcare LLC +1849348,Ignite Planners LLC +1849444,SOA Wealth Advisors LLC. +1849497,TRU INDEPENDENCE ASSET MANAGEMENT 2 LLC +1849517,Tenere Capital LLC +1849518,DEFINED WEALTH MANAGEMENT LLC +1849529,Revere Asset Management Inc +1849561,CHILDRESS CAPITAL ADVISORS LLC +1849614,ENCOMPASS WEALTH ADVISORS LLC +1849618,Guided Capital Wealth Management LLC +1849724,Alliance Wealth Advisors LLC +1849753,Voyager Global Management LP +1850871,Invariant Investment Management +1850996,IronBridge Private Wealth LLC +1851296,Timelo Investment Management Inc. +1851362,Lorne Steinberg Wealth Management Inc. +1851395,BARON SILVER STEVENS FINANCIAL ADVISORS LLC +1851418,Valley Brook Capital Group Inc. +1851815,SBI Securities Co. Ltd. +1851869,Concentric Capital Strategies LP +1852042,Bancreek Capital Management LP +1852307,Sterling Group Wealth Management LLC +1852338,Pacific Wealth Management +1852808,Tower Wealth Partners Inc. +1852858,Everhart Financial Group Inc. +1852930,Mayflower Financial Advisors LLC +1852993,South Shore Capital Advisors +1853239,Willis Johnson & Associates Inc. +1853322,DARK FOREST CAPITAL MANAGEMENT LP +1853401,ADVOCATE GROUP LLC +1854428,CFC Planning Co LLC +1854794,Patient Capital Management LLC +1855567,Naviter Wealth LLC +1855713,WCG Wealth Advisors LLC +1855835,Piscataqua Savings Bank +1855967,January Capital Advisors LLC +1856022,Blue Trust Inc. +1856042,Spectrum Investment Advisors Inc. +1856103,PINNBROOK CAPITAL MANAGEMENT LP +1856155,MANE GLOBAL CAPITAL MANAGEMENT LP +1856405,SONA ASSET MANAGEMENT (US) LLC +1856637,Siemens Fonds Invest GmbH +1857144,FLYNN ZITO CAPITAL MANAGEMENT LLC +1857187,Myriad Asset Management US LP +1857581,Granby Capital Management LLC +1857588,Clearview Wealth Management LLC +1857666,Capital Group Private Client Services Inc. +1858699,Mayar Capital Ltd. +1858740,Spire Wealth Management +1858782,Canvas Wealth Advisors LLC +1858789,YOUSIF CAPITAL MANAGEMENT LLC +1858828,Guardian Wealth Advisors LLC +1859259,Fermata Advisors LLC +1859392,Galaxy Digital Inc. +1859579,Paradigm Strategies in Wealth Management LLC +1859606,Optiver Holding B.V. +1859677,Alaethes Wealth LLC +1859918,TECTONIC ADVISORS LLC +1860063,Lauer Wealth LLC +1860132,ONE PLUS ONE WEALTH MANAGEMENT LLC +1860487,Deuterium Capital Management LLC +1860501,Howard Capital Management Group LLC +1860698,CEERA INVESTMENTS LLC +1860719,RIDGECREST WEALTH PARTNERS LLC +1860790,COERENTE CAPITAL MANAGEMENT +1860998,Skaana Management L.P. +1861026,Marathon Asset Management Ltd +1861125,Westwood Wealth Management +1861159,O'Neil Global Advisors Inc. +1861163,Koss-Olinger Consulting LLC +1861378,SageGuard Financial Group LLC +1861642,Hennion & Walsh Asset Management Inc. +1861678,Bradley & Co. Private Wealth Management LLC +1861752,LCM Capital Management Inc +1861796,NewEdge Wealth LLC +1862067,FWG Holdings LLC +1862145,Clarity Financial LLC +1862176,Cornerstone Wealth Advisors Inc. +1862282,Pallas Capital Advisors LLC +1862427,Fi3 FINANCIAL ADVISORS LLC +1862428,Union Heritage Capital LLC +1862443,Vermillion & White Wealth Management Group LLC +1862664,NFJ INVESTMENT GROUP LLC +1862682,Caerus Investment Advisors LLC +1862864,Eagle Bay Advisors LLC +1862965,Applied Capital LLC +1863265,KENNICOTT CAPITAL MANAGEMENT LLC +1863523,CSM Advisors LLC +1863768,Capitol Family Office Inc. +1863894,Regency Capital Management Inc.\DE +1864123,Goodman Advisory Group LLC +1864229,Prentice Wealth Management LLC +1864835,Metavasi Capital LP +1864880,Wescott Financial Advisory Group LLC +1864916,Mezzasalma Advisors LLC +1865158,Keystone Financial Services +1866040,Heartland Bank & Trust Co +1866189,Keystone Wealth Services LLC +1867587,Invst LLC +1867731,SELDON CAPITAL LP +1867894,WealthTrust Asset Management LLC +1867958,SK Wealth Management LLC +1868491,Pinnacle Wealth Management Group Inc. +1868537,FUNDSMITH INVESTMENT SERVICES LTD. +1868643,SIG North Trading ULC +1868872,Focus Partners Advisor Solutions LLC +1868903,C2C Wealth Management LLC +1869032,Newport Capital Group LLC +1869164,IQ EQ FUND MANAGEMENT (SINGAPORE) PRIVATE Ltd +1869199,Philosophy Capital Management LLC +1869316,Laurus Global Equity Management Inc. +1869685,Mirabaud & Cie SA +1869923,Corsicana & Co. +1870012,TUCKER ASSET MANAGEMENT LLC +1870364,Y.D. More Investments Ltd +1870686,Strategic Financial Concepts LLC +1870761,Bank of New Hampshire +1871112,Elevation Capital Advisory LLC +1871352,Town Capital LLC +1871593,Sageworth Trust Co of South Dakota +1871734,Level Financial Advisors +1871926,Nuveen LLC +1872254,CV Advisors LLC +1872738,CenterBook Partners LP +1872787,Goldstream Capital Management Ltd +1873438,XY Planning Network Inc. +1873973,Hansen & Associates Financial Group Inc. +1873989,BG Investment Services LLC +1874080,Boulder Wealth Advisors LLC +1874146,Tenret Co LLC +1875525,STRATEGIC PLANNING GROUP LLC +1875645,JTC Employer Solutions Trustee Ltd +1875768,PENOBSCOT WEALTH MANAGEMENT +1876326,Five Oceans Advisors +1876496,Border to Coast Pensions Partnership Ltd +1876811,Heritage Investment Group Inc. +1877090,Twelve Points Wealth Management LLC +1877093,MOTIVE WEALTH ADVISORS +1877728,Accurate Wealth Management LLC +1877822,HFR Wealth Management LLC +1877829,Cherry Creek Investment Advisors Inc. +1878326,Delos Wealth Advisors LLC +1878547,Carmel Capital Management L.L.C. +1879371,JB Capital LLC +1879757,NFSG Corp +1880087,Topel & Distasi Wealth Management LLC +1881335,Dominguez Wealth Management Solutions Inc. +1881490,Palliser Capital (UK) Ltd +1881567,Clear Street Group Inc. +1882132,Lifeworks Advisors LLC +1882572,Riverwater Partners LLC +1882673,US Asset Management LLC +1882903,Heirloom Wealth Management +1883006,Private Wealth Asset Management LLC +1883134,Activest Wealth Management +1883629,Financial Council LLC +1884018,Rollins Financial Advisors LLC +1884799,Guardian Partners Inc. +1885319,Roberts Wealth Advisors LLC +1885767,DB Fitzpatrick & Co Inc +1885946,Boxwood Ventures Inc. +1886707,ARS Wealth Advisors Group LLC +1886813,McElhenny Sheffield Capital Management LLC +1887409,Revisor Wealth Management LLC +1887441,ENZI WEALTH +1888792,Ardent Capital Management Inc. +1888831,Innova Wealth Partners +1888847,REICON WEALTH ADVISORS LLC +1889147,Essex LLC +1889322,Strong Tower Advisory Services +1889830,MIC Capital Management UK LLP +1889918,BROGAN FINANCIAL INC. +1890149,Bell Investment Advisors Inc +1890183,Holland Advisory Services Inc. +1890222,J. Safra Sarasin Holding AG +1890435,Iyo Bank Ltd. +1890698,MARIPAU WEALTH MANAGEMENT LLC +1890748,Kercheville Advisors LLC +1890906,Allspring Global Investments Holdings LLC +1891201,MARYLAND CAPITAL ADVISORS INC. +1891240,Independent Wealth Network Inc. +1891713,Herold Advisors Inc. +1891892,DARA CAPITAL US INC. +1891904,Octahedron Capital Management L.P. +1892378,MBB PUBLIC MARKETS I LLC +1892770,Knott David M Jr +1892929,Tanager Wealth Management LLP +1893134,Smith Group Asset Management LLC +1893143,Atlantic Private Wealth LLC +1893159,NorthCrest Asset Manangement LLC +1893261,Alliance Wealth Advisors LLC /UT +1893327,FRG Family Wealth Advisors LLC +1893403,DMC Group LLC +1893767,CADEN CAPITAL PARTNERS LP +1893809,VISTA FINANCE LLC +1893893,STUDIO INVESTMENT MANAGEMENT LLC +1894044,Yahav Achim Ve Achayot - Provident Funds Management Co Ltd. +1894164,Burns Matteson Capital Management LLC +1894188,Saraza Management LP +1894203,VPR Management LLC +1894206,FIDELIS iM LLC +1894302,SJS Investment Consulting Inc. +1894447,Whalen Wealth Management Inc. +1894484,FOURPATH CAPITAL MANAGEMENT LLC +1894532,SORA INVESTORS LLC +1894712,Cromwell Holdings LLC +1894830,Corus Family Wealth Advisors +1894921,Whelan Financial +1895252,Aspen Wealth Management LLC +1895362,Badgley Phelps Wealth Managers LLC +1895612,VELA Investment Management LLC +1895678,GoalFusion Wealth Management LLC +1895911,Paralel Advisors LLC +1896148,R Squared Ltd +1896150,Catalyst Funds Management Pty Ltd +1896419,Collaborative Wealth Managment Inc. +1896430,Greenland Capital Management LP +1896447,IMPACTfolio LLC +1896476,Trans-Canada Capital Inc. +1897071,Andrew Hill Investment Advisors Inc. +1897090,PensionDanmark Pensionsforsikringsaktieselskab +1897612,T. Rowe Price Investment Management Inc. +1897700,Family Investment Center Inc. +1897835,Alan B Lancz & Associates Inc. +1898131,Wayfinding Financial LLC +1898282,VIRGINIA WEALTH MANAGEMENT GROUP INC. +1898296,Vickerman Investment Advisors Inc. +1898297,BayBridge Capital Group LLC +1898772,Crow's Nest Holdings LP +1898838,Schaeffer Financial LLC +1899030,Fifth Third Wealth Advisors LLC +1899146,SPRING CAPITAL MANAGEMENT LLC +1899158,THEORY FINANCIAL LLC +1899703,Paladin Advisory Group LLC +1899753,Pine Haven Investment Counsel Inc +1900099,Washington Trust Advisors Inc. +1900110,DGS Capital Management LLC +1900195,SURIENCE PRIVATE WEALTH LLC +1900406,Cross Staff Investments Inc +1900409,Agate Pass Investment Management LLC +1900481,Carmel Capital Partners LLC +1900576,Geometric Wealth Advisors +1900584,Amplius Wealth Advisors LLC +1900923,READYSTATE ASSET MANAGEMENT LP +1900946,CarsonAllaria Wealth Management Ltd. +1901166,Mill Capital Management LLC +1901222,Channing Global Advisors LLC +1901275,Ameritas Advisory Services LLC +1901337,DMKC Advisory Services LLC +1901361,Capricorn Fund Managers Ltd +1901384,Lane Generational LLC +1901403,GUARDIAN WEALTH ADVISORS LLC / NC +1901865,Divisadero Street Capital Management LP +1901929,Trevian Wealth Management LLC +1902024,Austin Asset Management Co Inc +1902501,CoreCap Advisors LLC +1902506,Straight Path Wealth Management +1902567,Varenne Capital Partners +1902806,NORTHCAPE WEALTH MANAGEMENT LLC +1902826,INTEGRAL INVESTMENT ADVISORS INC. +1902901,Collier Financial +1903035,KEB ASSET MANAGEMENT LLC +1903044,TAGStone Capital Inc. +1903055,Snowden Capital Advisors LLC +1903058,MWA Asset Management +1903059,BlackDiamond Wealth Management LLC +1903273,ShankerValleau Wealth Advisors Inc. +1903321,COTTONWOOD CAPITAL ADVISORS LLC +1903573,FJ Investments LLC +1903579,Wilson & Boucher Capital Management LLC +1903786,Highview Capital Management LLC/DE/ +1903859,Henrickson Nauta Wealth Advisors Inc. +1903866,Highlander Partners L.P. +1903880,JACKSON THORNTON WEALTH MANAGEMENT LLC +1903883,SHELTON WEALTH MANAGEMENT LLC +1904033,Buttonwood Financial Advisors Inc. +1904126,Martin Capital Advisors LLP +1904152,DOPKINS WEALTH MANAGEMENT LLC +1904274,Wedmont Private Capital +1904323,Royal Capital Wealth Management LLC +1904373,Caldwell Investment Management Ltd. +1904388,Wick Capital Partners LLC +1904423,Custos Family Office LLC +1904431,AVAII WEALTH MANAGEMENT LLC +1904432,KINTRA WEALTH LLC +1904477,Endowment Wealth Management Inc. +1904677,White Knight Strategic Wealth Advisors LLC +1904770,Manhattan West Asset Management LLC +1904822,DBK Financial Counsel LLC +1904825,Legacy CG LLC +1904832,Stiles Financial Services Inc +1904893,PineStone Asset Management Inc. +1904897,Styrax Capital LP +1904906,Consilium Wealth Advisory LLC +1905083,Connective Portfolio Management LLC +1905092,CLARIS ADVISORS LLC / MO / +1905128,American Trust +1905198,Morling Financial Advisors LLC +1905218,RK Capital Management LLC/FL +1905367,Orcam Financial Group +1905393,PCG Wealth Advisors LLC +1905627,FSA Advisors Inc. +1905663,EARNED WEALTH ADVISORS LLC +1905665,Shearwater Capital LLC +1905669,Synergy Financial Group LTD +1905673,Scott Capital Advisors LLC +1905765,Melone Private Wealth LLC +1905867,SAXON INTERESTS INC. +1905875,Conrad Siegel Investment Advisors Inc. +1905962,Pinnacle Financial Group LLC / IL +1906014,Aspire Capital Advisors LLC +1906023,ENDEAVOR PRIVATE WEALTH INC. +1906223,Viewpoint Capital Management LLC +1906322,POM Investment Strategies LLC +1906539,LOCKERMAN FINANCIAL GROUP INC. +1906547,Eagle Bluffs Wealth Management LLC +1906594,China Universal Asset Management Co. Ltd. +1906613,Solidarity Wealth LLC +1906640,Widmann Financial Services Inc. +1906711,WMG Financial Advisors LLC +1906719,Lynch Asset Management Inc. +1906766,OLIO Financial Planning +1906790,BAYSHORE ASSET MANAGEMENT LLC +1906793,Scissortail Wealth Management LLC +1906798,Barden Capital Management Inc. +1906799,Peterson Wealth Services +1906802,MKT Advisors LLC +1906805,Midwest Heritage Bank FSB +1906866,MATRIX PRIVATE CAPITAL GROUP LLC +1906937,Silver Oak Advisory Group Inc. +1906967,Evolution Advisers Inc. +1907054,NCM Capital Management LLC +1907092,Shore Point Advisors LLC +1907157,Financial Connections Group Inc. +1907212,PACK Private Wealth LLC +1907240,UDINE WEALTH MANAGEMENT INC. +1907254,WEALTH EFFECTS LLC +1907281,1620 INVESTMENT ADVISORS INC. +1907294,JMAC ENTERPRISES LLC +1907320,Western Pacific Wealth Management LP +1907327,CHERRYDALE WEALTH MANAGEMENT LLC +1907433,Stegner Investment Associates Inc. +1907528,GRAHAM CAPITAL WEALTH MANAGEMENT LLC +1907666,Eagle Strategies LLC +1907802,Ervin Investment Management LLC +1907803,Phraction Management LLC +1907820,AFG FIDUCIARY SERVICES LIMITED PARTNERSHIP +1907826,BLODGETT WEALTH ADVISORS LLC +1907898,SC&H Financial Advisors Inc. +1908108,TrueWealth Financial Partners +1908158,MILLER WEALTH ADVISORS LLC +1908165,Sovereign Investment Advisors LLC +1908169,Darrow Company Inc. +1908177,AIRE ADVISORS LLC +1908186,Missouri Trust & Investment Co +1908192,Stone Summit Wealth LLC +1908217,Sandy Cove Advisors LLC +1908219,Canoe Financial LP +1908275,Laraway Financial Advisors Inc +1908288,BARNES PETTEY FINANCIAL ADVISORS LLC +1908378,EdgeRock Capital LLC +1908386,Leverty Financial Group LLC +1908423,Park State Asset Management +1908450,Newlands Management Operations LLC +1908462,WealthCare Investment Partners LLC +1908585,LifeGuide Financial Advisors LLC +1908587,Members Advisory Group LLC +1908607,YARGER WEALTH STRATEGIES LLC +1908612,Allie Family Office LLC +1908617,Echo45 Advisors LLC +1908619,Promethos Capital LLC +1908623,PayPay Securities Corp +1908695,Kearns & Associates LLC +1908732,Magnolia Wealth Management LLC +1908765,Performance Wealth Partners LLC +1908828,Gouws Capital LLC +1908893,McBroom & Associates LLC +1908914,SFG Wealth Management LLC. +1908923,Czech National Bank +1908938,Beaird Harris Wealth Management LLC +1908944,CONSILIO WEALTH ADVISORS LLC +1908965,Gould Capital LLC +1908976,Stegent Equity Advisors Inc. +1909089,Barrier Capital Management LLC +1909126,SEVEN GRAND MANAGERS LLC +1909180,KIDS CAPITAL MANAGEMENT L.P. +1909249,SOUTHERN CAPITAL ADVISORS LLC +1909304,Carson Advisory Inc. +1909307,Investments & Financial Planning LLC +1909316,JOURNEY STRATEGIC WEALTH LLC +1909319,HERBST GROUP LLC +1909322,Insight Inv LLC +1909380,Disciplined Equity Management Inc. +1909565,New Perspectives Inc +1909570,FIGURE 8 INVESTMENT STRATEGIES LLC +1909571,Elevate Capital Advisors LLC +1909572,Enhancing Capital LLC +1909619,PRIMORIS WEALTH ADVISORS LLC +1909664,Pitti Group Wealth Management LLC +1909750,Operose Advisors LLC +1909760,Zullo Investment Group Inc. +1909798,HILL ISLAND FINANCIAL LLC +1909800,DENVER WEALTH MANAGEMENT INC. +1909805,WEALTHSPAN PARTNERS LLC +1909828,Lauterbach Financial Advisors LLC +1909846,Covestor Ltd +1909851,Morton Brown Family Wealth LLC +1909879,WEST MICHIGAN ADVISORS LLC +1909904,GREENUP STREET WEALTH MANAGEMENT LLC +1909993,DIXON FNANCIAL SERVICES INC. +1910000,CARDIFF PARK ADVISORS LLC +1910146,Frontier Asset Management LLC +1910168,Ameliora Wealth Management Ltd. +1910174,Archvest Wealth Advisors Inc. +1910180,STUART INVESTMENT ADVISORS INC. +1910183,Chemistry Wealth Management LLC +1910205,Guardian Wealth Management Inc. +1910210,TILIA FIDUCIARY PARTNERS INC. +1910248,Fingerlakes Wealth Management Inc. +1910273,Investment Advisory Group LLC +1910274,GREYCROFT LP +1910312,Paradigm Capital Management LLC/NV +1910321,Troluce Capital Advisors LLC +1910323,Ritter Alpha LP +1910364,Intrinsic Value Partners LLC +1910366,Next Level Private LLC +1910381,Kellett Wealth Advisors LLC +1910383,Joseph Group Capital Management +1910386,GUERRA PAN ADVISORS LLC +1910387,Unconventional Investor LLC +1910389,DRAVO BAY LLC +1910398,Prosperity Consulting Group LLC +1910411,Confluence Wealth Services Inc. +1910462,Drystone LLC +1910488,Red Tortoise LLC +1910503,Buska Wealth Management LLC +1910636,Forum Private Client Group LLC +1910641,Bryant Woods Investment Advisors LLC +1910660,Horizon Family Wealth Inc. +1910666,City State Bank +1910845,PARAGON FINANCIAL PARTNERS INC. +1910852,KRAEMATON INVESTMENT ADVISORS INC +1910854,VANCE WEALTH LLC +1910858,G2 CAPITAL MANAGEMENT LLC / OH +1910867,Gridiron Partners LLC +1910874,Eschler Asset Management LLP +1910876,KRS Capital Management LLC +1910934,Life Planning Partners Inc +1910961,William Allan Corp +1910966,Planning Center Inc. +1910971,Benchmark Investment Advisors LLC +1910984,Blackston Financial Advisory Group LLC +1911000,Intelligent Financial Strategies +1911013,Trail Ridge Investment Advisors LLC +1911026,5th Street Advisors LLC +1911035,626 Financial LLC +1911052,James Reed Financial Services Inc. +1911056,AXXCESS WEALTH MANAGEMENT LLC +1911067,Spinnaker Investment Group LLC +1911087,HFG Advisors Inc. +1911091,PURSUE WEALTH PARTNERS LLC +1911097,Marion Wealth Management +1911113,Settian Capital LP +1911159,JACKSON HILL ADVISORS LLC +1911244,ADAMSBROWN WEALTH CONSULTANTS LLC +1911253,HB Wealth Management LLC +1911264,Mid-American Wealth Advisory Group Inc. +1911266,Gibson Wealth Advisors LLC +1911278,LGT Group Foundation +1911284,LGT Fund Management Co Ltd. +1911307,Treasure Coast Financial Planning +1911316,Forbes Financial Planning Inc. +1911322,Byrne Asset Management LLC +1911342,Intergy Private Wealth LLC +1911348,JOSH ARNOLD INVESTMENT CONSULTANT LLC +1911378,Saber Capital Managment LLC +1911384,Mendel Capital Management LLC +1911391,Nicholas Wealth LLC. +1911400,Carroll Investors Inc +1911407,EAGLE ROCK INVESTMENT COMPANY LLC +1911448,Kinetic Partners Management LP +1911464,Pinnacle Wealth Management LLC +1911468,O'ROURKE & COMPANY Inc +1911470,ETF Store Inc. +1911472,Granite Bay Wealth Management LLC +1911488,REGATTA CAPITAL GROUP LLC +1911497,Ulland Investment Advisors LLC +1911520,Cladis Investment Advisory LLC +1911616,Financial Futures Ltd Liability Co. +1911621,Autumn Glory Partners LLC +1911695,Family CFO Inc +1911702,BetterWealth LLC +1911726,Raleigh Capital Management Inc. +1911735,SS&H Financial Advisors Inc. +1911822,Concorde Financial Corp +1911832,Forum Financial Management LP +1911876,BSN CAPITAL PARTNERS Ltd +1911900,Richwood Investment Advisors LLC +1911938,Capasso Planning Partners LLC +1911970,Future Fund LLC +1912040,Cordgrass Capital Advisors LLC +1912095,Milestones Administradora de Recursos Ltda. +1912128,McDonough Capital Management Inc +1912187,RDST Capital LLC +1912202,Leo Wealth LLC +1912297,Global Wealth Strategies & Associates +1912300,Vericrest Private Wealth +1912338,Quent Capital LLC +1912339,Resurgent Financial Advisors LLC +1912448,Bulltick Wealth Management LLC +1912451,Bullseye Investment Management LLC +1912460,Mystic Asset Management Inc. +1912612,Key Client Fiduciary Advisors LLC +1912835,COMMONS CAPITAL LLC +1912970,Brown Shipley& Co Ltd +1913043,Nilsine Partners LLC +1913231,Ascent Group LLC +1913243,Simplicity Wealth LLC +1913467,Lakeside Advisors INC. +1913545,Vienna Asset Management LLC +1913590,Security Financial Services INC. +1913842,apricus wealth LLC +1914099,BancFirst Trust & Investment Management +1914395,Summit Investment Advisory Services LLC +1914472,WILSON ASSET MANAGEMENT (INTERNATIONAL) PTY LTD +1914558,SageView Advisory Group LLC +1914606,Retirement Solution LLC +1914617,Glassy Mountain Advisors Inc. +1914644,Bill Few Associates Inc. +1914987,Schrum Private Wealth Management LLC +1915315,Nvest Financial LLC +1915494,Clientfirst Wealth Management LLC +1915687,Clay Northam Wealth Management LLC +1915714,Comprehensive Financial Consultants Institutional Inc. +1915765,jvl associates llc +1915842,Nearwater Capital Markets Ltd +1916123,Brooklyn FI LLC +1916366,My Personal CFO LLC +1916757,ASR Vermogensbeheer N.V. +1916908,AXQ CAPITAL LP +1917618,Alley Investment Management Company LLC +1917686,Mason & Associates Inc +1917704,Integrity Financial Corp /WA +1918181,Saudi Central Bank +1918613,Montz Harcus Wealth Management LLC +1918707,Fortune 45 LLC +1919142,High Net Worth Advisory Group LLC +1919158,Andina Capital Management LLC +1919176,Retireful LLC +1919344,KFG WEALTH MANAGEMENT LLC +1919438,Kraft Davis & Associates LLC +1919701,LOWERY THOMAS LLC +1919749,Carter Financial Group INC. +1919867,iSAM Funds (UK) Ltd +1920117,Hutchens & Kramer Investment Management Group LLC +1920405,Leo H. Evart Inc. +1921093,Athena Investment Management +1921196,Strengthening Families & Communities LLC +1921304,Smallwood Wealth Investment Management LLC +1921448,WorthPointe LLC +1921487,Beacon Capital Management LLC +1922200,Greenspring Advisors LLC +1922281,Aletheian Wealth Advisors LLC +1922448,Waycross Investment Management Co +1922684,Paragon Private Wealth Management LLC +1922875,MAYPORT LLC +1922879,Asset Allocation Strategies LLC +1922884,Boyce & Associates Wealth Consulting Inc. +1922963,SEMITAM BONAM LLC +1923052,CAPSTONE WEALTH MANAGEMENT GROUP LLC +1923053,Walker Asset Management LLC +1923591,West Wealth Group LLC +1923739,Harvest Portfolios Group Inc. +1923769,Grafton Street Partners Management Company LLC +1924152,Objective Capital Management LLC +1924615,Millington Financial Advisors LLC +1925220,Strategic Planning Inc. +1925251,Walter Public Investments Inc. +1925385,LIBRA WEALTH LLC +1925418,Sage Investment Advisers LLC +1925420,FIDUCIARY FINANCIAL GROUP LLC +1925853,Sterling Investment Counsel LLC +1926037,Chico Wealth RIA +1926253,KEYSTONE INVESTORS PTE LTD +1926349,Investmark Advisory Group LLC +1926571,Lansing Street Advisors +1926596,Empirical Asset Management LLC +1926783,NOVEM GROUP +1927129,FORTE ASSET MANAGEMENT LLC +1927175,WARNER FINANCIAL INC +1927285,Worth Asset Management LLC +1927315,Kingsbury Capital Investment Advisors LLC +1927474,Powers Advisory Group LLC +1927537,CATALYST FINANCIAL PARTNERS LLC +1927705,Aerodigm Wealth LLC +1927724,Redwood Financial Network Corp +1927769,CROSSPOINT FINANCIAL LLC +1927796,WILLNER & HELLER LLC +1928635,Range Financial Group LLC +1928877,Gray Private Wealth LLC +1928999,Fortitude Advisory Group L.L.C. +1929008,Balboa Wealth Partners +1929070,Joule Financial LLC +1929071,Finer Wealth Management Inc. +1929139,Quantum Financial Advisors LLC +1929170,ANGELES WEALTH MANAGEMENT LLC +1929349,Financial Guidance Group Inc. +1929662,Quantum Private Wealth LLC +1929907,W ADVISORS LLC +1929977,Seaside Wealth Management Inc. +1929986,WealthPlan Investment Management LLC +1930301,SUTTON PLACE INVESTORS LLC +1931041,Warwick Investment Management Inc. +1931232,Carr Financial Group Corp +1931465,Altura Wealth Advisors Inc. +1931642,Astoria Portfolio Advisors LLC. +1931750,Presidio Capital Management LLC +1931870,ARGONAUTICA PRIVATE WEALTH MANAGEMENT INC +1932342,Tradewinds LLC. +1932645,Wallace Advisory Group LLC +1932952,ARMSTRONG ADVISORY GROUP INC +1933059,RETIREMENT GUYS FORMULA LLC +1933132,XY Capital Ltd +1933952,AI-Squared Management Ltd +1934041,Opus Financial Solutions LLC +1934226,Ridgeline Wealth LLC +1934396,STG SECURITIES LLC +1934415,Trajan Wealth LLC +1934500,Coastwise Capital Group LLC +1934721,STAGE HARBOR FINANCIAL LLC +1934951,Schwallier Wealth Management LLC +1934965,AMARA FINANCIAL LLC. +1935795,Drake & Associates LLC +1936380,PPS&V ASSET MANAGEMENT CONSULTANTS INC. +1936416,Cercano Management LLC +1936420,CASTLE WEALTH MANAGEMENT LLC +1936603,Ritter Daniher Financial Advisory LLC / DE +1936845,Pacific Sage Partners LLC +1936953,Bleakley Financial Group LLC +1937021,CYPRESS FINANCIAL PLANNING LLC +1937769,Latko Wealth Management Ltd. +1938514,DecisionPoint Financial LLC +1938757,Triasima Portfolio Management inc. +1938970,Callan Family Office LLC +1939136,Coleford Investment Management Ltd. +1939202,Acumen Wealth Advisors LLC +1939208,INVICTUS PRIVATE WEALTH LLC +1939237,Highland Financial Advisors LLC +1939443,GRAND WEALTH MANAGEMENT LLC +1939480,GCM Grosvenor Holdings LLC +1939831,PRESILIUM PRIVATE WEALTH LLC +1939970,CAPITAL GROUP INVESTMENT MANAGEMENT PTE. LTD. +1940000,Wellment Financial +1940033,Plan Group Financial LLC +1940406,Greenfield Savings Bank +1940416,Tevis Investment Management +1940646,Altrius Capital Management Inc +1940660,MONTCHANIN ASSET MANAGEMENT LLC +1940678,Ruedi Wealth Management Inc. +1940823,W.H. Cornerstone Investments Inc. +1940869,GDS Wealth Management +1940917,Signature Resources Capital Management LLC +1941030,First National Advisers LLC +1941260,Higgins & Schmidt Wealth Strategies LLC +1941369,Veltria Advisors Corp. +1942341,Vertex Planning Partners LLC +1942364,Azimuth Capital Investment Management LLC +1942548,FSM Wealth Advisors LLC +1942932,Global Assets Advisory LLC +1943004,bLong Financial LLC +1943071,Ridgepath Capital Management LLC +1943228,Horizons Wealth Management +1943239,V-Square Quantitative Management LLC +1943395,Hook Mill Capital Partners LP +1943822,FIRETRAIL INVESTMENTS PTY LTD +1944142,Resona Asset Management Co. Ltd. +1944437,Cape Investment Advisory Inc. +1944755,Heritage Wealth Management Inc. +1944877,FWG Investments LLC. +1945037,Coston McIsaac & Partners +1945894,Davis Asset Management L.P. +1946136,Naman Capital Ltda +1946237,Strata Wealth Advisors LLC +1946654,Second Half Financial Partners LLC +1947503,Swisher Financial Concepts Inc. +1947670,Cresta Advisors Ltd. +1948435,ZENITH SOLUTIONS INC. +1948622,Avos Capital Management LLC +1948632,Citizens Business Bank +1948780,Corient Private Wealth LP +1948899,Avala Global LP +1948904,Dunhill Financial LLC +1949033,Spahn Wealth & Retirement LLC +1949059,James J. Burns & Company LLC +1949824,Essential Planning LLC. +1950118,WPWealth LLP +1950158,Lodestone Wealth Management LLC +1950218,Lantern Wealth Advisors LLC +1950323,Abound Wealth Management +1950490,Panoramic Capital LLC +1950506,Fortitude Family Office LLC +1950556,Dakota Community Bank & Trust NA +1950591,Mullooly Asset Management Inc. +1950607,Axim Planning & Wealth +1950637,Hibernia Wealth Partners LLC +1950841,Cardinal Point Capital Management ULC +1950947,Cyndeo Wealth Partners LLC +1950962,Three Bridge Wealth Advisors LLC +1951167,AJ Advisors LLC +1951283,Crane Advisory LLC +1951368,Centurion Wealth Management LLC +1951376,Centennial Advisors LLC +1951908,INTEGRITY WEALTH ADVISORS INC. +1952532,Legacy PCG LLC +1952722,SWAN Capital LLC +1952781,DRIVE WEALTH MANAGEMENT LLC +1953154,Investidor Profissional Gestao de Recursos Ltda. +1953787,Patrick Mauro Investment Advisor INC. +1954044,Fee-Only Financial Planning L.C. +1954085,STAPP WEALTH MANAGEMENT PLLC +1954093,Colonial Trust Co / SC +1954126,David Kennon Inc +1954136,Johnson & White Wealth Management LLC +1954242,Ethos Financial Group LLC +1954337,Cedar Point Capital Partners LLC +1954480,Western Financial Corp/CA +1954551,Warther Private Wealth LLC +1954782,Hoxton Planning & Management LLC +1954805,Fiduciary Alliance LLC +1954832,Leslie Global Wealth LLC +1954929,Summa Corp. +1955091,EnRich Financial Partners LLC +1956244,Prodigy Asset Management LLC +1956471,Meredith Wealth Planning +1956498,Crocodile Capital Partners GmbH +1956564,RFP Financial Group LLC +1956649,Daner Wealth Management LLC +1956790,FourThought Financial Partners LLC +1956824,DAYMARK WEALTH PARTNERS LLC +1957124,UNIQUE WEALTH LLC +1957363,Creative Capital Management Investments LLC +1957370,Left Brain Wealth Management LLC +1957394,Beaumont Financial Advisors LLC +1957726,Whitford Management LLC +1957840,Onyx Financial Advisors LLC +1957867,Hilltop Partners LLC +1957878,Cooper Capital Advisors LLC +1957886,Retirement Investment Advisors Inc. +1958250,Passive Capital Management LLC. +1958384,RIA Advisory Group LLC +1958456,Rochester Wealth Strategies LLC +1958491,Hofer & Associates. Inc +1958743,GUIDANCE CAPITAL INC +1958984,Verum Partners LLC +1959415,Sharper & Granite LLC +1959730,Fund 1 Investments LLC +1959790,Fortis Capital Advisors LLC +1959989,DoubleLine ETF Adviser LP +1960144,Strategic Investment Solutions Inc. /IL +1960212,JDM Financial Group LLC +1960657,Waterford Advisors LLC +1960749,Gallacher Capital Management LLC +1960860,Canopy Partners LLC +1961210,Shira Ridge Wealth Management +1961290,Mainsail Financial Group LLC +1961292,WealthSpring Partners LLC +1961304,Templeton & Phillips Capital Management LLC +1961628,Clarendon Private LLC +1961635,BEACON INVESTMENT ADVISORS LLC +1961738,Arvin Capital Management LP +1961742,New Hampshire Trust +1961828,Fortune Financial Advisors LLC +1961850,Kooman & Associates +1961898,TCP Asset Management LLC +1961944,LAM GROUP INC. +1962005,Crescent Sterling Ltd. +1962086,SP Asset Management LLC +1962166,RED LIGHTHOUSE INVESTMENT MANAGEMENT LLC +1962236,Kingdom Financial Group LLC. +1962382,Two Point Capital Management Inc. +1962449,Kestra Investment Management LLC +1962450,Compound Global Advisors LLC +1962457,Northern Financial Advisors Inc +1962465,MELFA WEALTH MANAGEMENT INC. +1962532,RETIREMENT FINANCIAL SOLUTIONS LLC +1962552,Bain Capital Public Equity LP +1962615,Trifecta Capital Advisors LLC +1962628,Phillips Wealth Planners LLC +1962636,Register Financial Advisors LLC +1962685,Talisman Wealth Advisors LLC +1962695,Olistico Wealth LLC +1962713,Nordwand Advisors LLC +1962755,FIDELIS CAPITAL PARTNERS LLC +1962838,Schear Investment Advisers LLC +1962933,Riverview Capital Advisers LLC +1963030,Forza Wealth Management LLC +1963040,LODESTAR PRIVATE ASSET MANAGEMENT LLC +1963169,HF Advisory Group LLC +1963212,Icon Wealth Advisors LLC +1963319,GEM Asset Management LLC +1963326,RPOA Advisors Inc. +1963352,Kennon-Green & Company LLC +1963355,Caitong International Asset Management Co. Ltd +1963404,Gavilan Investment Partners LLC +1963421,Koesten Hirschmann & Crabtree INC. +1963452,Windle Wealth LLC +1963536,Jessup Wealth Management Inc +1963565,Value Aligned Research Advisors LLC +1963612,Cassaday & Co Wealth Management LLC +1963669,TSA Wealth Managment LLC +1963728,Seed Wealth Management Inc. +1963732,Rooted Wealth Advisors Inc. +1963736,Hidden Cove Wealth Management LLC +1963764,Slotnik Capital LLC +1963787,Commonwealth Retirement Investments LLC +1963807,Goldstein Advisors LLC +1963839,Redwood Wealth Management Group LLC +1963863,REGIMEN WEALTH LLC +1963865,Davis Investment Partners LLC +1963875,HTG Investment Advisors Inc. +1963967,CPA Asset Management Group LLC +1964047,Melia Wealth LLC +1964068,Allegiance Financial Group Advisory Services LLC +1964106,Atlas Wealth LLC +1964171,DEEPWATER ASSET MANAGEMENT LLC +1964189,Auto-Owners Insurance Co +1964203,Richard W. Paul & Associates LLC +1964226,Arcadia Wealth Management LLC +1964298,Syntegra Private Wealth Group LLC +1964309,Breakwater Capital Group +1964344,Family Capital Management Inc. +1964358,Worth Financial Advisory Group LLC +1964394,Glass Jacobson Investment Advisors llc +1964400,FACT Capital LP +1964460,Envision Financial Planning LLC +1964530,CORA CAPITAL ADVISORS LLC +1964532,Kaydan Wealth Management Inc. +1964535,Great Waters Wealth Management +1964538,Mendota Financial Group LLC +1964541,DOVER ADVISORS LLC +1964544,PETREDIS INVESTMENT ADVISORS LLC +1964631,Alpine Investment Management Ltd +1964652,New England Capital Financial Advisors LLC +1964680,Significant Wealth Partners LLC +1964722,MILESTONE ASSET MANAGEMENT LLC +1964758,Arista Wealth Management LLC +1964775,Semus Wealth Partners LLC +1964809,AllGen Financial Advisors Inc. +1964810,B.O.S.S. Retirement Advisors LLC +1964819,McGlone Suttner Wealth Management Inc. +1964820,Tilson Financial Group Inc. +1964829,Marmo Financial Group LLC +1964831,MRP Capital Investments LLC +1964835,Compass Financial Group INC/SD +1964897,River Street Advisors LLC +1964922,Ipsen Advisor Group LLC +1964958,INSIGNEO ADVISORY SERVICES LLC +1964962,Mill Creek Capital Advisors LLC +1965005,Estuary Capital Management LP +1965078,Core Wealth Partners LLC +1965104,Manchester Global Management (UK) Ltd +1965150,Core Wealth Management Inc. +1965176,STEVENS CAPITAL PARTNERS +1965191,LGT Financial Advisors LLC +1965201,TFB Advisors LLC +1965207,Quarry LP +1965229,Sensible Money LLC +1965241,MOSAIC FAMILY WEALTH PARTNERS LLC +1965267,Eley Financial Management Inc +1965271,WATERSHED PRIVATE WEALTH LLC +1965275,Destiny Capital Corp/CO +1965292,Evergreen Wealth Management LLC +1965307,Prossimo Advisors LLC +1965328,Owen LaRue LLC +1965329,STAR Financial Bank +1965334,Moran Wealth Management LLC +1965351,Garden State Investment Advisory Services LLC +1965362,Grey Fox Wealth Advisors LLC +1965393,Applied Capital LLC/FL +1965401,VIAWEALTH LLC +1965468,Beacon Bridge Wealth Partners LLC +1965479,Walker Financial Services Inc. +1965484,Financial Freedom LLC +1965522,Clifford Group LLC +1965529,GENESIS PRIVATE WEALTH LLC +1965546,BRIGHT VALLEY CAPITAL Ltd +1965552,Longbow Finance SA +1965579,LEGACY FINANCIAL GROUP INC. +1965616,Tanglewood Legacy Advisors LLC +1965653,Compass Wealth Management LLC +1965659,Park Edge Advisors LLC +1965665,Monument Group Wealth Advisors LLC +1965668,Parker Financial LLC +1965702,Linden Thomas Advisory Services LLC +1965710,Beverly Hills Private Wealth LLC +1965756,C2P Capital Advisory Group LLC d.b.a. Prosperity Capital Advisors +1965757,Sonoma Private Wealth LLC +1965760,DUDLEY CAPITAL MANAGEMENT LLC +1965772,LMG Wealth Partners LLC +1965773,Northeast Financial Group Inc. +1965776,Graphene Investments SAS +1965796,Main Street Group LTD +1965798,Atlas Wealth Partners LLC +1965810,Park Place Capital Corp +1965814,BOS Asset Management LLC +1965819,West Tower Group LLC +1965915,RF&L WEALTH MANAGEMENT LLC +1965923,Gutierrez Wealth Advisory LLC +1965941,Bensler LLC +1966007,Applied Finance Capital Management LLC +1966011,Massachusetts Wealth Management +1966026,SILVERLAKE WEALTH MANAGEMENT LLC +1966033,True Wealth Design LLC +1966037,Gateway Wealth Partners LLC +1966057,Broyhill Asset Management LLC +1966066,BEACON FINANCIAL PLANNING INC +1966087,Kapstone Financial Advisors LLC +1966094,Gross & Hartman Investments LLC +1966116,Cravens & Co Advisors LLC +1966171,St. Louis Financial Planners Asset Management LLC +1966180,Talbot Financial LLC +1966193,LaSalle St. Investment Advisors LLC +1966210,Strait & Sound Wealth Management LLC +1966219,Alpha Financial Partners LLC +1966297,BURR FINANCIAL SERVICES LLC +1966351,Foundation Wealth Management LLC +1966355,Tillman Hartley LLC +1966400,Heritage Wealth Management Inc./Texas +1966482,CONTINUUM WEALTH ADVISORS LLC +1966581,Lakewood Asset Management LLC +1966595,JIA INVESTMENT ALLIANCE PTE. LTD. +1967193,Midwest Financial Group LLC +1967205,Leading Edge Financial Planning LLC +1967227,Financial Alternatives Inc +1967261,Harvest Investment Advisors LLC +1967332,PANORAMIC INVESTMENT ADVISORS LLC +1967456,Granite Harbor Advisors Inc. +1967640,DDFG Inc +1967844,LUTS & GREENLEIGH GROUP INC. +1968109,Vinva Investment Management Ltd +1968434,Wealth Preservation Advisors LLC +1968437,Perbak Capital Partners LLP +1968507,Baron Wealth Management LLC +1968777,25 LLC +1968850,NORTHBRIDGE FINANCIAL GROUP LLC +1968851,Midwest Financial Partners Investments Inc. +1968890,Flagstone Financial Management +1969566,iA Global Asset Management Inc. +1970075,LBP AM SA +1970465,Polymer Capital Management (HK) LTD +1970701,Delta Wealth Advisors LLC +1971029,Werba Rubin Papier Wealth Management +1971230,Ruggaard & Associates LLC +1971339,Kore Advisors LP +1971427,PETRA FINANCIAL ADVISORS INC +1971456,BRADY FAMILY WEALTH LLC +1971875,Empire Financial Management Company LLC +1972138,Alpha Financial Advisors LLC +1972322,Eaton Financial Holdings Company LLC +1972331,ClearAlpha Technologies LP +1972517,Aspen Wealth Strategies LLC +1972653,Imprint Wealth LLC +1972750,MilWealth Group LLC +1972835,Consolidated Portfolio Review Corp +1973209,Ergawealth Advisors Inc. +1973224,PFG Investments LLC +1973259,Buck Wealth Strategies LLC +1973323,Foresight Global Investors Inc. +1973324,Polymer Capital Management (US) LLC +1973339,Pioneer Wealth Management Group +1973728,Dodds Wealth LLC +1973783,O'Connor Financial Group LLC +1973849,Whitaker-Myers Wealth Managers LTD. +1973921,Values Added Financial LLC +1973967,Yoder Wealth Management Inc. +1973981,Resolute Wealth Strategies LLC +1974277,North Ridge Wealth Advisors Inc. +1974312,TORNO CAPITAL LLC +1974403,Vawter Financial Ltd. +1974438,OneAscent Investment Solutions LLC +1974910,Alpine Bank Wealth Management +1975417,Pine Valley Investments Ltd Liability Co +1975550,CHANNEL WEALTH LLC +1975700,Decker Retirement Planning Inc. +1975710,EVEXIA WEALTH LLC +1975730,Pinnacle West Asset Management Inc. +1975764,Sellaronda Global Management LP +1976010,INSTRUMENTAL WEALTH LLC +1976065,Noble Family Wealth LLC +1976151,Entropy Technologies LP +1976157,M1 Capital Management LLC +1976256,ELEVATION WEALTH PARTNERS LLC +1976435,Strategies Wealth Advisors LLC +1976780,Invera Wealth Advisors LLC +1977044,PCA Investment Advisory Services Inc. +1977092,Legacy Capital Group California Inc. +1977181,Root Financial Partners LLC +1977290,Quintet Private Bank (Europe) S.A. +1977444,FISCHER INVESTMENT STRATEGIES LLC +1977465,BlueStem Wealth Partners LLC +1977500,ABLE Financial Group LLC +1977560,Frank Rimerman Advisors LLC +1977602,OFI INVEST ASSET MANAGEMENT +1977723,National Wealth Management Group LLC +1977759,TITLEIST ASSET MANAGEMENT LLC +1977794,Bastion Asset Management Inc. +1977992,Trivant Custom Portfolio Group LLC +1978005,KENNEDY INVESTMENT GROUP INC. +1978011,Abacus Wealth Partners LLC +1978521,3Chopt Investment Partners LLC +1978608,Portside Wealth Group LLC +1978879,Compound Planning Inc. +1978883,Hudson Canyon Capital Management +1978885,Empower Advisory Group LLC +1979028,Hill Investment Group Partners LLC +1979556,SWEENEY & MICHEL LLC +1980273,EDENTREE ASSET MANAGEMENT Ltd +1980695,PMV Capital Advisers LLC +1982273,Nemes Rush Group LLC +1982776,ELEVATUS WELATH MANAGEMENT +1982920,9823 Capital L.P. +1983616,First County Bank /CT/ +1984256,Vanguard National Trust Co +1984475,Plato Investment Management Ltd +1984555,Impact Partnership Wealth LLC +1984918,Slagle Financial LLC +1985284,Aspect Partners LLC +1985414,Cherry Tree Wealth Management LLC +1985855,UNION SAVINGS BANK +1986156,Altiora Financial Group LLC +1986389,LAZARI CAPITAL MANAGEMENT INC. +1986457,MYECFO LLC +1986590,Samjo Management LLC +1986795,Kure Advisory LLC +1987005,BROWN WEALTH MANAGEMENT LLC +1987261,Marathon Mission Inc. +1987314,CORNERSTONE ENTERPRISES LLC +1987321,Arcataur Capital Management LLC +1987720,Corundum Trust Company INC +1987855,KINGSWOOD WEALTH ADVISORS LLC +1987932,SEVEN MILE ADVISORY +1988307,Point Nemo Capital LLC +1988408,SYKON CAPITAL LLC +1988563,LJI Wealth Management LLC +1989031,Trellis Wealth Advisors LLC +1989251,Dupree Financial Group LLC +1989341,INVENIO WEALTH PARTNERS LLC +1989349,KPP Advisory Services LLC +1989379,MFA Wealth Services +1989400,UniSuper Management Pty Ltd +1989672,RAM Investment Partners LLC +1989744,PFW Advisors LLC +1989834,HOGE FINANCIAL SERVICES LLC +1989941,PREVAIL INNOVATIVE WEALTH ADVISORS LLC +1989988,New Republic Capital LLC +1990058,MIDLAND WEALTH ADVISORS LLC +1990080,Kenora Financial LLC +1990099,Armstrong Fleming & Moore Inc +1990190,LongView Wealth Management +1990467,Talon Private Wealth LLC +1990690,Meritas Wealth Management LLC +1990699,NORDEN GROUP LLC +1990849,LECAP ASSET MANAGEMENT LTD +1991334,Prosperity Financial Group Inc. +1991340,Strategic Advocates LLC +1991463,Prosperity Wealth Management Inc. +1991835,Canada Post Corp Registered Pension Plan +1991983,SILVIA MCCOLL WEALTH MANAGEMENT LLC +1992110,Ramirez Asset Management Inc. +1992193,Waterway Wealth Management LLC +1992344,Avid Wealth Partners LLC +1992519,EntryPoint Capital LLC +1992724,Delta Global Management LP +1992748,CJM Wealth Advisers Ltd. +1992785,CGC Financial Services LLC +1992825,Farther Finance Advisors LLC +1992879,Second Line Capital LLC +1992915,LuminArx Capital Management LP +1992972,MN Wealth Advisors LLC +1993022,Madison Park Capital Advisors LLC +1993325,Slocum Gordon & Co LLP +1993327,CAPSTONE CAPITAL LLC +1993352,INSPIRE TRUST CO N.A. +1993404,Disciplina Capital Management LLC +1993485,Dixon Mitchell Investment Counsel Inc. +1993607,Borer Denton & Associates Inc. +1993888,Pictet Asset Management Holding SA +1994249,University of Illinois Foundation +1994252,FINANCIAL ADVISORY PARTNERS LLC +1994332,Payne Capital Management LLC +1994461,Equita Financial Network Inc. +1994495,Global View Capital Management LLC +1994512,Cobblestone Asset Management LLC +1994563,DiNuzzo Private Wealth Inc. +1994625,Visualize Group LP +1994744,Centennial Bank/AR/ +1994827,ACORN CREEK CAPITAL LLC +1995383,Constant Guidance Financial LLC +1995773,GSG Advisors LLC +1995984,Viaable LLC +1996154,UNICOM Systems Inc. +1996244,SIH Partners LLLP +1996449,Susquehanna Portfolio Strategies LLC +1996454,QRG CAPITAL MANAGEMENT INC. +1996846,Financiere des Professionnels - Fonds d'investissement inc. +1997245,Birnam Oak Advisors LP +1997405,TIAA TRUST NATIONAL ASSOCIATION +1997464,Marex Group Ltd +1997586,Etesian Wealth Advisors Inc. +1997602,Financial Security Advisor Inc. +1997650,Red Mountain Financial LLC +1997685,Arlington Trust Co LLC +1998000,Park Capital Management LLC / WI +1998018,NBZ Investment Advisors LLC +1998033,Envestnet Portfolio Solutions Inc. +1998101,Sound Stewardship LLC +1998182,IAMS WEALTH MANAGEMENT LLC +1998269,Peoples Bank/KS +1998414,Annis Gardner Whiting Capital Advisors LLC +1998419,Williams & Novak LLC +1998653,SAPIENT CAPITAL LLC +1998892,ROGCO LP +1998946,Wheelhouse Advisory Group LLC +1998980,AdviceOne Advisory Services LLC +1999144,DORVAL Corp +1999346,Knollwood Investment Advisory LLC +1999353,SYON CAPITAL LLC +1999514,denkapparat Operations GmbH +1999606,TD Waterhouse Canada Inc. +1999827,SYNTAX RESEARCH INC. +1999898,Kampmann Melissa S. +1999925,Premier Path Wealth Partners LLC +1999928,BARLOW WEALTH PARTNERS LLC +2000314,United Community Bank +2000355,KICKSTAND VENTURES LLC. +2000390,Maestria Partners LLC +2000493,Werlinich Asset Management LLC +2000571,Lakeshore Financial Planning Inc. +2001015,Concurrent Investment Advisors LLC +2001016,Prosperitas Financial LLC +2001019,NEOS Investment Management LLC +2001039,POWER WEALTH MANAGEMENT LLC +2001155,Exchange Bank +2001434,Burford Brothers Inc. +2001461,Allen Mooney & Barnes Investment Advisors LLC +2001473,White Wing Wealth Management +2001520,PUREfi Wealth LLC +2001526,DecisionMap Wealth Management LLC +2001544,Stephenson & Company Inc. +2001765,Independence Wealth Advisors LLC +2001900,Modern Wealth Management LLC +2001943,KKM Financial LLC +2002409,Tactive Advisors LLC +2002628,Morton Capital Management LLC/CA +2002630,Philip James Wealth Mangement LLC +2002654,PFS Partners LLC +2002745,Frazier Financial Advisors LLC +2002815,HARMONY ASSET MANAGEMENT LLC +2003112,Austin Wealth Management LLC +2003287,Financial Perspectives Inc +2003557,Harbour Trust & Investment Management Co +2003570,BCU Wealth Advisors LLC +2003615,Visionary Horizons LLC +2003633,Savvy Advisors Inc. +2003672,Quantum Portfolio Management LLC +2004474,Sentry LLC +2004495,Keener Financial Planning LLC +2004520,Lewis Asset Management LLC +2004720,WEST PACES ADVISORS INC. +2004818,Tyche Wealth Partners LLC +2004843,Financial Symmetry Inc +2004873,United Advisor Group LLC +2004904,Quotient Wealth Partners LLC +2004963,Petros Family Wealth LLC +2005098,Rockline Wealth Management LLC +2005134,Arrow Capital Pty Ltd +2005245,Siren L.L.C. +2005292,RESOLUTE WEALTH ADVISOR INC. +2005353,UPTICK PARTNERS LLC +2005380,Trueblood Wealth Management LLC +2005409,Bruce G. Allen Investments LLC +2005547,Cetera Trust Company N.A +2005743,One Degree Advisors Inc +2006008,Gold Investment Management Ltd. +2006218,TWIN PEAKS WEALTH ADVISORS LLC +2006405,Solutions 4 Wealth Ltd +2006517,Southland Equity Partners LLC +2006637,JAMISON PRIVATE WEALTH MANAGEMENT INC. +2006661,Leibman Financial Services Inc. +2006870,CAP Partners LLC +2006918,Integrity Wealth Solutions LLC +2007082,Oxford Wealth Group LLC +2007116,Physician Wealth Solutions Inc. +2007171,Turtle Creek Wealth Advisors LLC +2007175,Valued Wealth Advisors LLC +2007263,BKM Wealth Management LLC +2007281,Vivid Wealth Management LLC +2007591,Freestone Grove Partners LP +2007613,FLOYD FINANCIAL GROUP LLC +2007748,Traction Financial Partners LLC +2007877,LB Partners LLC +2007880,Silverberg Bernstein Capital Management LLC +2007960,Foundry Financial Group Inc. +2008165,Trust Co of the South +2008166,Peirce Capital Management LLC +2008171,TRITONPOINT WEALTH LLC +2008178,Shared Vision Wealth Group LLC +2008409,StoneCrest Wealth Management Inc. +2008410,Berry Wealth Group LP +2008513,Turning Point Benefit Group Inc. +2008554,ROBINSON SMITH WEALTH ADVISORS LLC +2008648,Forthright Family Wealth Advisory LLC +2008666,PRECEDENT WEALTH PARTNERS LLC +2008703,Fonville Wealth Management LLC +2008738,Stonebrook Private Inc. +2008758,MontVue Capital Management Inc. +2008792,Rachor Investment Advisory Services LLC +2008851,Capital Management Associates Inc +2009023,L1 Capital International Pty Ltd +2009176,Traveka Wealth LLC +2009211,WETZEL INVESTMENT ADVISORS INC. +2009224,LHM INC. +2009275,a16z Perennial Management L.P. +2009346,Advantage Trust Co +2009367,Stonebridge Financial Group LLC +2009388,VCI Wealth Management LLC +2009396,E. Ohman J:or Asset Management AB +2009419,Bear Mountain Capital Inc. +2009426,Bare Financial Services Inc +2009427,One Wealth Management Investment & Advisory Services LLC +2009486,Alternative Investment Advisors LLC. +2009521,HRC WEALTH MANAGEMENT LLC +2009530,Evernest Financial Advisors LLC +2009539,Rakuten Securities Inc. +2009590,Quantessence Capital LLC +2009591,Tidemark LLC +2009674,Quadcap Wealth Management LLC +2009724,Sequent Planning LLC +2009743,OPINICUS CAPITAL INC. +2009781,OxenFree Capital LLC +2009783,BALANCED WEALTH GROUP LLC +2009809,Unique Wealth Strategies LLC +2009813,NavPoint Financial Inc. +2009882,EAGLE WEALTH STRATEGIES LLC +2009886,Gilliland Jeter Wealth Management LLC +2009890,Arbor Wealth Advisors LLC +2009900,Wynn Capital LLC +2010015,Hara Capital LLC +2010029,EVOLUTION WEALTH MANAGEMENT INC. +2010095,SARD WEALTH MANAGEMENT GROUP LLC +2010098,Evergreen Private Wealth LLC +2010145,KITCHING PARTNERS LLC +2010185,Thayer Partners LLC / MA +2010186,BARTLETT & CO. WEALTH MANAGEMENT LLC +2010212,True Blue Financial LLC +2010235,GAMMA Investing LLC +2010248,ORBA Wealth Advisors L.L.C. +2010262,Elite Life Management LLC +2010278,Portfolio Design Labs LLC +2010315,Marest Capital LLC +2010327,Valley Financial Group Inc. +2010374,PFC CAPITAL GROUP INC. +2010393,Smartleaf Asset Management LLC +2010410,EVERPAR ADVISORS LLC +2010436,OMNI 360 Wealth Inc. +2010442,ENGLISH CAPITAL MANAGEMENT LLC +2010453,PATRICK M SWEENEY & ASSOCIATES INC +2010474,Clear Point Advisors Inc. +2010477,Access Investment Management LLC +2010507,Discipline Wealth Solutions LLC +2010574,Sachetta LLC +2010632,EHRLICH FINANCIAL GROUP +2010635,Northwest Financial Advisors +2010644,Farrow Financial Inc. +2010656,Partnership Wealth Management LLC +2010657,Winthrop Capital Management LLC +2010666,Cyr Financial Inc. +2010698,CENTRAL VALLEY ADVISORS LLC +2010710,JPL Wealth Management LLC +2010748,Wealth Group Ltd +2010765,Fielder Capital Group LLC +2010766,OV Management LLC +2010786,Boomfish Wealth Group LLC +2010854,BOCHK Asset Management Ltd +2010858,Maia Wealth LLC +2010926,Signal Advisors Wealth LLC +2010942,Focus Financial Network Inc. +2010947,Caitlin John LLC +2011000,Foundation Wealth Management LLC\PA +2011014,CONSCIOUS WEALTH INVESTMENTS LLC +2011050,MOSAIC FINANCIAL GROUP LLC +2011052,Eaton-Cambridge Inc. +2011081,Nationale-Nederlanden Powszechne Towarzystwo Emerytalne S.A. +2011113,Objectivity Squared LLC +2011145,Atlantic Edge Private Wealth Management LLC +2011147,1248 Management LLC +2011149,Sterling Wealth Management Inc. +2011155,FMA Wealth Management LLC +2011169,GC Wealth Management RIA LLC +2011176,Pingora Partners LLC +2011177,Long Island Wealth Management Inc. +2011184,De Lisle Partners LLP +2011194,PRAIRIEVIEW WEALTH PARTNERS LLC +2011195,Apollon Financial LLC +2011201,Elm3 Financial Group LLC +2011212,Clayton Financial Group LLC +2011215,VOISARD ASSET MANAGEMENT GROUP INC. +2011218,Encompass More Asset Management +2011219,Clarity Capital Advisors LLC +2011221,Darden Wealth Group Inc +2011229,Climber Capital SA +2011237,Stablepoint Partners LLC +2011256,Torque Asset Management LLC +2011267,SEAMOUNT FINANCIAL GROUP INC +2011271,Bey-Douglas LLC +2011314,LM Advisors LLC +2011321,Mustard Seed Financial LLC +2011325,Triavera Capital LLC +2011328,EIGHT 31 FINANCIAL LLC +2011333,VICTORY FINANCIAL GROUP LLC +2011335,J. Stern & Co. LLP +2011342,Private Wealth Management Group LLC +2011352,Keyes Stange & Wooten Wealth Management LLC +2011399,nVerses Capital LLC +2011427,FOGEL CAPITAL MANAGEMENT INC. +2011524,AYAL Capital Advisors Ltd +2011548,Novak & Powell Financial Services Inc. +2011550,Wiser Advisor Group LLC +2011556,Scientech Research LLC +2011563,Pennant Select LLC +2011587,NorthStar Asset Management LLC /NJ/ +2011593,Sprinkle Financial Consultants LLC +2011612,Pathstone Holdings LLC +2011633,ROI Financial Advisors LLC +2011649,UP STRATEGIC WEALTH INVESTMENT ADVISORS LLC +2011651,ELEVATE WEALTH ADVISORY INC +2011652,Lummis Asset Management LP +2011668,Pullen Investment Management LLC +2011697,FCG Investment Co +2011727,Spinecap SAS +2011736,Trademark Financial Management LLC +2011751,GR FINANCIAL GROUP LLC +2011771,Duncan Williams Asset Management LLC +2011780,Schulz Wealth LTD. +2011802,AVISO WEALTH MANAGEMENT +2011821,MORTON COMMUNITY BANK +2011849,Mosley Wealth Management +2011850,Optimist Retirement Group LLC +2011851,Balanced Rock Investment Advisors LLC +2011856,Gordian Advisors LLC +2011872,Advyzon Investment Management LLC +2011882,FOSTER DYKEMA CABOT & PARTNERS LLC +2011891,GLOBALT Investments LLC / GA +2011901,Randall & Associates Wealth Management +2011904,FF Advisors LLC +2011908,Able Wealth Management LLC +2011958,Kuhn & Co Investment Counsel +2011965,Williamson Legacy Group LLC +2012003,ALLEN WEALTH MANAGEMENT LLC +2012028,Lakeridge Wealth Management LLC +2012031,Financial Network Wealth Advisors LLC +2012032,Sivia Capital Partners LLC +2012033,Silver Coast Investments LLC +2012034,Sollinda Capital Management LLC +2012041,Chaney Capital Management Inc. +2012065,PROATHLETE WEALTH MANAGEMENT LLC +2012090,Unisphere Establishment +2012155,VIMA LLC +2012170,Heritage Wealth Management Inc. /CA/ +2012181,Sunpointe LLC +2012184,Oceanside Advisors LLC +2012280,Hobbs Group Advisors LLC +2012303,RICHARDSON FINANCIAL SERVICES INC. +2012356,SECURED RETIREMENT ADVISORS LLC +2012383,BlackRock Inc. +2012467,Counterweight Ventures LLC +2012511,ATLANTIC FAMILY WEALTH LLC +2012516,Taylor Financial Group Inc. +2012519,Prospect Financial Services LLC +2012614,SIERRA SUMMIT ADVISORS LLC +2012673,Sugar Maple Asset Management LLC +2012674,Pilgrim Partners Asia Pte Ltd +2012717,Triad Wealth Partners LLC +2012773,Wealth Forward LLC +2012816,Aware Super Pty Ltd as trustee of Aware Super +2012868,GREAT OAK CAPITAL PARTNERS LLC +2013188,ARK & TLK INVESTMENTS LLC +2013334,Zeno Equity Partners LLP +2013339,Abel Hall LLC +2013342,Avise Financial Cooperative Inc. +2013390,CacheTech Inc. +2013460,True Vision MN LLC +2013499,Accordant Advisory Group Inc +2013703,Transcendent Capital Group LLC +2013713,ABLES IANNONE MOORE & ASSOCIATES INC. +2013737,PBCay One RSC Ltd +2013788,Ridgeline Wealth Planning LLC +2013902,WHI TRUST Co LLC +2013937,ADAPT WEALTH ADVISORS LLC +2013988,Wealth Group Ltd. +2014164,GERBER LLC +2014179,Accent Capital Management LLC +2014200,SWP FINANCIAL LLC +2014209,Processus Wealth & Capital Management LLC +2014454,GOLDEN ROAD ADVISORS LLC +2014826,M3 Advisory Group LLC +2014898,MUIRFIELD WEALTH ADVISORS LLC +2015131,Cove Private Wealth LLC +2015178,Ravenswood Partners LP +2015578,BlueChip Wealth Advisors LLC +2015727,Gallo Partners LP +2016051,Pine Harbor Wealth Management LLC +2016110,Fox Hill Wealth Management +2016209,SHEPHERD WEALTH MANAGEMENT Ltd LIABILITY Co +2016217,FAMILY WEALTH PARTNERS LLC +2016322,Foguth Wealth Management LLC. +2016708,Niles Investment Management LLC +2016719,Fairman Group LLC +2016777,Benchstone Capital Management LP +2016793,DLK Investment Management LLC +2016899,AA Financial Advisors LLC +2016904,Verisail Partners LLC +2016961,Provident Co of the Employees of the Hebrew University LTD +2016972,MAINSAIL ASSET MANAGEMENT LLC +2017259,Columbia Bank +2017598,Summerhill Capital Management lnc. +2017692,Glass Wealth Management Co LLC +2017735,IMZ Advisory Inc +2017744,Arrowroot Family Office LLC +2017868,Financial Synergies Wealth Advisors Inc. +2017870,WealthCollab LLC +2017878,JBR Co Financial Management Inc +2017993,Hancock Prospecting Pty Ltd +2018007,American Capital Advisory LLC +2018090,Catalina Capital Group LLC +2018114,Granite Group Advisors LLC +2018284,Client First Investment Management LLC +2018412,Vann Equity Management LLC +2018815,Emprise Bank +2018936,Cascade Wealth Advisors Inc +2018963,Wallace Hart LLC +2019038,Atlanta Consulting Group Advisors LLC +2019084,KIRTLAND HILLS CAPITAL MANAGEMENT LLC +2019316,Heritage Family Offices LLP +2019337,Wall Street Financial Group Inc. +2019393,Fairway Wealth LLC +2019411,Capstone Wealth Management LLC +2019663,Ashton Thomas Private Wealth LLC +2019946,Mowery & Schoenfeld Wealth Management LLC +2020066,Central Pacific Bank - Trust Division +2020280,FIDUCIARY FAMILY OFFICE LLC +2020296,Davies Financial Advisors Inc. +2020459,Joel Adams & Associates Inc. +2020560,Attessa Capital LLC +2020582,O'Domhnaill Enterprises Inc. +2020781,Tenon Financial LLC +2020860,Embree Financial Group +2020935,Regents Gate Capital LLP +2021047,SEEDS INVESTOR LLC +2021208,Cascade Financial Partners LLC +2021217,FPC INVESTMENT ADVISORY INC. +2021232,Diversify Advisory Services LLC +2021242,Sienna Gestion +2021265,Broadway Wealth Solutions Inc. +2021272,Guardian Asset Advisors LLC +2021320,DEFINE FINANCIAL LLC +2021442,Nutshell Asset Management Ltd +2021464,Mittelman Wealth Management +2021658,Hilltop National Bank +2021703,Strathmore Capital Advisors Inc. +2021711,Marathon Strategic Advisors LLC +2021722,GGM Financial LLC +2021762,Prospect Financial Group LLC +2021982,TWIN CITY PRIVATE WEALTH LLC +2022028,Mediolanum International Funds Ltd +2022076,Councilmark Asset Management LLC +2022118,Koa Wealth Management LLC +2022154,Concord Investment Counsel Inc. +2022161,Gratus Wealth Advisors LLC +2022291,49 WEALTH MANAGEMENT LLC +2022297,Federation des caisses Desjardins du Quebec +2022328,New Insight Wealth Advisors +2022427,MARSHALL INVESTMENT MANAGEMENT LLC +2022456,BRIGHT FINANCIAL ADVISORS INC. +2022512,JDH Wealth Management LLC +2022609,Kelsey Financial LLC +2022614,Black Cypress Capital Management LLC +2022634,GENTRY PRIVATE WEALTH LLC +2022637,International Private Wealth Advisors LLC +2022783,Prairie Wealth Advisors Inc. +2022801,Income Insurance Ltd +2022843,V2 Financial group LLC +2022866,Rockport Wealth LLC +2022893,Miller Financial Services LLC +2022908,SPIREPOINT PRIVATE CLIENT LLC +2023054,Wrenne Financial Planning LLC +2023071,Perissos Private Wealth Management LLC +2023097,New Covenant Trust Company N.A. +2023166,Hopwood Financial Services Inc. +2023168,Atlatl Advisers LLC +2023324,Stanich Group LLC +2023325,LRI Investments LLC +2023336,Kelly Financial Services LLC +2023375,Gen-Wealth Partners Inc +2023380,TRAPHAGEN INVESTMENT ADVISORS LLC +2023386,VISTA INVESTMENT PARTNERS LLC +2023475,Spear Holdings RSC Ltd +2023493,William Howard & Co Financial Advisors Inc +2023568,Global Financial Private Client LLC +2023570,Transce3nd LLC +2023633,WEALTHGARDEN F.S. LLC +2023709,NCP Inc. +2023744,Everstar Asset Management LLC +2023896,RAELIPSKIE PARTNERSHIP +2024049,Investment Planning Advisors Inc. +2024115,Westfuller Advisors LLC +2024152,Pathway Financial Advisers LLC +2024251,Parkshore Wealth Management Inc. +2024264,ABOUND FINANCIAL LLC +2024333,ALEXANDER LABRUNERIE & CO. INC. +2024532,Erste Asset Management GmbH +2024579,Jain Global LLC +2024585,Painted Porch Advisors LLC +2025353,Katamaran Capital LLP +2025409,Greater Midwest Financial Group LLC +2025905,Generate Investment Management Ltd +2025925,Breakwater Investment Management +2025964,Landing Point Financial Group LLC +2026053,PERSHING SQUARE INC. +2026082,Bank & Trust Co +2026127,KEYNOTE FINANCIAL SERVICES LLC +2026128,Havemeyer Place LP +2026150,M Wealth Management LLC +2026215,Sone Capital Management LLC +2026286,Explore Capital Management LLC +2026391,OMC Financial Services LTD +2026480,Midwest Capital Advisors LLC +2026617,Two West Capital Advisors LLC +2026645,HAGER INVESTMENT MANAGEMENT SERVICES LLC +2026745,KCM Capital Inc +2026798,Meridiem Capital Partners LP +2026926,PARR MCKNIGHT WEALTH MANAGEMENT GROUP LLC +2026980,Expressive Wealth LLC +2027176,Rockingstone Advisors LLC +2027449,Sagace Wealth Management LLC +2027450,Global X Japan Co. Ltd. +2027462,Kurv Investment Management LLC +2027836,HFG Wealth Management LLC +2027921,Strategic Financial Partners Ltd. +2028202,Elser Financial Planning Inc +2028812,Lynx Investment Advisory +2029294,Mills Wealth Advisors LLC +2029317,Glen Eagle Advisors LLC +2029433,Science & Technology Partners L.P. +2029597,Kerusso Capital Management LLC +2029680,Flywheel Private Wealth LLC +2029708,Avenir Tech Ltd +2029917,ADAPT Investment Managers SA +2030036,Axecap Investments LLC +2030055,Argentarii LLC +2030181,Axxion S.A. +2030341,City Center Advisors LLC +2030525,Sphera Management Technology Funds Ltd +2030542,Codex Capital Asset Management L.L.C. +2030667,Sage Capital Management LLC +2030780,DIVERSIFY WEALTH MANAGEMENT LLC +2030974,Kings Path Partners LLC +2031123,Cannon Financial Strategists Inc. +2031235,Farrell Financial LLC +2031291,Squire Investment Management Company LLC +2031554,GCQ FUNDS MANAGEMENT PTY Ltd +2031637,LITTLEJOHN FINANCIAL SERVICES INC. +2031642,Drucker Wealth 3.0 LLC +2031671,RPS ADVISORY SOLUTIONS LLC +2031775,BridgePort Financial Solutions LLC +2031885,Concord Asset Management LLC/VA +2031979,Symphony Financial Services Inc. +2031991,Richmond Investment Services LLC +2032103,IMG Wealth Management Inc. +2032350,Fairscale Capital LLC +2032404,Chris Bulman Inc +2032436,Smith Thornton Advisors LLC +2032486,Stonekeep Investments LLC +2032497,GEN Financial Management INC. +2032544,Christensen King & Associates Investment Services Inc. +2032561,FreeGulliver LLC +2032602,Ascentis Wealth Management LLC +2032629,Meridian Financial Advisors LLC +2032709,RMR Capital Management LLC +2032856,Blue Capital Inc. +2033053,Paul R. Ried Financial Group LLC +2033094,Hardin Capital Partners LLC +2033232,Fairfield Financial Advisors LTD +2033266,OPTIMIZE FINANCIAL INC. +2033299,VESTIA PERSONAL WEALTH ADVISORS +2033312,Summit Wealth Partners LLC +2033384,Blue Water Asset Management +2033388,TPG Advisors LLC +2033413,Ted Buchan & Co +2033534,Aviso Financial Inc. +2033536,Northwest & Ethical Investments L.P. +2033609,Capital Investment Counsel LLC +2033683,Persium Advisors LLC +2033735,Sound Capital Solutions LLC +2033794,LifeWealth Investments LLC +2033881,Vienna Powszechne Towarzystwo Emerytalne S.A. Vienna Insurance Group +2033920,Orion Investment Co +2033987,A4 Wealth Advisors LLC +2034001,Convergence Financial LLC +2034054,William B. Walkup & Associates Inc. +2034064,ECOFI INVESTISSEMENTS SA +2034073,PROMETHIUM ADVISORS LLC +2034090,Granite FO LLC +2034181,WealthCare Asset Management LLC +2034214,Hershey Financial Advisers LLC +2034361,MUSTICO FINANCIAL GROUP INC. +2034519,Gallagher Capital Advisors LLC +2034565,Albar Capital Partners LLP +2034566,Brightwater Advisory LLC +2034579,Tumwater Wealth Management LLC +2034595,Mattson Financial Services LLC +2034793,Ariadne Wealth Management LP +2035144,NORTH DALLAS BANK & TRUST CO +2035215,Quest 10 Wealth Builders Inc. +2035216,Accelerate Investment Advisors LLC +2035219,VISTA INVESTMENT PARTNERS II LLC +2035232,BLKBRD Asset Management LP +2035324,NEW WAVE WEALTH ADVISORS LLC +2035325,Avanza Fonder AB +2035329,Carrera Capital Advisors +2035512,Cascades Capital Asset Management LLC +2035533,HERITAGE OAK WEALTH ADVISORS LLC +2035548,Brown Financial Advisors +2035883,KEELER & NADLER FINANCIAL PLANNING & WEALTH MANAGEMENT +2035951,Creekside Partners +2035982,Nicholson Wealth Management Group LLC +2036114,Safe Harbor Fiduciary LLC +2036117,FLAGSHIP WEALTH ADVISORS LLC +2036346,Headwater Capital Co Ltd +2036388,Brooklands Fund Management Ltd +2036461,S-Bank Fund Management Ltd +2036517,Luminvest Wealth Management LLC +2036769,KP Management LLC +2036775,Level Wealth Management LLC +2036922,MCCARTER PRIVATE WEALTH SERVICES LLC +2036975,YANKCOM Partnership +2037238,Massar Capital Management LP +2037264,Diversified Enterprises LLC +2037426,Pines Wealth Management LLC +2037578,HAMILTON CAPITAL PARTNERS LLC +2038170,Timonier Family Office LTD. +2038285,Portfolio Resources Advisor Group Inc. +2038325,Dickmeyer Boyce Financial Management Inc. +2038506,PARAGON CAPITAL MANAGEMENT INC +2039088,Compass Financial Services Inc +2039144,Hickory Point Bank & Trust +2039196,Wealth Advisory Team LLC +2039212,Code Waechter LLC +2039313,5T Wealth LLC +2039415,High Probability Advisors LLC +2039437,Martel Wealth Advisors LLC +2039501,Fortress Financial Solutions LLC +2039659,Seneschal Advisors LLC +2039698,BWM Planning LLC +2039738,Global Wealth Management LLC +2039850,TOUNJIAN ADVISORY PARTNERS LLC +2039918,Seros Financial LLC +2040013,Bawa N Mallick Trust +2040021,Goldstone Financial Group LLC +2040070,Integrated Capital Management LLC +2040084,Wealthspire Retirement LLC +2040221,Fieldview Capital Management LLC +2040224,FMB WEALTH MANAGEMENT +2040263,Rule One Partners LLC +2040317,Merkkuri Wealth Advisors LLC +2040353,Kera Capital Partners Inc. +2040377,Paladin Wealth LLC +2040393,Triune Financial Partners LLC +2040405,Shengqi Capital (Hong Kong) Ltd +2040515,Wealth Watch Advisors INC +2040600,FORONJY FINANCIAL LLC +2040686,Police & Firemen's Retirement System of New Jersey +2040860,WealthPoint Financial LLC +2040900,2UniFi Bank +2040901,Financially Speaking Inc +2040915,Aster Capital Management (DIFC) Ltd +2041021,Board of the Pension Protection Fund +2041065,von Borstel & Associates Inc. +2041220,Greykasell Wealth Strategies Inc. +2041262,Old North State Wealth Management LLC +2041267,Family Office Research LLC +2041427,Dale Q Rice Investment Management Ltd +2041436,Principia Wealth Advisory LLC +2041441,American Alpha Advisors LLC +2041800,Wingate Wealth Advisors Inc. +2041805,Osprey Private Wealth LLC +2041807,Five Pine Wealth Management +2041943,Millstone Evans Group LLC +2042011,Blake Schutter Theil Wealth Advisors LLC +2042068,MidAtlantic Capital Management Inc. +2042091,Whitebark Investors LP +2042493,Taylor Hoffman Capital Management LLC +2042508,NOVUS ADVISORS LLC +2042516,AUCTUS ADVISORS LLC +2042565,WIM INVESTMENT MANAGEMENT Ltd +2042654,HIGHLINE WEALTH PARTNERS LLC +2042772,Parkwoods Wealth Partners LLC +2042783,Wealth Management Nebraska +2042810,Lighthouse Wealth Management Inc. +2042876,Optivise Advisory Services LLC +2042930,Prosperity Advisers LLC +2042938,VERUS WEALTH MANAGEMENT LLC +2042955,Generali Powszechne Towarzystwo Emerytalne +2043084,Mainstream Capital Management LLC +2043129,Proactive Wealth Strategies LLC +2043130,Generali Investments Towarzystwo Funduszy Inwestycyjnych +2043136,Generali Investments Management Co LLC +2043186,SGL Investment Advisors Inc. +2043220,Mission Hills Financial Advisory LLC +2043468,Beckerman Institutional LLC +2043536,Kennebec Savings Bank +2043538,Elyxium Wealth LLC +2043591,Carnegie Lake Advisors LLC +2043671,Bretton Capital Management LLC +2043694,Palidye Holdings (Caymans) Ltd +2043725,Cannon Wealth Management Services LLC +2043729,Galilei Investment Office LLP +2043756,Arohi Asset Management PTE Ltd. +2043757,Oregon Pacific Wealth Management LLC +2043765,Orca Wealth Management LLC +2043810,BFI Infinity Ltd. +2043986,HOWARD BAILEY SECURITIES LLC +2044001,BCS Private Wealth Management Inc. +2044121,Sylvest Advisors LLC +2044171,LFG Wealth Partners LLC +2044208,Next Level Wealth Planning LLC +2044232,MINDSET WEALTH MANAGEMENT LLC +2044285,Dauntless Investment Group LLC +2044323,Yardley Wealth Management LLC +2044324,CORNERSTONE ADVISORS ASSET MANAGEMENT LLC +2044420,JIM SAULNIER & ASSOCIATES LLC +2044495,Three Seasons Wealth LLC +2044533,Rareview Capital LLC +2044575,WASHINGTON GROWTH STRATEGIES LLC +2044675,Shum Financial Group Inc. +2044723,SMART Wealth LLC +2044734,BAYPOINTE PARTNERS LLC +2044741,Retirement Wealth Solutions LLC +2044851,Nabity-Jensen Investment Management Inc +2044855,Comprehensive Financial Planning Inc./PA +2044874,Valued Retirements Inc. +2044885,Raiffeisen Bank International AG +2044901,Investor's Fiduciary Advisor Network LLC +2044929,CONWAY CAPITAL MANAGEMENT INC. +2045082,Legacy Wealth Managment LLC/ID +2045104,CIBC Capital Markets (Europe) S.A. +2045252,Lord & Richards Wealth Management LLC +2045258,Copley Financial Group Inc. +2045307,Tudor Financial Inc. +2045484,LifePlan Investment Advisors Inc. +2045703,ZEGA Investments LLC +2045735,NWF Advisory Services Inc. +2045870,Virtus Wealth Solutions LLC +2045972,FFG Partners LLC +2045974,CATHY PARETO & ASSOCIATES INC +2046033,Keystone Financial Services LLC +2046147,Wright Wealth LLC +2046157,FOUNDERS GROVE WEALTH PARTNERS LLC +2046179,RD Lewis Holdings Inc. +2046227,Heck Capital Advisors LLC +2046333,Oriental Harbor Investment Master Fund +2046605,LYNWOOD PRICE CAPITAL MANAGEMENT LP +2046607,Fairvoy Private Wealth LLC +2046751,Generali Asset Management SPA SGR +2046822,SHARPEPOINT LLC +2046823,Compass Planning Associates Inc +2046834,Integrated Quantitative Investments LLC +2047030,FIDUCIARY ADVISORS INC. +2047089,BLUE JEAN FINANCIAL LLC +2047201,Reyes Financial Architecture Inc. +2047270,BankPlus Trust Department +2047271,Murphy & Mullick Capital Management Corp +2047443,Legacy Wealth Management LLC / MS +2047463,XXI WEALTH LLC +2047540,Generali Investments CEE investicni spolecnost a.s. +2047572,EQ WEALTH ADVISORS LLC +2047606,AG2R LA MONDIALE GESTION D'ACTIFS +2047728,WINEBRENNER CAPITAL MANAGEMENT LLC +2047823,Finley Financial LLC +2048051,Hiley Hunt Wealth Management +2048100,Unifi Asset Management LP +2048387,Grange Capital LLC +2048423,Bayforest Capital Ltd +2048486,Jacksonville Wealth Management LLC +2048547,Asset Planning Inc +2048581,REAP Financial Group LLC +2048608,Aspetuck Financial Management LLC +2048733,R.H. Investment Group LLC +2048750,Clark & Stuart Inc +2048774,Clarity Wealth Development LLC +2048792,Friday Financial +2048885,Prasad Wealth Partners LLC +2048892,Vantage Point Financial LLC +2049064,Manuka Financial LLC +2049157,Tandem Financial LLC +2049176,RoundAngle Advisors LLC +2049201,CURIO WEALTH LLC +2049221,ABC ARBITRAGE SA +2049470,Brady Martz Wealth Solutions LLC +2049540,Mallini Complete Financial Planning LLC +2049750,Kaufman Rossin Wealth LLC +2049857,Penney Financial LLC +2050130,VestGen Advisors LLC +2050138,INKWELL CAPITAL LLC +2050159,ELWOOD CAPITAL PARTNERS LP +2050169,Quantum Financial Planning Services Inc. +2050308,ONE WEALTH CAPITAL MANAGEMENT LLC +2050555,KEYVANTAGE WEALTH LLC +2050660,VSM Wealth Advisory LLC +2050848,InvesTrust +2050968,Sincerus Advisory LLC +2050972,FLORIDA FINANCIAL ADVISORS LLC +2050974,CVFG LLC +2051108,Jordan Park Trust Co LLC +2051117,BankPlus Wealth Management LLC +2051288,WORMSER FRERES GESTION +2051323,CAXTON ASSOCIATES LLP +2051339,Rolek Wealth Management LLC +2051343,Ring Mountain Capital LLC +2051348,JBGlobal.com LLC +2051471,Sovran Advisors LLC +2051491,Westmount Partners LLC +2051568,Graney & King LLC +2051584,FSA Investment Group LLC +2051605,Berbice Capital Management LLC +2051613,KIECKHEFER GROUP LLC +2051705,Weinberger Asset Management Inc +2051715,SIERRA OCEAN LLC +2051717,Lifelong Wealth Advisors Inc. +2051783,Stark Wealth Management LLC +2051965,Berkeley Inc +2051980,Nova Wealth Management Inc. +2052024,FLP Wealth Management LLC +2052044,Inman Jager Wealth Management LLC +2052048,ARRIEN INVESTMENTS INC. +2052200,ENTREWEALTH LLC +2052222,SLT Holdings LLC +2052279,Crews Bank & Trust +2052308,POTENTIA WEALTH +2052310,SummitTX Capital L.P. +2052321,GKV Capital Management Co. Inc. +2052379,BXM Wealth LLC +2052405,Veridan Wealth LLC +2052436,CONQUIS FINANCIAL LLC +2052441,T3 Companies LLC +2052481,Wilmar Advisors LLC +2052484,Orvieto Partners L.P. +2052510,Whipplewood Advisors LLC +2052531,Axis Wealth Partners LLC +2052538,Carbahal Olsen Financial Services Group LLC +2052555,VAQUERO PRIVATE WEALTH LTD +2052564,Brentview Investment Management LLC +2052586,TigerOak Management L.L.C. +2052588,Baring Financial LLC +2052590,OFC FINANCIAL PLANNING LLC +2052593,Cache Advisors LLC +2052594,BLI - Banque de Luxembourg Investments +2052657,McHugh Group LLC +2052658,Saiph Capital LLC +2052710,WealthTrak Capital Management LLC +2052736,PUFF WEALTH MANAGEMENT LLC +2052737,Strategent Financial LLC +2052759,Councilor Wealth LLC +2052798,Flavin Financial Services Inc. +2052868,Archer Investment Management LLC +2052904,PMG Wealth Management Inc. +2052916,BEARING POINT CAPITAL LLC +2052933,Financial Harvest LLC +2052964,MATAURO LLC +2052970,Illumine Investment Management LLC +2052992,Panoramic Capital Partners LLC +2053046,MFG WEALTH MANAGEMENT INC. +2053050,LOUISBOURG INVESTMENTS INC. +2053138,one8zero8 LLC +2053150,Purkiss Capital Advisors LLC +2053236,Wernau Asset Management Inc. +2053242,Citrine Capital LLC +2053294,FORM Wealth Advisors LLC +2053303,PEREGRINE INVESTMENT MANAGEMENT INC +2053305,BIT Capital GmbH +2053307,Wealthcare Capital Partners LLC +2053314,LEGACY SOLUTIONS LLC +2053348,IFC Advisors LLC +2053350,Fjell Capital LLC +2053368,Financial Life Planners +2053628,Powszechne Towarzystwo Emerytalne Allianz Polska S.A. +2053642,Keystone Financial Group Inc. +2053665,Haven Private LLC +2053668,POSTROCK PARTNERS LLC +2053669,DAHRING | CUSMANO LLC +2053695,Serenus Wealth Advisors LLC +2053727,MOKAN Wealth Management Inc. +2053733,Michels Family Financial LLC +2053738,Aurelius Family Office LLC +2053750,CAPITAL & PLANNING LLC +2053756,Atala Financial Inc +2053757,Fiscal Wisdom Wealth Management LLC +2053783,BostonPremier Wealth LLC +2053786,Synergy Investment Management LLC +2053807,SIMS INVESTMENT MANAGEMENT LLC +2053824,ADG Wealth Management Group LLC +2053829,Palacios Wealth Management LLC +2053877,Graetz Wealth LLC +2053892,Wealth Advisors Northwest LLC +2053917,Ankerstar Wealth LLC +2054012,Pacific Asset Management LLC +2054047,CogentBlue Wealth Advisors LLC +2054083,SoundView Advisors Inc. +2054093,Centerpoint Advisory Group +2054098,Clare Market Investments LLC +2054100,Kentucky Trust Co +2054108,Permanent Capital Management LP +2054111,Atlas Legacy Advisors LLC +2054122,Longaeva Partners L.P. +2054129,Blueprint Financial Advisors LLC +2054149,Roxbury Financial LLC +2054234,Corps Capital Advisors LLC +2054263,Prudent Man Investment Management Inc. +2054264,Partners in Financial Planning +2054270,CRUX WEALTH ADVISORS +2054271,Alexis Investment Partners LLC +2054278,Chung Wu Investment Group LLC +2054328,Bravias Capital Group LLC +2054384,Generation Capital Management LLC +2054451,FORTRESS FINANCIAL GROUP LLC +2054458,KDT Advisors LLC +2054465,Hughes Financial Services LLC +2054476,Post Resch Tallon Group Inc. +2054496,Eleva Capital SAS +2054540,Runnymede Capital Advisors Inc. +2054543,POINCIANA ADVISORS GROUP LLC +2054598,Freedom Financial Partners LLC +2054674,ArborFi Advisors LLC +2054677,PIAR LLC +2054679,Drum Hill Capital LLC +2054680,ST. NICHOLAS PRIVATE ASSET MANAGEMENT INC +2054682,Grove Street Fiduciary LLC +2054684,LifeGoal Investments LLC +2054701,McGrath & Associates Inc. +2054714,PARTNERS WEALTH MANAGEMENT LLC +2054749,BCO Wealth Management LLC +2054795,Warburton Capital Management LLC +2054798,Variant Private Wealth LLC +2054801,Contrarius Group Holdings Ltd +2054825,Astra Wealth Partners LLC +2054827,Cushing Capital Partners LLC +2054855,Unified Investment Management +2054904,HMV Wealth Advisors LLC +2054906,PCG ASSET MANAGEMENT LLC +2054916,Impact Capital Partners LLC +2054920,First Financial Group Corp +2054942,Portland Financial Advisors Inc +2054946,BURLING WEALTH PARTNERS LLC +2054966,Rialto Wealth Management LLC +2054980,Alteri Wealth LLC +2055007,Bradyco Inc. +2055065,Noble Wealth Management PBC +2055099,Opulen Financial Group LLC +2055104,RFG - Bristol Wealth Advisors LLC +2055130,Magnolia Private Wealth LLC +2055134,North Forty Two & Co. +2055137,Intellus Advisors LLC +2055178,Sage Investment Counsel LLC +2055216,Grantvest Financial Group LLC +2055229,Victrix Investment Advisors +2055235,L.K. Benson & Company P.C. +2055276,Rubicon Global Capital Ltd +2055324,SCRATCH CAPITAL LLC +2055328,Frederick Financial Consultants LLC +2055344,Banque de Luxembourg S.A. +2055357,Forge Financial Services LLC +2055364,Antonelli Financial Advisors LLC +2055366,Cornerstone Select Advisors LLC +2055383,Range Rock Capital LLC +2055384,Integras Partners LLC +2055400,Ranmore Fund Management Ltd +2055414,Bay Capital Advisors LLC +2055492,AssuredPartners Investment Advisors LLC +2055521,Morey & Quinn Wealth Partners LLC +2055526,SwitchPoint Financial Planning LLC +2055532,Coordinated Financial Services Inc. +2055535,Davidson Kahn Capital Management LLC +2055554,Meriwether Wealth & Planning LLC +2055557,Mountain Hill Investment Partners Corp. +2055568,Aspire Growth Partners LLC +2055570,Riverbend Wealth Management LLC +2055574,Momentous Wealth Management Inc. +2055584,Catalyst Investment Management LLC +2055620,CERTIOR FINANCIAL GROUP LLC +2055645,BREAKTHRU ADVISORY SERVICES LLC +2055657,Endure Capital Management LLC +2055670,Bestgate Wealth Advisors LLC +2055803,Hippocratic Financial Advisors LLC +2055804,Haven Capital Group Inc. +2055812,OneAscent Family Office LLC +2055816,Factor Wealth Management LTD +2055829,SageOak Financial LLC +2055833,DKM Wealth Management Inc. +2055838,Trace Wealth Advisors LLC +2055875,PKO BP BANKOWY Universal Pension Society JSC +2055882,Sava Infond d.o.o. +2055889,Moment Partners LLC +2055907,Vista Cima Wealth Management LLC +2055925,Tepp RIA LLC +2055985,RIVERCHASE WEALTH MANAGEMENT LLC +2055997,WAYSTONE ADVISORS LLC +2056001,Three Cord True Wealth Management LLC +2056037,Prepared Retirement Institute LLC +2056052,Persistent Asset Partners Ltd +2056079,Spurstone Advisory Services LLC +2056088,Groupe la Francaise +2056095,Banque Transatlantique SA +2056100,Navigoe LLC +2056106,Stenger Family Office LLC +2056177,Holcombe Financial Inc. +2056230,TME FINANCIAL INC. +2056242,Disciplined Investors L.L.C. +2056243,Note Advisors LLC +2056245,Maridea Wealth Management LLC +2056266,Groupe des Assurances du Credit Mutuel +2056274,Redwood Park Advisors LLC +2056292,St. Clair Advisors LLC +2056306,MSH Capital Advisors LLC +2056313,Bernard Wealth Management Corp. +2056315,Marshall & Sterling Wealth Advisors Inc. +2056320,Rockbridge Asset Management LLC +2056333,Tandem Investment Partners LLC +2056334,STANCE CAPITAL LLC +2056336,Yoffe Investment Management LLC +2056338,TT Capital Management LLC +2056340,May Hill Capital LLC +2056354,Argosy-Lionbridge Management LLC +2056391,Everest Financial Group LLC +2056402,STEPHEN J. GARRY & ASSOCIATES LLC +2056410,RAHLFS CAPITAL LLC +2056414,Uniting Wealth Partners LLC +2056418,Grant Private Wealth Management Inc +2056425,SAGESPRING WEALTH PARTNERS LLC +2056438,Cohalo Advisory LLC +2056441,Coign Capital Advisors LLC +2056447,McMill Wealth Management +2056510,Rossby Financial LCC +2056521,MASTER'S WEALTH MANAGEMENT INC. +2056532,ROSS\JOHNSON & Associates LLC +2056550,Premier Private Wealth Management LLC +2056556,Texas Bank & Trust Co +2056566,AGP FRANKLIN LLC +2056576,INVESTMENT COUNSEL CO OF NEVADA +2056577,CHAPMAN FINANCIAL GROUP LLC +2056589,Graver Capital Management LLC +2056602,Charis Legacy Partners LLC +2056627,Topsail Wealth Management LLC +2056637,Headland Capital LLC +2056650,10Elms LLP +2056653,Helium Advisors LLC +2056656,Nolet Wealth Management LLC +2056667,LeClair Wealth Partners LLC +2056670,Morangie Management LLC +2056671,KANE INVESTMENT MANAGEMENT INC. +2056676,4WEALTH ADVISORS INC. +2056683,Everest Private Wealth +2056686,Advaya LLP +2056690,TRIGLAV INVESTMENTS D.O.O. +2056691,Fourth Dimension Wealth LLC +2056693,MPWM ADVISORY SOLUTIONS LLC +2056695,Apex Wealth Management LLC +2056697,Peak Retirement Planning Inc. +2056705,Vestment Financial LLC +2056711,Wise Wealth Partners +2056719,Collar Capital Management LLC +2056727,Sulzberger Capital Advisors Inc. +2056728,Oak Wealth Advisors LLC +2056729,CURA WEALTH ADVISORS LLC +2056752,Lionshead Wealth Management LLC +2056763,PINNEY & SCOFIELD INC. +2056764,Parvin Asset Management LLC +2056766,Sierra Legacy Group +2056783,Cornerstone Financial Group LLC /NE/ +2056795,Three Magnolias Financial Advisors LLC +2056807,Tema ETFs LLC +2056819,Arini Capital Management Ltd +2056907,Putney Financial Group LLC +2056914,VEGA INVESTMENT SOLUTIONS +2056922,Dogwood Wealth Management LLC +2056976,Pacific Point Advisors LLC +2057004,RETIREMENT PLANNING GROUP LLC / NY +2057054,Blue Sky Capital Consultants Group Inc. +2057056,SCHNIEDERS CAPITAL MANAGEMENT LLC. +2057060,GF FUND MANAGEMENT CO. LTD. +2057073,THOR TRADING ADVISORS LLC +2057074,Columbia River Financial Group LLC +2057075,Ball & Co Wealth Management Inc. +2057078,Warm Springs Advisors Inc. +2057153,Baer Investment Advisory LLC +2057170,Universal- Beteiligungs- und Servicegesellschaft mbH +2057199,Chokshi & Queen Wealth Advisors Inc +2057200,Griffith & Werner Inc. +2057208,Kingstone Capital Partners Texas LLC +2057278,Sunbeam Capital Management LLC +2057285,Wealth Management Strategies Inc. +2057318,Wilkins Miller Wealth Management LLC +2057382,Aurdan Capital Management LLC +2057421,General Pension Society PZU Joint Stock Co +2057465,Lakeshore Capital Group Inc. +2057556,Lighthouse Financial LLC +2057602,Challenger Wealth Management +2057637,AMERIFLEX GROUP INC. +2057655,LIBERTY SQUARE WEALTH PARTNERS LLC +2057931,Integrity Advisory Solutions LLC +2057936,RD Finance Ltd +2058093,INFINITUM ASSET MANAGEMENT LLC +2058144,GILPIN WEALTH MANAGEMENT LLC +2058235,KMT WEALTH MANAGEMENT LLC +2058267,Hartmann Taylor Wealth Management LLC +2058270,Barnes Dennig Private Wealth Management LLC +2058285,Park Square Financial Group LLC +2058383,Total Wealth Planning & Management Inc. +2058426,Corient IA LP +2058446,Brucke Financial Inc. +2058455,WEALTHEDGE INVESTMENT ADVISORS LLC +2058771,Copia Wealth Management +2058786,Night Squared LP +2058816,BANNERMAN WEALTH MANAGEMENT GROUP LLC +2058915,Kilter Group LLC +2058921,Comprehensive Money Management Services LLC +2058986,SpringVest Wealth Management LLC +2059107,J HAGAN CAPITAL INC. +2059321,PKO Investment Management Joint-Stock Co +2059323,Lansforsakringar Fondforvaltning AB (publ) +2059325,TFR Capital LLC. +2059327,Alpha Wealth Funds LLC +2059339,Arcadia Wealth Management Inc. +2059344,Wood Tarver Financial Group LLC +2059349,Elite Financial Inc. +2059365,Fairtree Asset Management (Pty) Ltd +2059571,Fire Capital Management LLC +2059574,MEMBERS WEALTH LLC +2059579,Wills Financial Group LLC +2059649,Capstone Wealth Management Group LLC +2059742,Chancellor Financial Group WB LP +2059743,Campbell Deegan Wealth Management LLC +2059754,RASP WEALTH SOLUTIONS LLC +2059828,Northstar Financial Companies Inc. +2059872,CLG LLC +2060114,Curry Webb Wealth Management LLC +2060278,Claris Financial LLC +2060298,Thoma Capital Management LLC +2060368,SILVER OAK WEALTH ADVISORS SERVICES LLC +2060412,111 Capital +2060443,LeConte Wealth Management LLC +2060492,Cloud Capital Management LLC +2060504,Seek First Inc. +2060663,TABLEAUX LLC +2060765,DSG Capital Advisors LLC +2060772,Good Steward Wealth Advisors LLC +2061010,AGH Wealth Advisors LLC +2061027,PTM WEALTH MANAGEMENT LLC +2061178,Dynamic Financial Group +2061818,Greenbush Financial Group LLC +2062026,Clark Asset Management LLC +2062383,North Berkeley Wealth Management LLC +2062492,AlpenGlobal Capital LLC +2062596,Innovative Wealth Building LLC +2062680,Rakuten Investment Management Inc. +2062716,NESTEGG ADVISORS INC. +2063003,Curat Global LLC +2063074,Mascagni Wealth Management Inc. +2063211,BINGHAM PRIVATE WEALTH LLC +2063243,Avant Financial Advisors LLC +2063452,Milestone Asset Management Group LLC +2063635,Phil A. Younker & Associates Ltd. +2063827,Lee Kelleher & Klein Wealth Management +2063915,Elevated Financial Group LLC +2063941,Stillwater Wealth Management Group +2063947,BYRNE FINANCIAL FREEDOM LLC +2063952,Papamarkou Wellner Asset Management inc. +2064001,Fischer Financial Services Inc. +2064039,North Dakota State Investment Board +2064043,Ledgewood Wealth Advisors LLC +2064172,Fremen Capital Management LP +2064225,SFM LLC +2064329,Henderson Brothers Financial Partners LLC +2064489,Kagan Cocozza Asset Management +2064545,Kultura Capital Management LP +2064588,WINCAP FINANCIAL LLC +2064807,Cauble & Harre Wealth Management Inc. +2064813,Genoa Capital Gestora de Recursos Ltda. +2064883,Woodside Wealth Management LLC +2064920,Pacific Capital Partners Ltd +2064978,Darwins River Capital LP +2065055,Evelyn Partners Investment Management (Europe) Ltd +2065074,SKY-MOUNTAIN CAPITAL MANAGEMENT INC. +2065205,Evelyn Partners Investment Management Services Ltd +2065206,Evelyn Partners Investment Management LLP +2065207,Evelyn Partners Asset Management Ltd +2065247,Balance Wealth Partners LLC +2065265,Global Trust Wealth Management LLC +2065347,Ivory Union Consulting Ltd +2065487,Rydar Equities Inc. +2065679,Parkway Wealth Management Group LLC +2065771,Nicholson Meyer Capital Management Inc. +2065777,MJT & Associates Financial Advisory Group Inc. +2065794,Summit Financial Consulting LLC +2065807,MB LEVIS & ASSOCIATES LLC +2065810,Safe Harbor Family Capital LLC +2065849,Yukon Wealth Management Inc. +2065938,North Capital Inc. +2065974,Aspen Capital Management LLC +2066080,Independence Financial Advisors LLC +2066105,Fortitude Financial LLC +2066147,Uptown Financial Advisors LLC +2066184,Kingsman Wealth Management Inc. +2066194,Barnes Wealth Management Group Inc +2066232,TradeWell Securities LLC. +2066260,Bosman Wealth Management LLC +2066265,SSA SWISS ADVISORS AG +2066311,RHL GROUP LLC +2066359,Integrity Investment Advisors LLC +2066488,Golden Reserve Retirement LLC +2066524,Infinity Wealth Counsel LLC +2066604,SmartHarvest Portfolios LLC +2066638,Legacy Wealth Partners LLC +2066674,Presper Financial Architects LLC +2066812,Brookwood Investment Group LLC +2067071,Ackerman Asset Management LLC +2067120,Caliber Wealth Management LLC / KS +2067126,ANB BANK +2067133,Cordoba Advisory Partners LLC +2067277,ALLIUM FINANCIAL ADVISORS LLC +2067339,Wealth Management Associates Inc. +2067342,Sun Financial Inc +2067343,WPG Advisers LLC +2067541,21 West Wealth Management LLC +2067581,Bullock Wealth Management Group +2067588,Robinhood Asset Management LLC +2067591,TOWER TRUST & INVESTMENT Co +2067608,DBA TRADING LLC +2067696,Wealth Enhancement Trust Services Inc. +2067954,QTR Family Wealth LLC +2067972,HFM Investment Advisors LLC +2068112,Forefront Wealth Partners LLC +2068217,Smith Asset Management Co. LLC +2068236,Plum Street Advisors LLC +2068376,Walleye Partners LLC +2068635,BOYUM WEALTH ARCHITECTS LLC +2068847,Legacy Advisory Services LLC +2069023,Vision Retirement LLC +2069031,Myriad Asset Management Advisors LLC +2069207,Reliant Investment Partners LLC +2069222,GK Wealth Management LLC +2069260,Capstone Capital Management Ltd +2069274,Investment Research Partners LLC +2069337,SIGNAL TREE FINANCIAL PARTNERS LLC +2069478,Evansbrook LLC +2069920,RESOLUTE CAPITAL LLC +2070026,Turn8 Private Wealth Inc. +2070361,DeLarme Wealth Management Inc. +2070529,Generations Wealth LLC +2070852,Wedbush Fund Advisers LLC +2070929,Kondo Wealth Advisors Inc. +2070972,OPTIMA CAPITAL LLC +2071170,TOWARZYSTWO FUNDUSZY INWESTYCYJNYCH PZU SA +2071183,LEGACY CAPITAL WEALTH MANAGEMENT LLC +2071468,BIRCHBROOK INC. +2072223,Valtrion Capital Management LLC +2072264,Providence Financial Advisors LLC +2072353,Meiji Yasuda America Inc +2072459,Life Cycle Investment Partners Ltd +2072569,HORAN Wealth LLC +2073596,RAINIER FAMILY WEALTH INC +2073603,Markin Volterra Fund LP +2073617,Warner Group LLC +2073676,Zinnia Wealth Advisory LLC +2073768,Westview Management dba Westview Investment Advisors +2073833,Investors Portfolio Services LLC +2073891,Wisconsin Wealth Advisors LLC +2074052,MASECO LLP +2074098,Taproot Management LP +2074418,Aventura Private Wealth LLC +2074563,Dockside LLC +2074628,Buckland Partners Management Co LLC +2074760,ARP Global Capital Ltd +2075029,MASTERINVEST Kapitalanlage GmbH +2075193,Spartan Wealth Advisory Services LLC +2075338,SMA Capital LLC +2075389,SHRIER WEALTH MANAGEMENT LLC +2075393,SNOWWATER INVESTMENT PARTNERS LLC +2075597,Limestone Investment Advisors LP +2075899,Bulwark Capital Corp +2076040,Charter Capital Management LLC\DE +2076068,Family Legacy Financial Solutions LLC +2076077,Longview Financial Advisors LLC +2076215,Sarver Vrooman Wealth Advisors +2076480,Compass Wealth Management LLC/GA +2077046,Willow Financial LLC +2077050,Harbor Asset Planning Inc. +2077076,MidFirst Bank +2077080,Midwest Trust Co +2077092,TB Capital Gestao de Recursos Ltda. +2077145,Square Wave Capital LLC +2077718,Sullivan Wood Capital Management LLC +2077884,J. Derek Lewis & Associates Inc. +2077902,180 GPS Investments IC Ltd +2077903,Cherokee Insurance Co +2077907,LOM Asset Management Ltd +2077991,Valpey Financial Services LLC +2078069,LETSON INVESTMENT MANAGEMENT INC. +2078353,October Effect Ltd +2078392,Cogent Private Wealth Inc. +2078684,Gladwyn Financial Advisors Inc. +2078760,Eddie Patel Inc +2078832,Q Fund Management (Hong Kong) Ltd +2078994,CAMBRIDGE CAPITAL MANAGEMENT LLC +2079032,Alpha Zero LLC +2079080,BRIAN LOW FINANCIAL GROUP LLC +2079098,MARS JEWETT FINANCIAL GROUP INC. +2079207,Traub Capital Management LLC +2079537,BLVD Private Wealth LLC +2079571,Investors Towarzystwo Funduszy Inwestycyjnych Spolka Akcyjna +2079593,NerdWallet Wealth Partners LLC +2079652,StoryOne LLC +2079661,Wilkerson Advisory Group LLC +2079687,WINNACLE WEALTH LLC +2079807,HighRoad Wealth Advisors LLC +2079812,Tripletail Wealth Management LLC +2079815,Burk Holdings LLC +2079995,Argyle Capital Partners LLC +2080094,Boreal Capital Management LLC +2080096,Holos Integrated Wealth LLC +2080218,Caldwell Trust Co +2080247,Pinpoint Asset Management (Singapore) Pte. Ltd. +2080267,Farnam Financial LLC +2080423,Westerkirk Capital Inc. +2080427,PERSPECTIVE WEALTH ADVISORS LLC +2080520,ABN AMRO INVESTMENT SOLUTIONS +2080627,DKRT Investments Corp. +2080849,Envision Financial Transparency LLC +2080991,Old Peak Finance LLC +2081110,Gibbs Wealth Management +2081139,Brindle & Bay Financial Advisors LLC +2081196,First International Bank of Israel Ltd. +2081211,Clarion Wealth Managment Partners LLC +2081278,SUMMIT WEALTH GROUP LLC / CO +2081584,Provenance Wealth Advisors LLC +2081714,Islander Capital Partners L.P. +2081847,REXFORD CAPITAL INC +2082460,Stonebridge Wealth Management LLC +2082673,Prospera Capital Management LLC +2082744,Exit Wealth Advisors LLC. +2082866,Pinnacle Financial Partners Inc. +2082922,Seneca Financial Advisors LLC +2082932,Verbena Value LP +2083087,Wealth Science Advisors LLC +2083149,Provident Living Financial Services Inc. +2083253,Nautilus Advisors LLC +2083276,Midwestern Financial LLC /IA +2083406,Third View Private Wealth LLC +2083411,Elevated Private Wealth LLC +2083456,Vilga Financial Planning LLC +2083499,Bannerstone Capital Management LLC +2083500,Anchor Bay Capital Inc. +2083592,BIRCH FINANCIAL GROUP LLC +2083646,Franchise GP Ltd +2083656,Ducere Wealth Management LLC +2083677,Tribridge Partners Financial LLC +2083963,Guardian Capital LLC +2083977,Base Wealth Management LLC +2084205,Kropog Financial Group LLC +2084239,Beacon Financial Strategies CORP +2084285,Black Diamond Capital Management I LLLP +2084339,BROADWATER CAPITAL MANAGEMENT LLC +2084965,Sonoma Allocations LLC +2085141,Welch Financial Planning LLC +2085256,Cambridge Financial Group LLC +2085325,Foundry Financial LLC +2085855,Harbour Wealth Management Group Inc. +2085963,ProCore Advisors LLC +2086041,Ravenstone Capital Management Inc. +2086121,Cane Capital Partners LLC +2086529,Integrated Financial Solutions Inc. +2086579,Napier Financial LLC +2086643,Element Squared LLC +2086953,American National Bank of Texas +2086998,Retail Employees Superannuation Pty Ltd as trustee for Retail Employees Superannuation Trust +2087139,IFS Group LLC +2087378,Avantyr Capital Partners LP +2087399,GatePass Capital LLC +2087462,Luma Capital S.A. - SPF +2087564,Bedminster LLC +2087652,Momentum Wealth Planning LLC +2087822,Torrey Growth & Income Advisors +2087873,Gaddis Premier Wealth Advisors LLC +2088236,Basepoint Wealth LLC +2088337,United Financial Planning Group LLC +2088548,PINEBRIDGE INVESTMENTS LLC +2088700,Hudson Oak Wealth Advisory LLC +2089089,Pitcairn Wealth Advisors LLC +2089123,PLATINUM PARAMOUNT INVESTMENT LTD. +2089183,Align Financial LLC +2089528,STEINBERGANNA WEALTH MANAGEMENT +2089718,Div Capital Phoenix Assets Ltd +2090040,Strive Financial Group LLC +2090165,INVESTED ADVISORS +2090208,Munich Reinsurance Co Stock Corp in Munich +2090468,Meadowbrook Wealth Management LLC +2090777,Cassilly Financial Group LLC +2091827,Hopwood Nicholas Hunter +2092331,BLALOCK WILLIAMS LLC +2092389,MOR Wealth Management LLC +2092853,Odyssean LLC +2093023,Jeter Robert S II +2093055,Aventus Investment Advisors Inc. +2093400,FORMULATE FINANCIAL LLC +2093444,Aureum Wealth Management LLC +2093518,Elevation Wealth Management LLC +2093645,Plan A Wealth LLC +2093649,BAM Wealth Management LLC +2093991,Mann Financial Group Inc. +2094120,Box Hill Private Wealth LLC +2094159,Fearless Solutions LLC dba Best Invest +2094332,Berman McAleer LLC +2094371,SAVA PENZISKO DRUSHTVO A.D. SKOPJE +2094379,CAPITOLIS LIQUID GLOBAL MARKETS LLC +2094426,Payne Capital LLC +2094435,TRED AVON FAMILY WEALTH LLC +2094533,VIRTUE ASSET MANAGEMENT LLC +2094564,Northwest Wealth Advisors LLC +2094948,Peak Wealth Management LLC +2095143,Manske Wealth Management +2095216,Dorato Capital Management +2095243,Lotus Technology Management LP +2095322,Index Technologies Group LLC +2095373,Guardian Wealth Management LLC +2095497,Michael Brady & Co. LLC +2095589,American Wealth Advisors LLC +2095682,WJ Financial Advisors LLC +2095709,IFC & Insurance Marketing Inc. +2095884,Split Rock Private Trading & Wealth Management LLC +2095935,Churchill Financial Advisors LLC +2095947,KERR FINANCIAL PLANNING Corp +2095967,Alpha Advisors LLC/VA +2095972,Kassira Wealth Management LLC +2096095,Danica Pension Livsforsikringsaktieselskab +2096110,Sherman Wealth Management LLC +2096203,Torren Management LLC +2096298,Signet Private Wealth LLC +2096338,Frankly Finances LLC +2096459,Kepler Cheuvreux (Suisse) SA +2096483,True Freedom Investing LLC +2096565,Aegis Wealth Management Inc. +2096567,Maxele Advisors LLC +2096700,William Mack & Associates Inc. +2096913,SHUTTLEWORTH & Co +2097005,OP Asset Management Ltd +2097025,Millennium Capital Advisors LLC +2097035,Englebert Financial Advisers LLC +2097037,Equity Wealth Partners LLC +2097223,Collaborative Capital Advisors LLC +2097384,Peak Planning Group LLC +2097516,Portus Wealth Advisors LLC +2097528,Greenfield Seitz Capital Management LLC +2097566,Roehl & Yi Investment Advisors LLC +2097587,Genesis Financial Group LLC +2097595,Steadtrust LLC +2097609,CHRISTINE MESSMER PC +2097856,Clear Trail Advisors LLC +2097898,Connecticut Capital Management Group LLC +2097943,Godfrey Financial Associates Inc. +2098300,SWP Investment Management LLC +2098400,Indivisible Partners +2098583,Hardworking Capital Advisors LLC +2098745,Saranac Partners Ltd +2098824,NWM ADVISORS LLC +2099019,Networth Advisors LLC +2099097,Miller Global Investments LLC +2099154,J.M. Arbour LLC +2099157,Investment Advisory Services Group LLC +2099257,Arta Finance Wealth Management LLC +2099846,Agave Capital Management Ltd +2099967,Asempa Wealth Advisors +2099997,KTF INVESTMENTS LLC +2100119,VANGUARD CAPITAL MANAGEMENT LLC +2100121,VANGUARD PORTFOLIO MANAGEMENT LLC +2100122,OPAL CAPITAL LLC +2100130,Cannon Capital Management Inc. +2100519,Pearl Planning LLC +2100778,Meramec Financial Planners LLC +2101006,Evergreen Wealth Partners LLC +2101641,Finivi Inc. +2101744,Xena Financial Planning LLC +2101909,Liberty Atlantic Advisors LLC +2101936,Daytona Street Capital LLC +2102009,Jefferson Bridge Capital LLC +2102043,Financial Planning Navigators CORP +2102299,PMG Family Office LLC +2102426,Maniro Ltd +2102530,Ebert Capital Management Inc. +2102688,Chatterton & Associates Inc. +2102803,6th Street Advisors LLC +2103310,PBU - The Pension Fund of Early Childhood & Youth Educators +2103332,Polaris Investment Advisors LLC +2103356,Titan Investment Solutions Ltd +2103359,JM2 Capital Inc. +2103364,KR Capital LP +2103405,BUFFALO BUSINESS & ESTATE SERVICES LTD +2103443,Keating Financial Advisory Services Inc. +2103454,Catherine Avery Investment Management LLC +2103520,Closed-End Fund Advisors Inc. +2103577,QUANTUM FINANCIAL PARTNERS LLC +2103792,Coquina Private Wealth LLC +2104293,Cornerstone National Bank & Trust Co +2104330,COFG Advisors LLC +2104394,Daviman Financial LLC +2104438,Jones Kertz & Associates Inc. +2104442,HBE Wealth Management LLC +2104503,Bluebird Wealth Management LLC +2104539,Blue Sparrow LLC /DE +2104543,Pekao Towarzystwo Funduszy Inwestycyjnych S.A. +2104828,BETO FINANCIAL GROUP LLC +2104872,Odyssey Capital Advisors Inc. +2104888,MFF Capital Investments Ltd +2104890,Montaka Global Pty Ltd +2104895,Argo Wealth Advisory LLC +2104898,Geremia Financial Services LLC +2105062,FOCUSED ALPHA LLC +2105146,Financial Planning Hawaii Inc. +2105385,Capital Developers LLC +2105389,Serenity Investment Advisors +2105393,Rare Wolf Capital LLC +2105395,Arbejdsmarkedets Tillaegspension +2105396,Legacy Edge Advisors LLC +2105416,TICINO WEALTH +2105684,CAMBIENT FAMILY OFFICE LLC +2105785,STEWARDSHIP CONCEPTS FINANCIAL SERVICES LLC +2105817,Directional Asset Management +2105906,ION Fund Management Ltd +2105908,AK GLOBAL ASSET MANAGEMENT LLC +2105919,Burton Enright Welch +2105933,ASL Financial LLC +2105992,Capelight Capital Asset Management LP +2106035,HughesLittle Investment Management Ltd. +2106214,Ethos Capital Management Inc. +2106311,Fortune Financial Group Inc. +2106457,55 North Private Wealth LLC +2106661,Breachway Investments LLC +2106678,Madrid Wealth Management LLC +2106717,Ares Financial Consulting LLC +2106763,Alliance Wealth Strategies LLC d/b/a Brown Edwards Wealth Strategies +2106796,Flagship Capital Management Inc. +2106810,Calder Financial LLC +2106852,COGENT STRATEGIC WEALTH LLC +2106874,Juno Financial Group LLC +2106882,High Point Wealth Management LLC +2106928,Ballast Rock Private Wealth LLC +2106942,Nvest Wealth Strategies Inc. +2106948,Approach Retirement Advisors LLC +2106968,Adelphi Trust Co +2106977,Corecam Pte. Ltd. +2107020,Arnold Financial Planning LLC +2107079,Womack Financial LLC +2107086,QUANTIFY CHAOS ADVISORS LLC +2107106,ANCHYRA PARTNERS LLC +2107111,Daybright Advisory Services Inc. +2107127,Price Financial Group Wealth Management Inc +2107238,FORTERIS WEALTH MANAGEMENT INC. +2107248,Palatine Hill Wealth Management LLC +2107249,Key Capital Management INC +2107252,Emissary Wealth LLC +2107255,Pacific Park Financial Inc. +2107256,TrustBank +2107257,Stronghold Wealth Management L.L.C. +2107260,Benchmark Financial LLC +2107271,Eagle Wealth Advisors LLC +2107286,Eurizon Capital SGR S.p.A. +2107309,Eurizon SLJ Capital Ltd +2107377,DB&C Advisors LLC +2107394,August Group Capital Ltd +2107398,Parsonex Advisory Services Inc. +2107449,Strategic Wealth Advisors LLC +2107460,Merrithew & Thorsten Inc +2107464,Gambit Capital Management LLC +2107467,Rayburn West Financial Services LLC +2107537,CDM FINANCIAL COUNSELING SERVICES INC. +2107563,Richard Young Associates Ltd. +2107564,Themes Management Co LLC +2107566,OFS Enterprise LLC +2107584,Financial Plan Inc. +2107623,Adirondack Capital Advisors LLC +2107625,Vigil Wealth Management LLC +2107629,J. Team Financial Inc. +2107657,Clark Wealth Partners +2107705,Fourier Capital Management Ltd +2107727,Hegarty Advisors LLC +2107738,Sherry Group Inc. +2107740,Ponta Wealth Partners LLC +2107751,Tencap Wealth Coaching LLC +2107860,Sandro Wealth Management LLC +2107886,Royal Palms Capital LLC +2107902,Colter Lewis Investment Partners LLC +2108102,Belleair Asset Management LLC +2108122,Boyer Financial Services Inc. +2108201,truNorth Financial Services Inc. +2108281,Piedmont Capital Management LLC/NC +2108355,Fullerton Advisors LLC +2108398,Reflection Asset Management +2108411,INTERCAPITAL LLC +2108483,Omnitrust Wealth Management Inc +2108559,Diesslin Group Inc. +2108684,Carroll Advisory Group LLC +2108771,ASO GROUP Ltd +2108790,FULCRUM WEALTH ADVISORS LLC +2108842,Pathfinder Wealth Consulting Inc. +2108989,STOLZ & ASSOCIATES PS +2109050,Trask Adam Roland +2109061,MLP3 LLC +2109063,Winter & Associates Inc. +2109094,Goldenstone Wealth Management LLC +2109121,Alvarez & Marsal Private Wealth Partners LLC +2109156,Briggs Wealth Management Inc +2109159,Timothy G. Youngquist 2020 Irrevocable Trust +2109198,Stonebridge Financial Group LLC / MO +2109204,Sound Portfolio Advisors LLC +2109205,McMillan Office Inc. +2109222,Tailwinds Wealth LLC +2109247,Vistica Wealth Advisors LLC +2109295,Dougherty & Associates LLC +2109305,Shelter Rock Management LLC +2109360,Burkett Asset Management Ltd +2109365,M3 Wealth Management LLC +2109367,TopTier Wealth Management LLC +2109387,Ferguson Johnson Wealth Management Inc +2109452,Titan Wealth (CI) Ltd +2109460,WIREGRASS INVESTMENT MANAGEMENT LLC +2109462,Three Arch Wealth Management LLC +2109472,IF Advisors LLC +2109474,Arsenal Capital Advisors LLC +2109484,LAWOOD & CO +2109497,Platt Wealth Management LLC +2109515,Plus Group Wealth Advisors LLC +2109534,Trailhead Planners LLC +2109595,Entelevest LLC +2109614,Petersen Hastings Wealth Advisors Inc. +2109644,Yanni & Associates Investment Advisors LLC +2109652,PINCUS CAPITAL MANAGEMENT LP +2109734,Stembrook Asset Management LLC +2109783,Vantus Wealth LLC +2109808,Artesa Financial Group LLC +2109813,Signature Equity Partners LLC +2109821,Ballast Financial Advisors LLC +2109834,Marin Bay Wealth Advisors LLC +2109835,Castlefield Investment Partners LLP +2109846,PCM Encore LLC +2109847,Karras Company Inc. +2109849,Alliance Private Wealth LLC +2109850,marrick wealth LLC +2109852,RM Financial Services LLC +2109857,Garton & Associates Financial Advisors LLC +2109860,ENTRUST FINANCIAL LLC +2109863,Benson Wealth Management INC +2109867,Wagner Wealth Management Corp +2109868,EFG International AG +2109913,Hilton Head Capital Partners LLC +2109923,Fideuram Asset Management (Ireland) dac +2109928,CDKV HOLDINGS LLC +2109944,Oath Planning LLC +2110045,Timmons Wealth Management LLC +2110046,Walsky Investment Management Inc. +2110060,Shepherd Street Advisors LLC +2110061,Harborfront Financial Group LLC +2110062,Birchwood Financial Partners Inc. +2110066,BLUELINE ADVISORS LLC +2110081,Marble Wealth LLC +2110106,BITTERROOT CAPITAL ADVISORS LLC +2110109,Ark Wealth Advisors LLC +2110205,Value Investment Professionals LLC +2110313,Altrafin AG +2110329,Cornerstone Financial Management LLC +2110354,Lynch Investment Planning LLC +2110357,Western Reserve Capital Management LLC +2110402,Asset Advisory Group Inc. +2110453,NewCorp Financial Services Inc. +2110507,Samara Investment Management LLC +2110509,Advocate Investing Services LLC +2110534,Prism Planning Partners LLC +2110593,Eurizon Asset Management Hungary Ltd. +2110606,Alchemi Wealth LLC +2110622,Platform Wealth Management LLC +2110646,Andrews Advisory Associates LLC +2110649,BAYBAN +2110651,BFI Wealth Solutions LLC +2110653,Johnson Wealth Management LLC +2110669,JHP Wealth Management LLC +2110677,Ruggiero Investments Inc. +2110678,Vines Capital Management LLC +2110679,Xcelsior Advisor Partners LLC +2110687,Reality Financial Planning Services LLC +2110690,LCW Services LLC +2110717,Wealth Analytics Partners LLC +2110721,BCV Asset Management Inc. +2110759,Asset One Wealth Management LLC +2110760,GUARDSMAN PRIVATE CAPITAL MANAGEMENT INC. +2110806,Markin Asset Management LP +2110807,Troutman Wealth Management LLC +2110810,LiftPoint Family Wealth Advisors LLC +2110817,E6 Portfolios LLC +2110834,Tannin Capital LLC +2110835,Storgate LLC +2110838,MATTERS CAPITAL LLC +2110878,Steffes Financial Ltd. +2110880,AG Campbell Advisory LLC +2110884,Pacific Excel Wealth Advisors Inc. +2110886,ATLas Financial Planning LLC +2110891,Sunrise Financial Services LLC +2110897,NLB Skladi upravljanje premozenja d.o.o. +2110902,Intesa Sanpaolo Wealth Management +2110914,Corecam AG +2110994,BLUEDOOR PRIVATE WEALTH LLC +2110996,H Squared Management LP +2111007,Sun Group Wealth Partners +2111013,ATX Financial Planning LLC +2111036,Smith Partners Wealth Management LLC +2111080,CSP Financial Group LLC +2111131,EVOLVE PRIVATE WEALTH LLC +2111158,Quattro Advisors LLC +2111167,TritonPoint Partners LLC +2111172,Silver Grove Financial Group Inc. +2111175,BNB Wealth Management LLC +2111209,SPWM Advisors LLC +2111343,SBE LLC DBA CEDAR COVE WEALTH PARTNERS +2111344,Copos Capital B.V. +2111345,OpenArc Corporate Advisory LLC +2111347,TSG Advice Partners LLC +2111352,URS Advisory LLC +2111359,Frec Markets Inc. +2111360,Tenzing Financial LLC +2111369,VUB Generali dochodkova spravcovska spolocnost a.s. +2111372,GRANITE ISLANDS PRIVATE WEALTH LLC +2111377,Miller Capital Partners Inc. +2111381,J.E. Simmons & Co. P.C. +2111403,LAUREL OAK WEALTH MANAGEMENT LLC +2111406,Blue Line Capital LLC / IL +2111409,Livet Wealth LLC +2111427,Worthington Financial Partners LLC +2111429,Ridge Creek Global Inc +2111450,Appalachian Capital Management Ltd +2111462,DiPaolo Financial Group Inc. +2111463,Legacy Wealth Advisors LLC +2111530,71 West Capital Partners +2111541,Greenline Wealth Management LLC +2111587,Donalies Financial Planning LLC +2111628,Laura & John Arnold Foundation +2111640,Hyposwiss Advisors SA +2111652,DECISION INVESTMENTS INC +2111697,Advisory Advocates LLC +2111703,Keenan LLC +2111719,Legend Capital Advisors LLC +2111728,Wealth High Governance Asset Management Ltda. +2111759,Braeburn Wealth Management LLC +2111761,Jacobs Equity LLC +2111764,AFFINITY WEALTH LLC +2111795,Strong Retirement Solutions LLC +2111802,Bauman Advisory Group LLC +2111807,Veratis Advisors Inc. +2111821,Clearwave Capital LLC +2111825,Van Diest Capital LLC +2111830,Phillip James Consulting Co. +2111919,LRZ CAPITAL LLC +2111920,Financial Planning Fort Collins LLC +2111931,TMB Capital Partners LLC +2111980,Lexington Hill Partners LLC +2111996,Sunstone Asset Management LP +2112003,Crusonia Wealth Advisors LLC +2112005,Tempo Wealth LLC +2112006,MCGUIRE CAPITAL ADVISORS INC +2112078,Wealth Intelligence LLC +2112081,First Growth Capital LLC +2112099,Financial Concepts Unlimited Inc. +2112134,Coastline Complete Wealth LLC +2112151,Torrey-Payne Wealth Management LLC +2112179,CrossGen Wealth LLC +2112205,Capstone Wealth Management Inc. +2112239,McLaughlin Asset Management Inc. +2112280,Walser Wealth Management Company A Ltd Liability Co +2112370,Monetary Solutions Ltd +2112547,Titan Investment Management LLC +2112570,Junk Investment Group LLC +2112636,BDFS Capital LLC +2112646,LARCH CAPITAL PARTNERS LLC +2112884,RIHO Partners LLC +2112907,S Harris Financial Group LLC +2112951,GuidedMoney LLC +2113129,Ketron Financial +2113155,Integrated Wealth Management +2113267,JMN Financial LLC +2113282,AMG Asset Management Group Inc. +2113283,THRYVE WEALTH MANAGEMENT LLC +2113408,Dedeker Financial LLC +2113426,Sankala Group LLC +2113492,Sanchez Gaunt Capital Management LLC +2113496,ALONGSIDE LLC +2113507,PeakShares LLC +2113615,CLEAR WAVE WEALTH MANAGEMENT LLC +2113621,ARWA LLC +2113629,Amicus Financial Advisors LLC +2113632,WMS Group LLC +2113810,Mission Financial Group LLC +2113908,OakTrust Wealth Advisors LLC +2113915,EJMK Ventures LLC +2113970,Financially in Tune LLC +2113997,Metatron Capital SICAV plc - Metatron Long Term Equity Fund +2114167,CFO CAPITAL MANAGEMENT LLC +2114194,Sentinel Dome Partners LLC +2114344,STRATEGIC ADVISORY PARTNERS LLC +2114442,Tulsa Wealth Advisors INC +2114448,Cedarwood Wealth LLC +2114481,Koenig Investment Advisory LLC +2114679,Rubicon Advisors GP +2114795,Gunpowder Capital Management LLC dba Oliver Wealth Management +2114882,Broadhurst Jeffrey B +2115120,Purpose Unlimited Inc. +2115141,DUTCH ASSET Corp +2115182,Taylor Securities Services Inc. +2115210,Compass Financial Group Inc. (Ohio) +2115254,Basecamp Wealth Advisors LLC +2115327,Virginia Estate & Retirement Planning Advisors Inc. +2115370,Oakmont Advisory Group LLC +2115416,Midway Capital Research & Management +2115523,ADVISORTRUST PARTNERS LLC +2115533,Carter Financial LLC +2115631,Henson-Edgewater Management LLC +2116061,Storen Legacy Partners LLC +2116118,Redwood Family Wealth LLC +2116119,FIDUCIARY FINANCIAL ADVISORS +2116315,Oak Barrel Wealth Advisory LLC +2116323,Forty-three Eighteen Advisors LLC +2116327,Fund Advisors of America Inc/FL +2116339,Lifetime Wealth Management P.C. +2116771,Ahara Advisors LLC +2116904,PURSUIT WEALTH STRATEGIES LLC +2117069,Green Ridge Wealth Planning LLC +2118051,Tortuga Wealth Management Inc +2118181,Sentinel Advisory Group LLC +2118274,Larry Mathis Financial Planning LLC +2118292,Galaxy Digital Capital Management GP LLC +2118318,LightSquare Wealth Management LLC +2118328,Vertrix Wealth Management LLC +2118380,Downshift Financial LLC +2118914,Titiun Yejiel +2118977,Estate Planners Group LLC +2119167,Pangea Capital Gestao de Recursos Ltda. +2119283,Meadowbrook Advisors Group LLC +2119881,Intentional Wealth Strategies LLC +2120450,Wealth Care LLC +2121075,Cassady Wealth & Retirement Planning LLC +2121390,Center for Wealth Management Advisory +2121836,TCFG Investment Advisors LLC +2121884,Integrity Wealth Partners LLC +2122180,JOURNEY RETIREMENT PLANNING & INVESTMENT MANAGEMENT LLC +2122473,American State Bank (Iowa) +2122970,RED REEF ADVISORS LLC +2123012,DWR WEALTH MANAGEMENT LLC +2123661,PCB Capital LLC +2123735,NorthAvenue LLC +2123739,Capital Asset Managemnet LLC +2123943,Merited Wealth LLC +2124354,Elevation Advisory Partners LLC +2124549,Capital Investment Management Inc. +2124777,Oasis Advisors LLC +2124783,Roan Capital Partners +2124867,TIAA Wealth Investment Management LLC +2126147,Hamrick Investment Counsel llc +2126257,Prota Financial LLC +2126518,KRM WEALTH MANAGEMENT L.L.C. +2126627,Sanchez Levi Garrett +2126773,Bravera Wealth +2126814,Unify Financial Advisors +2126837,Arch Global Advisors LLC +2127120,AtlasMark Financial Inc +2127147,Innovative Asset Advisors Group LLC +2127408,PW Nova Financial Services LLC +2127509,SEB Asset Management AB +2127635,RETIREMITTEN FINANCIAL LLC +2127787,Z3 Capital Partners LLC +2127791,Navigate Wealth Management LLC +2127798,Northern Lights Advisors Inc. +2127823,Emerald Investment Advisers LLC +2128243,Athena Wealth Management LLC +2128691,Capital Squared Financial LLC +2129058,Dala Group LLC +2129318,Norris Financial Group LLC +2129719,Crystal Cove Asset Management LLC +2129751,Decker Wealth Management LLC +2129795,Madson Wealth Advisors Inc +2129940,E-Wealth Partners LLC +2130022,ALLSTREET CAPITAL ADVISORS LLC +2130067,Flatrock Wealth Partners LLC +2130487,Militia Capital Management LLC +2130930,STONE LOFT WEALTH MANAGEMENT LLC +2130988,TRB Wealth Management LLC +2131047,Capital Advisor Network LLC +2131053,Parrish Capital LLC +2131132,Kentucky Farm Bureau Mutual Insurance Co +2131139,Medallion Wealth Advisors LLC +2131483,Henshaw Capital LLC +2131510,LEGACY FINANCIAL INDEPENDENT ADVISORS LLC +2131511,Philadelphia Investment Partners LLC +2131759,Investment Management Trust LLC +2131949,Altium Investment Strategies LLC +2132054,MTM FINANCIAL GROUP LLC +2132074,Collaborative Fund Advisors LLC +2132204,My Portfolio Guide LLC +2132253,Allied Private Wealth LLC +2132497,Navigation Group LLC +2132506,Alessandra Capital Management LLC +2132534,Sidoxia Capital Management LLC +2132639,Causey Wealth LLC +2132683,Summit Portfolio Management LTD. +2133354,Forum Finance Group S.A. +2133429,TrustWell Financial Advisors LLC +2133435,Bucket List Wealth Management LLC +2133484,Good Harbor Advisors Inc. +2133489,Gerald Baker Financial Group LLC +2133532,McKinney Capital Management LLC +2133547,CFG Wealth Management Services Inc. +2133805,Owl Creek Wealth Partners LLC +2133911,FinArc Investments Inc. +2133972,Frisco Financial Planning LLC +2133991,1015 Capital Partners LLC +2134013,Wellington Grp LLC +2134516,MIRAMONTES CAPITAL LLC +2134631,J. M. Brown & Associates Inc. +2134737,Guilbault Capital LLC +2134744,CPWA LLC +2134767,Cedrus Wealth Group LLC +2134779,Avail Investment Partners LLC +2134831,GLR Partners LLC +2134841,First Nebraska Trust Co +2134889,CARLSON FINANCIAL INC. +2135047,Bull Harbor Capital LLC +2135110,Pioneer Family Office LLC +2135125,Peck Wealth Management LLC +2135126,CapitalatWork S.A. +2135144,Walsh & Associates LLC +2135169,3 FACTOR INDEXING LLC +2135239,PlanVest Financial Inc +2135269,Financial Solutions Advisory Group Inc. +2135327,Purewater Capital LLC +2135336,Madrid Financial Services +2135356,Encore Global Management LP +2135379,Ares Systematic Credit Ltd +2135539,SIMA Wealth Partners LLC +2135623,PATHWAY WEALTH MANAGEMENT LLC +2135644,Cornerstone Wealth LLC/TN +2135773,BankChampaign National Association +2135775,Orographic Financial Advisors LLC +2135817,Cadia Private Client LLC +2135840,Mullaney Keating & Wright Inc. +2136099,Independent Wealth Advisors LLC +2136102,Blom & Howell Financial Planning Inc. +2136331,J. R. Prunier Capital Management LLC +2136442,Essential Partners LLC +2136566,LAVELLE CAPITAL LP +2136600,Woodward Financial Advisors Inc. +2136877,Burnham & Co LLC +2136908,Bellars Harris Wealth Management LLC +2136913,Krane Financial Solutions LLC +2137268,Lionhunter Capital Management LLC +2137280,Midwest Financial Network LLC +2137372,Atomic Invest LLC +2137383,Contango Wealth Management LLC +2137428,Positano Wealth Management Ltd +2137529,Regatta Research & Money Management +2137933,Stillwater Private Wealth LLC +2139665,Planning Strategies Inc. +2139748,Robinswood Financial LLC +2140230,Three Bearings Fiduciary Advisors Inc. +2140235,Evanson Financial LLC +2140428,S&A Financial Services Inc. +2140431,Optimus Capital Advisors LLC +2140580,LDIC Inc. +2140601,Capstone Wealth Management Group Inc. +2140714,Southern Financial Group LLC +2140766,Delaney Capital Management Ltd. +2140776,Steelhead Wealth Management LLC +2141005,Hamilton Capital Partners Inc. +2141570,Paladin Partners LLC +2141692,LANGLEY WEALTH MANAGEMENT LLC +2142299,Stonebridge Financial Group LLC/CA +2142345,TTRF Capital Ltd +2143070,Financial Management Inc. +2143895,TriCert Investment Counsel Inc. +2144052,PRAIRIE ADVISORY LLC +2144243,Daniel Investment Group Inc. +2145102,Bridgelight Financial Advisors Inc. +2145170,Essential Investment Partners LLC +2145383,Carolina Wealth Management Inc +2145782,PENSION & WEALTH MANAGEMENT ADVISORS INC. +2145868,SACKS & ASSOCIATES LLC +2146052,Jupiter Topco LLC +2146304,Odyssey Group LLC +2146357,N10 WEALTH LLC +2146729,Kroeger Financial Partners +2146839,Kinetic Wealth Investment Advisors LLC +2146922,N10 ASSETS LLC +2146944,Evelyn Partners Group Ltd +2146947,R2 Capital Strategies LLC +2147212,ADKINS SEALE CAPITAL MANAGEMENT LLC +2147314,BEACON HILL WEALTH MANAGEMENT LTD. +2147372,Noble Wealth Partners LLC +2147643,Advisory Solutions Group LLC +2147967,XP Advisory US Inc. +2148496,Rose Capital Advisors LLC +2148716,Schuylkill Financial LLC +2148724,COMPASS FINANCIAL MANAGEMENT LLC +2148757,NI ACQUISITIONS COMPANY LLC +2148930,NBH Bank +2148988,AlphaGrep UK Ltd +2149002,GAINLINE FINANCIAL PARTNERS LLC +2149018,Shah Wealth Advisors LLC +2149024,WILLIAM JOHN SEMPOLINSKI +2149091,Arbor Wealth Management LLC\AZ +2149098,SUCCESSION FINANCIAL INC. +2149104,MW ADVISORY LLC +2149209,TENET WEALTH PARTNERS LLC +2149656,Maloon Powers Pitre Higgins & Dennehy LLC +2149795,Markowski Investments +2149839,Old Mission Investment Co LLC +2149844,Montgomery Financial Services LLC +2149849,OceanIQ Capital LLC +2149882,Highland Investment Advisors LLC +2149913,Outlook Capital Management LLC +2149918,Turner Financial Group Inc. +2149956,MLG Wealth Management DBA Pine Grove Financial Group +2150168,Timbuktu Capital Management LLC +2150170,Oakmont Investment Advisors Inc. +2150231,TOP Private Wealth LLC. +2150482,IKE Capital LLC +2150492,Thompson David Blair +2150676,Advus Financial Partners LLC +2151928,Kachkovsky & Fisher Inc. +2152085,Willow Creek Capital Management Inc. +2152629,Range Advisory LLC +2153025,Twin Peaks Capital Ltd +2153211,Marathon Wealth Advisors LLC +2153440,Lotus Asset Management LLC +2153446,Spectrum Advisors Inc. diff --git a/output/alternative/sec/13f/meta.zip b/output/alternative/sec/13f/meta.zip new file mode 100644 index 0000000..2343a4f Binary files /dev/null and b/output/alternative/sec/13f/meta.zip differ diff --git a/output/alternative/sec/13f/msft.zip b/output/alternative/sec/13f/msft.zip new file mode 100644 index 0000000..e326f88 Binary files /dev/null and b/output/alternative/sec/13f/msft.zip differ diff --git a/output/alternative/sec/13f/qsr.zip b/output/alternative/sec/13f/qsr.zip new file mode 100644 index 0000000..ed4a969 Binary files /dev/null and b/output/alternative/sec/13f/qsr.zip differ diff --git a/output/alternative/sec/13f/uber.zip b/output/alternative/sec/13f/uber.zip new file mode 100644 index 0000000..576af28 Binary files /dev/null and b/output/alternative/sec/13f/uber.zip differ diff --git a/tests/SEC13FIncrementalRealDayTests.cs b/tests/SEC13FIncrementalRealDayTests.cs new file mode 100644 index 0000000..c3376b5 --- /dev/null +++ b/tests/SEC13FIncrementalRealDayTests.cs @@ -0,0 +1,165 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using NUnit.Framework; +using QuantConnect.Configuration; +using QuantConnect.DataProcessing; +using QuantConnect.DataSource; + +namespace QuantConnect.DataLibrary.Tests +{ + /// + /// Runs one real day of EDGAR filings as an incremental run, on top of a real published history. + /// + /// The two bugs the review found were exactly here and neither unit test caught them: the day's + /// archive lacked tables the processor reads, so every daily run threw, and an incremental run + /// published each touched ticker's zip holding that day alone, cutting the security's history + /// down to it. Both are covered by unit tests now, on archives this repository writes itself. + /// This one uses the archive EDGAR really served and a shelf the processor really produced, so + /// the day is read as the job reads it. + /// + /// Explicit: SEC13F_DAY_RAW is the raw folder holding archives/edgar-yyyyMMdd_form13f.zip, + /// SEC13F_DAY_PUBLISHED a published dataset folder, SEC13F_DAY_DATA a LEAN data folder and + /// SEC13F_DAY the day to read. + /// + [TestFixture, Explicit("Needs a cached EDGAR day and a published history on disk")] + public class SEC13FIncrementalRealDayTests + { + private string _root; + private string _previousDataFolder; + + [SetUp] + public void SetUp() + { + _root = Path.Combine(Path.GetTempPath(), $"sec-13f-day-{Guid.NewGuid():N}"); + Directory.CreateDirectory(_root); + _previousDataFolder = Config.Get("data-folder", null); + } + + [TearDown] + public void TearDown() + { + Config.Reset(); + if (_previousDataFolder != null) + { + Config.Set("data-folder", _previousDataFolder); + } + + Globals.Reset(); + Directory.Delete(_root, true); + } + + [Test] + public void ARealEdgarDayIsAddedToARealPublishedHistory() + { + var raw = Environment.GetEnvironmentVariable("SEC13F_DAY_RAW"); + var published = Environment.GetEnvironmentVariable("SEC13F_DAY_PUBLISHED"); + var data = Environment.GetEnvironmentVariable("SEC13F_DAY_DATA"); + Assert.IsTrue(Directory.Exists(raw), "set SEC13F_DAY_RAW to the raw folder of the cached archives"); + Assert.IsTrue(Directory.Exists(published), "set SEC13F_DAY_PUBLISHED to a published dataset folder"); + Assert.IsTrue(Directory.Exists(data), "set SEC13F_DAY_DATA to a LEAN data folder"); + + var day = DateTime.ParseExact(Environment.GetEnvironmentVariable("SEC13F_DAY") ?? "", + "yyyyMMdd", CultureInfo.InvariantCulture); + + Config.Set("data-folder", data); + Globals.Reset(); + + // A day the raw folder does not already hold is fetched, which is what makes this a test + // of the archive the processor writes rather than of one already on disk. The SEC's user + // agent then has to be configured, as it does for a real run. + + // The shelf the job hands over, holding what is published today. Copied rather than read + // in place, since a run that wrote into it would be rewriting the live dataset. + var shelf = Path.Combine(_root, "processed", "alternative", "sec"); + var shelfDataset = Path.Combine(shelf, SEC13FHolding.ReportFolder); + Directory.CreateDirectory(shelfDataset); + foreach (var file in Directory.GetFiles(published)) + { + File.Copy(file, Path.Combine(shelfDataset, Path.GetFileName(file))); + } + + var before = Dates(Path.Combine(shelfDataset, "aapl.zip")); + Assert.IsNotEmpty(before, "the shelf carries no AAPL history to add to"); + + var destination = Path.Combine(_root, "out", "alternative", "sec"); + var archive = new SEC13FDownloader.Archive( + SEC13FEdgarDay.ArchiveName(day), $"https://localhost/{SEC13FEdgarDay.ArchiveName(day)}", + day, day, IsDaily: true); + + using (var downloader = new SEC13FDownloader(destination, shelf, day, raw)) + { + downloader.ProcessArchive(archive); + downloader.FlushPendingRows(); + downloader.FinalizeSecurityFiles(); + } + + var written = Path.Combine(destination, SEC13FHolding.ReportFolder); + var touched = Directory.GetFiles(written, "*.zip"); + TestContext.Out.WriteLine($"{touched.Length} securities touched by {day:yyyy-MM-dd}"); + + Assert.IsNotEmpty(touched, "the day published nothing at all, which is the bug that threw"); + + // Every zip this run wrote must still hold what was published for that security, or the + // run has just cut its history down to one day. + var entry = $"{day:yyyyMMdd}.csv"; + var lost = 0; + var gained = 0; + foreach (var path in touched) + { + var ticker = Path.GetFileNameWithoutExtension(path); + var now = Dates(path); + var was = Dates(Path.Combine(shelfDataset, $"{ticker}.zip")); + + if (was.Except(now).Any()) + { + lost++; + } + + if (now.Contains(entry)) + { + gained++; + } + } + + TestContext.Out.WriteLine($"{gained} of them carry {entry}, {lost} lost a published date"); + Assert.AreEqual(touched.Length, gained, $"a touched security does not carry {entry}"); + Assert.AreEqual(0, lost, "a security lost dates it had published"); + + var after = Dates(Path.Combine(written, "aapl.zip")); + TestContext.Out.WriteLine($"aapl: {before.Count} dates published, {after.Count} after the day"); + Assert.IsEmpty(before.Except(after).ToList(), "AAPL lost published dates"); + Assert.IsTrue(after.Contains(entry), "AAPL did not gain the day"); + } + + /// The entry names a security's zip holds, or an empty set when there is no zip. + private static HashSet Dates(string path) + { + if (!File.Exists(path)) + { + return new HashSet(); + } + + using var zip = ZipFile.OpenRead(path); + return zip.Entries.Select(entry => entry.Name).ToHashSet(); + } + } +} diff --git a/tests/SEC13FPilotTests.cs b/tests/SEC13FPilotTests.cs new file mode 100644 index 0000000..ad1bd3d --- /dev/null +++ b/tests/SEC13FPilotTests.cs @@ -0,0 +1,248 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using QuantConnect.Configuration; +using QuantConnect.DataProcessing; +using QuantConnect.DataSource; + +namespace QuantConnect.DataLibrary.Tests +{ + /// + /// Runs the processor over one window of real SEC tables and reports what it wrote: how many + /// files, how large, how long, and the worst day. This is the pilot the redesign is measured on + /// before any history is generated, so it prints its numbers rather than only asserting on them. + /// + /// Explicit: it needs the SEC's own tables in the folder SEC13F_PILOT_RAW names, which is a + /// directory holding SUBMISSION.tsv, COVERPAGE.tsv, SUMMARYPAGE.tsv and INFOTABLE.tsv, and a + /// LEAN data folder with map files and security-database.csv in SEC13F_PILOT_DATA. + /// + [TestFixture, Explicit("Needs a window of real SEC tables on disk")] + public class SEC13FPilotTests + { + private string _root; + private string _previousDataFolder; + + [SetUp] + public void SetUp() + { + _root = Path.Combine(Path.GetTempPath(), $"sec-13f-pilot-{Guid.NewGuid():N}"); + Directory.CreateDirectory(_root); + _previousDataFolder = Config.Get("data-folder", null); + } + + [TearDown] + public void TearDown() + { + // The data folder is process wide and the resolver caches the one it was built with. + Config.Reset(); + if (_previousDataFolder != null) + { + Config.Set("data-folder", _previousDataFolder); + } + + Globals.Reset(); + Directory.Delete(_root, true); + } + + [Test] + public void OneWindowIsPublishedAndMeasured() + { + var raw = Environment.GetEnvironmentVariable("SEC13F_PILOT_RAW"); + Assert.IsTrue(Directory.Exists(raw), "set SEC13F_PILOT_RAW to a folder of SEC 13F tables"); + + var data = Environment.GetEnvironmentVariable("SEC13F_PILOT_DATA"); + Assert.IsTrue(Directory.Exists(data), "set SEC13F_PILOT_DATA to a LEAN data folder"); + + Config.Set("data-folder", data); + Globals.Reset(); + + var window = WindowOf(raw); + var name = $"{window.Start:ddMMMyyyy}-{window.End:ddMMMyyyy}_form13f.zip".ToLowerInvariant(); + var archive = new SEC13FDownloader.Archive( + name, $"https://localhost/{name}", window.Start, window.End); + + // The archive is handed over on disk in the cache the downloader reads, so the run needs + // nothing from the network. The path is the one the job hands it, which is the raw + // folder already narrowed to the vendor. + var rawRoot = Path.Combine(_root, "raw", "alternative", SEC13FDownloader.VendorName); + var archives = Path.Combine(rawRoot, SEC13FHolding.ReportFolder, "archives"); + Directory.CreateDirectory(archives); + BuildArchive(raw, Path.Combine(archives, name)); + + // The same two paths the job hands over: the temporary output and the published shelf, + // each already narrowed to the vendor. The downloader adds the dataset folder itself. + var destination = Path.Combine(_root, "out", "alternative", SEC13FDownloader.VendorName); + var processed = Path.Combine(_root, "processed", "alternative", SEC13FDownloader.VendorName); + Directory.CreateDirectory(processed); + + var clock = Stopwatch.StartNew(); + using (var downloader = new SEC13FDownloader(destination, processed, null, rawRoot)) + { + // The N-PORT crosswalk is built from four quarterly archives of about 450 MB each. + // SEC13F_PILOT_CROSSWALK points at one the real run already built, so the pilot uses + // the same map without downloading 1.8 GB or asking EDGAR which quarters exist. + // Without it the run still publishes, with the coverage that the security database + // alone gives, and the difference is reported either way. + downloader.TickerCrosswalk = ReadCrosswalk( + Environment.GetEnvironmentVariable("SEC13F_PILOT_CROSSWALK")); + + downloader.ProcessArchive(archive); + downloader.FlushPendingRows(); + downloader.FinalizeSecurityFiles(); + } + + clock.Stop(); + + var folder = Path.Combine(destination, SEC13FHolding.ReportFolder); + Assert.IsTrue(Directory.Exists(folder), $"nothing was written to {folder}"); + + Report(folder, clock.Elapsed); + + // Kept for the independent check that reads it back against the raw tables. + var keep = Environment.GetEnvironmentVariable("SEC13F_PILOT_OUT"); + if (!string.IsNullOrWhiteSpace(keep)) + { + CopyTree(folder, keep); + TestContext.Out.WriteLine($"output kept in {keep}"); + } + } + + /// + /// Reads a crosswalk the processor cached earlier: a header of the quarters it was built + /// from, then one CUSIP, ticker and observation date per line. An absent path gives an empty + /// map, which is a run on the security database alone. + /// + private static Dictionary ReadCrosswalk(string path) + { + var map = new Dictionary(StringComparer.Ordinal); + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + { + TestContext.Out.WriteLine("crosswalk none, the security database alone"); + return map; + } + + foreach (var line in File.ReadLines(path)) + { + if (line.StartsWith("#", StringComparison.Ordinal)) + { + TestContext.Out.WriteLine($"crosswalk quarters {line.TrimStart('#')}"); + continue; + } + + var fields = line.Split(','); + if (fields.Length == 3 && fields[0].Length == 9) + { + map[fields[0]] = new SEC13FTickerCrosswalk.Entry( + fields[1], DateTime.ParseExact(fields[2], "yyyyMMdd", null)); + } + } + + TestContext.Out.WriteLine($"crosswalk {map.Count} CUSIPs"); + return map; + } + + /// Copies the run's output somewhere it outlives the fixture's temporary folder. + private static void CopyTree(string from, string to) + { + Directory.CreateDirectory(to); + foreach (var file in Directory.GetFiles(from)) + { + File.Copy(file, Path.Combine(to, Path.GetFileName(file)), overwrite: true); + } + } + + /// Writes the tables into one zip, in the shape the processor reads them from. + private static void BuildArchive(string raw, string path) + { + using var zip = ZipFile.Open(path, ZipArchiveMode.Create); + foreach (var table in Directory.GetFiles(raw, "*.tsv")) + { + zip.CreateEntryFromFile(table, Path.GetFileName(table), CompressionLevel.Fastest); + } + } + + /// The filing dates the window covers, read from the submissions themselves. + private static (DateTime Start, DateTime End) WindowOf(string raw) + { + var submissions = Path.Combine(raw, "SUBMISSION.tsv"); + Assert.IsTrue(File.Exists(submissions), $"no SUBMISSION.tsv in {raw}"); + + var lines = File.ReadLines(submissions).ToList(); + var columns = lines[0].Split('\t').Select((column, index) => (column, index)) + .ToDictionary(pair => pair.column, pair => pair.index); + + var dates = lines.Skip(1) + .Where(line => !string.IsNullOrWhiteSpace(line)) + .Select(line => line.Split('\t')[columns["FILING_DATE"]].Trim().Split(' ')[0]) + .Select(date => DateTime.Parse(date)) + .ToList(); + + return (dates.Min(), dates.Max()); + } + + /// Prints what the run wrote, which is what the pilot exists to find out. + private static void Report(string folder, TimeSpan elapsed) + { + var zips = Directory.GetFiles(folder, "*.zip"); + var bytes = zips.Sum(path => new FileInfo(path).Length); + + var days = 0L; + var rows = 0L; + var worstDay = ("", "", 0L); + + foreach (var path in zips) + { + using var zip = ZipFile.OpenRead(path); + foreach (var entry in zip.Entries) + { + days++; + using var reader = new StreamReader(entry.Open()); + var lines = 0L; + while (reader.ReadLine() != null) + { + lines++; + } + + rows += lines; + if (lines > worstDay.Item3) + { + worstDay = (Path.GetFileNameWithoutExtension(path), entry.Name, lines); + } + } + } + + var managers = Path.Combine(folder, "managers.csv"); + + TestContext.Out.WriteLine($"securities {zips.Length}"); + TestContext.Out.WriteLine($"filing dates written {days}"); + TestContext.Out.WriteLine($"reported positions {rows}"); + TestContext.Out.WriteLine($"bytes on disk {bytes:N0} ({bytes / 1024d / 1024d:F1} MB)"); + TestContext.Out.WriteLine($"bytes per position {(rows == 0 ? 0 : bytes / rows)}"); + TestContext.Out.WriteLine($"busiest entry {worstDay.Item1}#{worstDay.Item2} with {worstDay.Item3} positions"); + TestContext.Out.WriteLine($"managers.csv {(File.Exists(managers) ? File.ReadLines(managers).Count() : 0)} names"); + TestContext.Out.WriteLine($"elapsed {elapsed.TotalSeconds:F1}s"); + + Assert.Greater(rows, 0, "the run published no positions"); + } + } +} diff --git a/tests/SEC13FProcessorTests.cs b/tests/SEC13FProcessorTests.cs new file mode 100644 index 0000000..8fa68a8 --- /dev/null +++ b/tests/SEC13FProcessorTests.cs @@ -0,0 +1,2018 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using NUnit.Framework; +using QuantConnect.Configuration; +using QuantConnect.DataProcessing; +using QuantConnect.DataSource; +using QuantConnect.Securities; + +namespace QuantConnect.DataLibrary.Tests +{ + /// + /// Unit tests for the 13F processor. + /// + /// The data classes had full coverage while the processor, which is where the subtle logic + /// lives, had none. Every case below is a defect that actually reached a run and was caught by + /// hand: a CUSIP left-padded into a different security, a thousand-fold step in the middle of + /// the value history, and a file written in quarter order that LEAN then silently truncated. + /// + [TestFixture] + public class SEC13FProcessorTests + { + /// The filing date the counting tests file on, when the date itself is not the point. + private static readonly DateTime Filed = new(2024, 2, 14); + + private string _root; + private bool _configSeeded; + private string _previousDataFolder; + private string _previousLookupDate; + + [SetUp] + public void SetUp() + { + _root = Path.Combine(Path.GetTempPath(), $"sec-13f-tests-{Guid.NewGuid():N}"); + Directory.CreateDirectory(_root); + } + + [TearDown] + public void TearDown() + { + if (_configSeeded) + { + // The data folder is process wide and the resolver caches the one it was built with, + // so a fixture left behind would be read by whatever runs next. A key that was absent + // goes back to absent: written back as an empty string it left Globals.DataFolder + // empty for every later test in the process. + Config.Reset(); + if (_previousDataFolder != null) + { + Config.Set("data-folder", _previousDataFolder); + } + + if (_previousLookupDate != null) + { + Config.Set("map-file-provider-lookup-date", _previousLookupDate); + } + + Globals.Reset(); + _configSeeded = false; + } + + if (Directory.Exists(_root)) + { + Directory.Delete(_root, true); + } + } + + // ---- CUSIP normalisation -------------------------------------------------------------- + + [TestCase("037833100")] // Apple + [TestCase("464287226")] // iShares Core US Aggregate Bond + [TestCase("72201R205")] + public void ANineCharacterCusipIsLeftAlone(string cusip) + { + // Most reported CUSIPs already carry their check digit and must not be touched at all. + Assert.AreEqual(cusip, SEC13FDownloader.NormalizeCusip(cusip)); + } + + [TestCase("37833100", "037833100")] // Apple, one leading zero eaten + [TestCase("2824100", "002824100")] // Abbott, two eaten + [TestCase("84670702", "084670702")] + public void AShortCusipMissingItsLeadingZerosIsPadded(string reported, string expected) + { + // The full history carries 12,372 short CUSIPs, most of them an identifier a spreadsheet + // somewhere read as a number and stripped the leading zeros off. + Assert.AreEqual(expected, SEC13FDownloader.NormalizeCusip(reported)); + } + + [TestCase("46428722", "464287226")] // iShares Core US Aggregate Bond + [TestCase("72201R20", "72201R205")] + public void AShortCusipMissingItsCheckDigitIsCompletedInstead(string reported, string expected) + { + // The other reason a CUSIP arrives short, and it needs the opposite repair. Left-padding + // "46428722" would have produced 046428722, a perfectly well-formed identifier belonging + // to a different security, so the two cases are told apart by the check digit itself: + // the padded reading is only accepted when it checks out. + Assert.AreEqual(expected, SEC13FDownloader.NormalizeCusip(reported)); + } + + [TestCase("037833100")] // Apple + [TestCase("594918104")] // Microsoft + [TestCase("67066G104")] // NVIDIA, the letter case + [TestCase("464287226")] // iShares Core US Aggregate Bond + [TestCase("002824100")] // Abbott, the leading-zero case + public void TheCheckDigitAgreesWithRealCusips(string cusip) + { + // Everything above rests on the check digit being right, so it is held against real + // published identifiers rather than against itself. + Assert.AreEqual(cusip[8], SEC13FDownloader.ComputeCusipCheckDigit(cusip.Substring(0, 8)), + $"the computed check digit disagrees with the published {cusip}"); + } + + [TestCase("")] + [TestCase("0")] + [TestCase("ABC")] + [TestCase("12345")] + public void JunkTooShortToBeACusipIsRejected(string cusip) + { + // Below six characters there is not enough of an identifier left to repair, and guessing + // one would attribute somebody's positions to whichever security the guess landed on. + Assert.IsNull(SEC13FDownloader.NormalizeCusip(cusip)); + } + + // ---- The constructed US ISIN ---------------------------------------------------------- + + [TestCase("037833100", "US0378331005")] // Apple + [TestCase("02079K305", "US02079K3059")] // Alphabet Class C + public void TheConstructedIsinMatchesTheRealOne(string cusip, string isin) + { + // This is the second step of the identity chain and it is pure arithmetic, so it is + // either exactly right or it silently resolves to nothing. It lifted measured coverage + // from 76.1% to 88.9% of reported value, which is only true while it is exact: both of + // these are checked against the security's real published ISIN. + Assert.AreEqual(isin, SEC13FDownloader.BuildUnitedStatesIsin(cusip)); + } + + // ---- The VALUE unit break at 2023 ------------------------------------------------------ + + [TestCase("2022-11-14", 1000000)] // reported in thousands, scaled up + [TestCase("2022-12-31", 1000000)] // the last day of the old unit + [TestCase("2023-01-01", 1000)] // the first day of the new one, left alone + [TestCase("2023-02-14", 1000)] + public void ValueIsScaledOnlyForFilingsMadeBefore2023(string filingDate, decimal expected) + { + // VALUE changed unit with the 2023 filings: through 2022 it is thousands of dollars, + // from 2023 whole dollars. Measured on the real archives as the median VALUE/SSHPRNAMT + // of share lines, which is a price per share: 0.0460 in NOV-2022 against 39.86 in + // FEB-2023. Left unscaled this puts a 1000x step in the middle of the published history + // and breaks every comparison that crosses it. The cut is on FILING_DATE rather than on + // the reported period, because the rule changed for filings made from January 2023. + var filing = DateTime.Parse(filingDate); + var holdings = ReadInfoTable( + new[] { Line("0000000000-00-000001", "037833100", "1000", "100", "SH") }, + Submission("0000000000-00-000001", filing, new DateTime(2022, 9, 30))); + + Assert.AreEqual(1, holdings.Count); + Assert.AreEqual(expected, holdings.Values.Single().ReportedValue); + } + + [Test] + public void AFilerThatReportedDollarsBefore2023IsNotScaled() + { + // The rule was thousands, but not every filer followed it. In Apple's December 2019 + // quarter 85 of 5,365 lines were already in dollars, and multiplied by a thousand they + // made 88 percent of the total: an implied $2,577 a share against a $293.65 close. Each + // line is held against the quarter-end close, which needs no other filer. + SeedCloses("20220930", ("aapl", 150m)); + var filing = new DateTime(2022, 11, 14); + var quarter = new DateTime(2022, 9, 30); + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "15", "100", "SH"), + Line("0000000000-00-000002", "037833100", "15000", "100", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 111), + Submission("0000000000-00-000002", filing, quarter, cik: 222)); + + Assert.AreEqual(15000m + 15000m, holdings.Values.Single().ReportedValue, + "the one in thousands is scaled, the one already in dollars is not"); + } + + [Test] + public void AFilerStillReportingThousandsAfter2023IsScaled() + { + // The same break from the other side: 548 of Apple's March 2026 lines were still in + // thousands, which left the published value five percent short. The quarter ends on a + // Sunday, so the close is the Friday's. + SeedCloses("20231229", ("aapl", 150m)); + var filing = new DateTime(2024, 2, 14); + var quarter = new DateTime(2023, 12, 31); + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "15000", "100", "SH"), + Line("0000000000-00-000002", "037833100", "15", "100", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 111), + Submission("0000000000-00-000002", filing, quarter, cik: 222)); + + Assert.AreEqual(2 * 15000m, holdings.Values.Single().ReportedValue); + } + + [Test] + public void OnlyAShareLineIsHeldAgainstTheClose() + { + // A bond at par against a stock near $1,000 sits on the thousands step by chance, and so + // can an option line. Neither has a price of its own, so both keep their filing's unit. + SeedCloses("20231229", ("aapl", 1000m)); + var filing = new DateTime(2024, 2, 14); + var quarter = new DateTime(2023, 12, 31); + var lines = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "100000", "100", "SH"), + Line("0000000000-00-000001", "037833100", "100", "100", "PRN"), + Line("0000000000-00-000001", "037833100", "100", "100", "SH", putCall: "Call") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 111)).Values.Single().Lines; + + Assert.AreEqual(new[] { 0, 0, 0 }, lines.Select(line => line.ValueScale).ToArray()); + } + + [Test] + public void AFieldTheFilingLeftEmptyIsNotAZero() + { + var line = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "", "100", "SH", votingSole: "", votingShared: "0", votingNone: "") + }, + Submission("0000000000-00-000001", Filed, new DateTime(2023, 12, 31))).Values.Single().Lines.Single(); + + Assert.IsNull(line.ReportedValue); + Assert.IsNull(line.VotingSole); + Assert.IsNull(line.VotingNone); + Assert.AreEqual(0m, line.VotingShared); + Assert.AreEqual(100m, line.Amount); + } + + [TestCase("1, 2", "1;2")] + [TestCase("9,10,11", "9;10;11")] + [TestCase("1 2", "1;2")] + [TestCase("1;2", "1;2")] + [TestCase("NONE", "")] + [TestCase("0", "")] + [TestCase("", "")] + public void TheOtherManagersAreJoinedBySingleSemicolons(string filed, string expected) + { + // A comma became a space and every space a semicolon, so "9, 10" was published as "9;;10". + Assert.AreEqual(expected, SEC13FDownloader.FormatOtherManagers(filed)); + } + + [TestCase("G1151C101", true)] + [TestCase("N07059210", true)] + [TestCase("037833100", false)] + [TestCase("", false)] + public void ACinsOpensWithALetter(string cusip, bool expected) + { + Assert.AreEqual(expected, SEC13FDownloader.IsCins(cusip)); + } + + [TestCase("03783#100")] + [TestCase("0378-3100")] + [TestCase("N/A")] + public void ACusipNoIsinCanHoldResolvesToNothing(string cusip) + { + // Building the ISIN threw on the first such character and ended the run. + SeedMapFiles("aapl"); + using var downloader = Downloader(); + downloader.TickerCrosswalk = new Dictionary(); + + Assert.IsNull(downloader.ResolveSecurity(cusip, Filed)); + } + + [Test] + public void TheUnitIsDecidedWithoutLookingAtAnyOtherFiling() + { + // The unit used to come from the median of every filing in the three month window, so a + // filing made on its first day was corrected with filings made weeks later. Read alone, + // the way the daily job reads a day, the same filing comes out the same. + SeedCloses("20220930", ("aapl", 150m)); + var filing = new DateTime(2022, 10, 3); + var quarter = new DateTime(2022, 9, 30); + + var alone = ReadInfoTable( + new[] { Line("0000000000-00-000001", "037833100", "15000", "100", "SH") }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 111)); + + Assert.AreEqual(15000m, alone.Values.Single().ReportedValue, + "a lone filing in dollars before 2023 is not multiplied by the thousands rule"); + } + + [Test] + public void ALineInAnotherUnitThanTheRestOfItsFilingIsCorrectedOnItsOwn() + { + // Some filers mix units inside one filing, so a decision per filing left Apple's June 2020 + // quarter 14 percent high: the filing is in thousands, its Apple line in dollars. Held + // against its own close, the line is measured on its own. + SeedCloses("20200630", ("aapl", 360m), ("msft", 200m), ("nvda", 400m)); + var filing = new DateTime(2020, 8, 14); + var quarter = new DateTime(2020, 6, 30); + + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000099", "037833100", "36000", "100", "SH"), + Line("0000000000-00-000099", "594918104", "20", "100", "SH"), + Line("0000000000-00-000099", "67066G104", "40", "100", "SH") + }, + resolve: true, + Submission("0000000000-00-000099", filing, quarter, cik: 99)); + + Assert.AreEqual(36000m, holdings.Single(pair => pair.Key.Cusip == "037833100").Value.ReportedValue, + "the dollar line is not scaled"); + Assert.AreEqual(20000m, holdings.Single(pair => pair.Key.Cusip == "594918104").Value.ReportedValue, + "and the rest of its filing still is"); + } + + [Test] + public void ALineAThousandTimesTheCloseIsBroughtDown() + { + // Three SPY lines of the March 2023 quarter carried VALUE a thousand times the price, and 16 + // percent of the total with it. It is the VALUE that is off, not the share count: the same + // filers reported the same number of shares the quarter before, as 4,578 of the 4,740 + // comparable lines of that quarter did. + SeedCloses("20230331", ("spy", 409.39m)); + var filing = new DateTime(2023, 5, 15); + var quarter = new DateTime(2023, 3, 31); + + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "78462F103", "40939", "100", "SH"), + Line("0000000000-00-000002", "78462F103", "40939000", "100", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 1), + Submission("0000000000-00-000002", filing, quarter, cik: 2)); + + Assert.AreEqual(2 * 40939m, holdings.Values.Single().ReportedValue); + } + + [Test] + public void ARunWithoutTheSecUserAgentKeysFailsBeforeAnyRequest() + { + // The SEC asks automated readers to identify themselves, and the reports dataset reads the + // name and email from these two keys. Without them the run stops instead of calling out + // anonymously. + var previousName = Config.Get("sec-user-agent-company-name", null); + var previousEmail = Config.Get("sec-user-agent-company-email", null); + try + { + Config.Set("sec-user-agent-company-name", string.Empty); + Config.Set("sec-user-agent-company-email", string.Empty); + + using var downloader = Downloader(); + Assert.Throws(() => downloader.Run()); + } + finally + { + // As in TearDown: written back as an empty string, an absent key would stay set. + Config.Reset(); + if (previousName != null) + { + Config.Set("sec-user-agent-company-name", previousName); + } + + if (previousEmail != null) + { + Config.Set("sec-user-agent-company-email", previousEmail); + } + } + } + + [Test] + public void ALineAMillionTimesOffIsLeftToItsFiling() + { + // A price a thousand times below the close in a thousands filing would take a millionfold + // factor. That is a wrong share count, not a unit, and scaling such lines put Apple's + // December 2019 quarter ten percent above its close. + SeedCloses("20191231", ("aapl", 290m)); + var filing = new DateTime(2020, 2, 14); + var quarter = new DateTime(2019, 12, 31); + + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "29", "100", "SH"), + Line("0000000000-00-000002", "037833100", "29", "100", "SH"), + Line("0000000000-00-000002", "037833100", "29", "100", "SH"), + Line("0000000000-00-000002", "037833100", "29", "100000", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 1), + Submission("0000000000-00-000002", filing, quarter, cik: 2)); + + Assert.AreEqual(4 * 29000m, holdings.Values.Single().ReportedValue, + "the odd line keeps the thousands factor its other lines prove"); + } + + [TestCase(true, 1000, 0, new double[0])] // nothing to compare against: the rule stands + [TestCase(false, 1, 0, new double[0])] + [TestCase(true, 1000, 3, new[] { -3.0, -2.9, -3.1 })] // thousands, as the rule says + [TestCase(true, 1, 4, new[] { 0.0, 0.1, -0.1, -3.0 })] // whole dollars before 2023 + [TestCase(false, 1, 2, new[] { 0.1, -0.1 })] + [TestCase(false, 1000, 3, new[] { -3.0, -2.8, 0.1 })] // still thousands after 2023 + [TestCase(false, 1, 10, new[] { -3.0, -2.9 })] // two of ten priced lines on a step: no evidence + [TestCase(true, 1, 10, new[] { -3.0, -2.9 })] // and before 2023 it is not scaled up either + public void AFilingsUnitComesFromHowItsPricesCompareWithTheClose(bool thousandsRule, + decimal expected, int priced, double[] offsets) + { + Assert.AreEqual(expected, SEC13FDownloader.ValueMultiplier(offsets.ToList(), priced, thousandsRule)); + } + + [Test] + public void AFilingWithTheShareCountTypedAsValueIsNotScaled() + { + // A manager's 9 March 2026 filing carried SSHPRNAMT equal to VALUE on every line, a price of + // exactly $1. Against a close near $1,000 that reads as thousands, so the filing was taken to + // report in thousands and every one of its lines was multiplied: Dow's December 2025 quarter + // went from $12.6 billion to $50.2 billion. Most of its lines sit on no unit step, which is no + // evidence of a unit, and a line that sits on none keeps the rule of its filing date. + SeedCloses("20251231", ("aapl", 250m), ("msft", 480m), ("nvda", 180m), ("spy", 680m)); + var filing = new DateTime(2026, 3, 9); + var quarter = new DateTime(2025, 12, 31); + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "1000000", "1000000", "SH"), + Line("0000000000-00-000001", "594918104", "500000", "500000", "SH"), + Line("0000000000-00-000001", "67066G104", "300000", "300000", "SH"), + Line("0000000000-00-000001", "78462F103", "200000", "200000", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 1964189)); + + Assert.AreEqual(1000000m, holdings.Single(pair => pair.Key.Cusip == "037833100").Value.ReportedValue); + Assert.AreEqual(300000m, holdings.Single(pair => pair.Key.Cusip == "67066G104").Value.ReportedValue); + + // MSFT at $480 and SPY at $680 put a $1 price on the thousands step by chance. In a filing + // whose lines mostly sit on no step those landings are not evidence either. + Assert.AreEqual(500000m, holdings.Single(pair => pair.Key.Cusip == "594918104").Value.ReportedValue); + Assert.AreEqual(200000m, holdings.Single(pair => pair.Key.Cusip == "78462F103").Value.ReportedValue); + } + + [Test] + public void ADebtLineKeepsItsAmountTypeInsteadOfBeingFoldedAway() + { + // On a PRN line SSHPRNAMT is the principal amount and VALUE what that debt is worth. + // The aggregated model summed the two into separate measures and lost which line each + // came from; the line is now published as filed, with the unit beside the amount, so a + // reader can tell a bond from a share holding. + var holdings = ReadInfoTable( + new[] { Line("0000000000-00-000001", "037833100", "4800000", "5000000", "PRN") }, + Submission("0000000000-00-000001", new DateTime(2024, 2, 14), new DateTime(2023, 12, 31))); + + var line = holdings.Values.Single().Lines.Single(); + Assert.AreEqual(5000000m, line.Amount); + Assert.AreEqual("PRN", line.AmountType); + Assert.AreEqual(4800000m, line.ReportedValue); + } + + // ---- The incremental run has to see the published history --------------------------------- + + [Test] + public void AnIncrementalRunWithNoPublishedHistoryFails() + { + // The destination arrives empty on every deployment, so a processed-data-directory that + // is wrong or unmounted leaves an incremental run with nothing to merge into. Publishing + // the window on its own would republish thirteen years as three months and return + // success: the files come out the right shape, so no row count tells the two apart. The + // guard runs before anything is fetched. + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 8)); + + Assert.Throws(() => downloader.Run(), + "the run carried on with no history behind it"); + } + + [Test] + public void ARunIntoADestinationAlreadyHoldingFilesFails() + { + // The job hands the destination over empty. A file an earlier run left there would be + // published again, so a run refuses to start on top of one. + var destination = Path.Combine(_root, "out", SEC13FHolding.ReportFolder); + Directory.CreateDirectory(destination); + File.WriteAllText(Path.Combine(destination, "aapl.zip"), "20240214"); + + using var downloader = Downloader(); + + Assert.Throws(() => downloader.RequireEmptyDestination()); + } + + // ---- Publication time and the EDGAR days --------------------------------------------------- + + [Test] + public void TheRebuildHandsOverToEdgarTheDayAfterTheLastDataSet() + { + // The data sets come out in three month batches, so the rebuild reads them as far as they + // reach and EDGAR after that, the way the daily job does. EDGAR can also take over + // earlier, which is how the two sources are compared over a window both carry. + var archives = new List + { + new("01dec2025-28feb2026_form13f.zip", "https://localhost/a.zip", new DateTime(2025, 12, 1), new DateTime(2026, 2, 28)), + new("01mar2026-31may2026_form13f.zip", "https://localhost/b.zip", new DateTime(2026, 3, 1), new DateTime(2026, 5, 31)) + }; + + Assert.AreEqual(new[] { "01dec2025-28feb2026_form13f.zip", "01mar2026-31may2026_form13f.zip" }, + SEC13FDownloader.ArchivesBefore(archives, new DateTime(2026, 6, 1)).Select(archive => archive.Name).ToArray()); + Assert.AreEqual(new[] { "01dec2025-28feb2026_form13f.zip" }, + SEC13FDownloader.ArchivesBefore(archives, new DateTime(2026, 3, 1)).Select(archive => archive.Name).ToArray()); + } + + [Test] + public void EdgarCannotTakeOverInTheMiddleOfADataSet() + { + // The window's filings before the switch would come from the data set and the rest from + // EDGAR only if the archive were cut by date, which it is not: it would be read twice. + var archives = new List + { + new("01mar2026-31may2026_form13f.zip", "https://localhost/b.zip", new DateTime(2026, 3, 1), new DateTime(2026, 5, 31)) + }; + + Assert.Throws(() => SEC13FDownloader.ArchivesBefore(archives, new DateTime(2026, 4, 15))); + } + + [Test] + public void ADailyRunReadsTheWeekdaysNoEarlierRunFoldedIn() + { + // A day already published is never read again, since its filings would be added a second + // time under a later stamp, and a day without an index is tried again by the next run. + // Nor is a day before EDGAR took over from the data sets: the rebuild read those from the + // data set, so they are not in the list, and reading them from EDGAR counted them twice. + var shelf = PublishedShelf(); + File.WriteAllText(Path.Combine(shelf, "edgar-days.txt"), "#from 20260901\n20260903\n20260904\n"); + + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 9)); + downloader.ReadEdgarState(); + + Assert.AreEqual( + new[] { "20260901", "20260902", "20260907", "20260908", "20260909" }, + downloader.EdgarDaysToRead(new DateTime(2026, 8, 30), new DateTime(2026, 9, 9)) + .Select(day => day.ToString("yyyyMMdd")).ToArray()); + } + + [Test] + public void AnIncrementalRunWithoutTheEdgarStateFails() + { + // Without the list of days already published a run cannot tell a new day from one it + // would add a second time, so it stops instead of guessing. + var shelf = PublishedShelf(); + File.Delete(Path.Combine(shelf, "edgar-days.txt")); + SeedPublishedZip(shelf, "aapl", "20240215"); + + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 9)); + + Assert.Throws(() => downloader.RequirePublishedHistoryForIncrementalRun()); + } + + [Test] + public void AMissingDeploymentDateIsAnErrorUnlessTheRebuildIsAskedFor() + { + // An empty date used to mean the full history, which turned a misconfigured nightly job + // into a five gigabyte refetch that exited zero. + var previousDate = Environment.GetEnvironmentVariable("QC_DATAFLEET_DEPLOYMENT_DATE"); + try + { + Environment.SetEnvironmentVariable("QC_DATAFLEET_DEPLOYMENT_DATE", null); + + Config.Set(Program.RebuildHistoryKey, "false"); + Assert.IsFalse(SECProcessingContext.TryParseDeploymentDate(Program.RebuildHistoryKey, out _), "no date and no rebuild asked for"); + + Config.Set(Program.RebuildHistoryKey, "true"); + Assert.IsTrue(SECProcessingContext.TryParseDeploymentDate(Program.RebuildHistoryKey, out var rebuild)); + Assert.IsNull(rebuild, "the rebuild runs over the whole history"); + + Environment.SetEnvironmentVariable("QC_DATAFLEET_DEPLOYMENT_DATE", "20260908"); + Assert.IsTrue(SECProcessingContext.TryParseDeploymentDate(Program.RebuildHistoryKey, out var date)); + Assert.AreEqual(new DateTime(2026, 9, 8), date); + } + finally + { + Environment.SetEnvironmentVariable("QC_DATAFLEET_DEPLOYMENT_DATE", previousDate); + Config.Set(Program.RebuildHistoryKey, "false"); + } + } + + [Test] + public void AnIncrementalRunAcceptsAShelfThatHoldsSecurities() + { + // The same guard from the other side: a published history present is not a failure. Only + // the guard is exercised here, by handing it a deployment date and a shelf and checking + // it does not stop the run before the first archive is fetched. + var processed = PublishedShelf(); + SeedPublishedZip(processed, "aapl", "20240215"); + + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 8)); + + Assert.DoesNotThrow(() => downloader.RequirePublishedHistoryForIncrementalRun()); + } + + /// + /// A shelf directory an incremental run accepts. It used to have to carry a filer state as + /// well, because a distinct count of managers cannot be rebuilt from published totals; with + /// the positions published as filed there is no running count to carry, and the EDGAR days + /// are all the run needs to tell a day already published from a new one. + /// + private string PublishedShelf() + { + var shelf = Path.Combine(_root, "processed", SEC13FHolding.ReportFolder); + Directory.CreateDirectory(shelf); + File.WriteAllText(Path.Combine(shelf, "edgar-days.txt"), "#from 20260601\n20260601\n"); + return shelf; + } + + // ---- Delistings, renames and the tickers that name the files ---------------------------- + + /// + /// Points LEAN's data folder at a map file archive holding exactly the rows given per ticker, + /// so a test can shape a listing, a rename or a delisting. See SeedMapFiles for why the data + /// folder and lookup date are pinned. + /// + private void SeedMapFileRows(params (string Ticker, string[] Rows)[] files) + { + var dataFolder = Path.Combine(_root, "data"); + var mapFiles = Path.Combine(dataFolder, "equity", "usa", "map_files"); + Directory.CreateDirectory(mapFiles); + + var lookupDate = new DateTime(2026, 1, 2); + using (var zip = ZipFile.Open( + Path.Combine(mapFiles, $"map_files_{lookupDate:yyyyMMdd}.zip"), ZipArchiveMode.Create)) + { + foreach (var (ticker, rows) in files) + { + using var entry = new StreamWriter(zip.CreateEntry($"{ticker}.csv").Open()); + foreach (var row in rows) + { + entry.WriteLine(row); + } + } + } + + // Null when absent, so the teardown can tell a missing key from an empty one. + if (!_configSeeded) + { + _previousDataFolder = Config.Get("data-folder", null); + _previousLookupDate = Config.Get("map-file-provider-lookup-date", null); + _configSeeded = true; + } + + Config.Set("data-folder", dataFolder); + Config.Set("map-file-provider-lookup-date", $"{lookupDate:yyyyMMdd}"); + Globals.Reset(); + } + + // ---- N-PORT ticker normalisation ---------------------------------------------------------- + + [TestCase("GOOGL US", "GOOGL")] // the Bloomberg style venue suffix + [TestCase("googl", "GOOGL")] + [TestCase("GOOGL", "GOOGL")] + [TestCase(" brk.b ", "BRK.B")] // share classes survive, they are the LEAN spelling + public void ANPortTickerIsNormalisedBeforeItIsVotedOn(string raw, string expected) + { + // IDENTIFIER_TICKER is free text written by fund administrators, so the same security + // arrives as "GOOGL", "GOOGL US" and "goog" across funds. It is normalised and then + // voted on across every fund that reported the security, rather than trusted row by row. + Assert.AreEqual(expected, SEC13FTickerCrosswalk.Normalize(raw)); + } + + [TestCase("N/A")] + [TestCase("")] + [TestCase(" ")] + [TestCase("912828YV6")] // a CUSIP typed into the ticker column + [TestCase("NOTATICKERATALL")] // longer than any US equity ticker + public void ANPortTickerThatIsNotOneIsRejected(string raw) + { + // The crosswalk feeds a map file lookup, and a ticker the map files do not know still + // produces a plausible looking Symbol with no data behind it, so anything that is not + // ticker-shaped is dropped here rather than downstream. + Assert.IsNull(SEC13FTickerCrosswalk.Normalize(raw)); + } + + [Test] + public void ACrosswalkTickerNamesTheSecurityItNamedWhenTheFundsReportedIt() + { + // Facebook's CUSIP reaches the crosswalk as META, the ticker funds report today. Resolved + // at each filing date instead, META named the Roundhill Metaverse ETF from 2021-06-30 to + // 2022-01-28, so seven months of Facebook's holders were published as that ETF's, and + // before it META named nothing at all. + SeedMapFileRows( + ("meta", new[] { "20120518,fb", "20220608,fb", "20501231,meta" }), + ("metv", new[] { "20210630,meta", "20220128,meta", "20501231,metv" })); + + using var downloader = Downloader(); + downloader.TickerCrosswalk = new Dictionary + { + ["30303M102"] = new("META", new DateTime(2026, 1, 1)) + }; + + var security = downloader.ResolveThroughTicker("30303M102"); + + Assert.AreEqual("FB", downloader.ResolveTicker(security, new DateTime(2021, 10, 15))?.ToUpperInvariant(), + "Facebook's filing lands in Facebook's file, not the ETF's"); + Assert.AreEqual("FB", downloader.ResolveTicker(security, new DateTime(2016, 2, 12))?.ToUpperInvariant(), + "and the years before the ETF existed resolve too"); + Assert.AreEqual("META", downloader.ResolveTicker(security, new DateTime(2023, 2, 14))?.ToUpperInvariant()); + } + + [Test] + public void ACrosswalkTickerThatNamedNoUsSecurityWhenObservedResolvesToNothing() + { + // Barrick's CUSIP reached the crosswalk as ABX, its Toronto ticker, in data reported from + // July 2025. No US security traded as ABX then, and the resolver still returned the + // company that took the ticker in December, so its holders would have gone there. + SeedMapFileRows( + ("abx", new[] { "20200914,eres", "20230703,eres", "20251229,abl", "20501231,abx" }), + ("b", new[] { "19980102,abx", "20181231,abx", "20250508,gold", "20501231,b" })); + + using var downloader = Downloader(); + downloader.TickerCrosswalk = new Dictionary + { + ["067901108"] = new("ABX", new DateTime(2025, 7, 1)) + }; + + Assert.IsNull(downloader.ResolveThroughTicker("067901108")); + } + + // ---- security database rows repeated across lineages ---------------------------------------- + + [Test] + public void ACusipOnSeveralDatabaseRowsResolvesToTheRowCarryingTheIsinItBuilds() + { + // The security database repeats Alcoa's CUSIP on the old Alcoa, which trades as Howmet today and + // carries Howmet's ISIN, and on the Alcoa spun off in 2016. LEAN's resolver takes the first row, so + // the new Alcoa's holders went to Howmet's lineage. Both rows trade on the date; the one carrying the + // ISIN the CUSIP builds is the security the CUSIP names. + var oldAlcoa = SecurityIdentifier.GenerateEquity(new DateTime(1998, 1, 2), "AA", Market.USA); + var newAlcoa = SecurityIdentifier.GenerateEquity(new DateTime(2016, 11, 1), "AA", Market.USA); + SeedMapFileRows( + ("hwm", new[] { "19980102,aa", "20161031,aa", "20200331,arnc", "20501231,hwm" }), + ("aa", new[] { "20161101,aa", "20501231,aa" })); + SeedSecurityDatabase( + $"{oldAlcoa},01387210,,,US4432011082,4281", + $"{newAlcoa},01387210,,,US0138721065,1675149"); + + using var downloader = Downloader(); + + Assert.AreEqual(newAlcoa, downloader.ResolveSecurity("013872106", new DateTime(2022, 11, 14))); + } + + [Test] + public void ACusipWhoseFirstDatabaseRowNoLongerTradesResolvesToTheRowThatDoes() + { + // TG Therapeutics' CUSIP and ISIN sit on its current listing and on the one it had as Atlantic + // Technology Ventures, whose map file ends in 2005. The first row names a security with no ticker + // since, so every TG holder was dropped as belonging to a ticker another security owned. + var atlantic = SecurityIdentifier.GenerateEquity(new DateTime(1998, 1, 2), "ATLC", Market.USA); + var tg = SecurityIdentifier.GenerateEquity(new DateTime(2012, 10, 1), "TGTX", Market.USA); + SeedMapFileRows( + ("mhan", new[] { "19980102,atlc", "20040630,atlc", "20051230,mhan" }), + ("tgtx", new[] { "20121001,tgtx", "20501231,tgtx" })); + SeedSecurityDatabase( + $"{atlantic},88322Q10,,,US88322Q1085,", + $"{tg},88322Q10,,,US88322Q1085,1001316"); + + using var downloader = Downloader(); + + Assert.AreEqual(tg, downloader.ResolveSecurity("88322Q108", new DateTime(2025, 2, 14))); + } + + [Test] + public void ACusipWhoseDatabaseRowsAllStoppedTradingFallsThroughToTheCrosswalk() + { + // A row that no longer trades used to end the search: its security was dropped later as the owner + // of no ticker, and the N-PORT crosswalk, which knew the CUSIP, was never asked. + var cusip = "12345678" + SEC13FDownloader.ComputeCusipCheckDigit("12345678"); + var dead = SecurityIdentifier.GenerateEquity(new DateTime(1998, 1, 2), "OLDCO", Market.USA); + var live = SecurityIdentifier.GenerateEquity(new DateTime(2015, 3, 2), "NEWCO", Market.USA); + SeedMapFileRows( + ("oldco", new[] { "19980102,oldco", "20101231,oldco" }), + ("newco", new[] { "20150302,newco", "20501231,newco" })); + SeedSecurityDatabase($"{dead},12345678,,,,"); + + using var downloader = Downloader(); + downloader.TickerCrosswalk = new Dictionary + { + [cusip] = new("NEWCO", new DateTime(2025, 7, 1)) + }; + + Assert.AreEqual(live, downloader.ResolveSecurity(cusip, new DateTime(2025, 11, 14))); + } + + /// + /// Writes the security database the downloader reads at construction into the seeded data folder, + /// rows as the real file has them: SID, CUSIP without its check digit, FIGI, SEDOL, ISIN and CIK. + /// + private void SeedSecurityDatabase(params string[] rows) + { + var folder = Path.Combine(_root, "data", "symbol-properties"); + Directory.CreateDirectory(folder); + File.WriteAllLines(Path.Combine(folder, "security-database.csv"), rows); + } + + // ---- An option is reported under its own CUSIP ------------------------------------------ + + [Test] + public void AnOptionResolvesThroughTheSecurityItIsWrittenOn() + { + // 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. That CUSIP is + // in no security database, so the whole line used to resolve to nothing and the position + // was dropped: 6,083 reported option lines in the week of 3 August 2026 alone. + var apple = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "AAPL", Market.USA); + SeedMapFileRows(("aapl", new[] { "19801212,aapl", "20501231,aapl" })); + SeedSecurityDatabase($"{apple},03783310,BBG000B9XRY4,2046251,US0378331005,320193"); + + using var downloader = Downloader(); + downloader.TickerCrosswalk = new Dictionary(); + var filed = new DateTime(2026, 8, 7); + + Assert.AreEqual(apple, downloader.ResolveSecurity("037833100", filed), "the stock itself still resolves"); + Assert.AreEqual(apple, downloader.ResolveSecurity("037833900", filed), "calls reach Apple"); + Assert.AreEqual(apple, downloader.ResolveSecurity("037833956", filed), "puts reach Apple"); + } + + [Test] + public void AnOptionOnAnIssuerWithSeveralStocksIsNotGuessedAt() + { + // iShares writes seventy equity issues under 464287 and SPDR eleven under 81369Y. An + // option CUSIP there names one of them without saying which, and filing the position + // under the wrong fund would be worse than not publishing it. + SeedMapFileRows( + ("ivv", new[] { "20000519,ivv", "20501231,ivv" }), + ("ijh", new[] { "20000531,ijh", "20501231,ijh" })); + SeedSecurityDatabase( + $"{SecurityIdentifier.GenerateEquity(new DateTime(2000, 5, 19), "IVV", Market.USA)},46428710,,,US4642871010,", + $"{SecurityIdentifier.GenerateEquity(new DateTime(2000, 5, 31), "IJH", Market.USA)},46428712,,,US4642871200,"); + + using var downloader = Downloader(); + downloader.TickerCrosswalk = new Dictionary(); + + Assert.IsNull(downloader.ResolveSecurity("464287902", new DateTime(2026, 8, 7)), + "two stocks under one issuer is not an answer"); + } + + [Test] + public void ACompanysBondNeverBecomesItsStock() + { + // Apple's 3.45% 2045 bond, 037833BA7, reached AAPL through a fund administrator's N-PORT + // ticker and added a constant ten thousand shares to it: the principal amount of the + // bond, on a line the filer had typed SH. Its prices agreed with Apple's close by + // coincidence, a bond near par against a stock near the same number, so the price test + // let it through. The issuer having stock of its own is what settles it instead. + SeedCloses("20260630", ("aapl", 250m)); + SeedSecurityDatabase( + $"{ListedSince1980("AAPL")},03783310,BBG000B9XRY4,2046251,US0378331005,320193"); + + using var downloader = Downloader(); + var apple = ListedSince1980("AAPL"); + var period = new DateTime(2026, 6, 30); + var filed = new DateTime(2026, 8, 7); + + Assert.IsTrue(downloader.IssuerHasStock("037833BA7"), "Apple's issuer has stock in the database"); + Assert.IsFalse( + downloader.KeepsCrosswalkGroup("037833BA7", apple, period, filed, new List { 250.0, 250.1 }), + "the bond is dropped although its prices match Apple's close"); + Assert.IsTrue( + downloader.KeepsCrosswalkGroup("037833100", apple, period, filed, new List { 250.0, 250.1 }), + "the stock itself is unaffected"); + } + + [TestCase("BRK.B", true)] + [TestCase("AAPL", true)] + [TestCase("UA.C ", false)] // the map files carry a trailing space for Under Armour's class C + [TestCase("A/B", false)] + [TestCase("X|Y", false)] + public void OnlyATickerLeanCanAskForBecomesAFile(string ticker, bool expected) + { + // A Symbol cannot hold a space or a '|', so a file named after such a ticker is one LEAN + // never reads, and every row in it failed when the delivery archive was read back. + Assert.AreEqual(expected, SEC13FDownloader.IsFileNameSafe(ticker)); + } + + [Test] + public void ASecurityHasNoTickerBeforeItBeganTrading() + { + // Before its first row a map file answers with its first ticker, so a filing dated + // before the listing would land under a ticker the security did not have yet. + SeedMapFileRows(("late", new[] { "20200914,late", "20501231,late" })); + + using var downloader = Downloader(); + var security = SecurityIdentifier.GenerateEquity(new DateTime(2020, 9, 14), "LATE", Market.USA); + + Assert.IsNull(downloader.ResolveTicker(security, new DateTime(2019, 6, 3)), "not listed yet"); + Assert.AreEqual("LATE", downloader.ResolveTicker(security, new DateTime(2021, 1, 4))?.ToUpperInvariant()); + } + + [Test] + public void TheNewestObservationOfACusipWinsWhateverOrderTheQuartersArrive() + { + // The quarters used to be folded newest first, so a first build kept the oldest ticker + // of a renamed security while a refresh kept the newest. + var older = new[] { KeyValuePair.Create("30303M102", "FB") }; + var newer = new[] { KeyValuePair.Create("30303M102", "META") }; + + var olderFirst = new Dictionary(); + SEC13FTickerCrosswalk.Fold(olderFirst, new DateTime(2022, 4, 1), older); + SEC13FTickerCrosswalk.Fold(olderFirst, new DateTime(2022, 7, 1), newer); + + var newerFirst = new Dictionary(); + SEC13FTickerCrosswalk.Fold(newerFirst, new DateTime(2022, 7, 1), newer); + SEC13FTickerCrosswalk.Fold(newerFirst, new DateTime(2022, 4, 1), older); + + Assert.AreEqual("META", olderFirst["30303M102"].Ticker); + Assert.AreEqual("META", newerFirst["30303M102"].Ticker); + } + + // ---- Crosswalk resolutions are held against the close -------------------------------------- + + [Test] + public void ACrosswalkGroupStaysUnlessItsPricesSayItIsAnotherSecurity() + { + // The crosswalk is a fund administrator's ticker, so a CUSIP can reach the wrong company. + // The group's own prices against the quarter-end close decide, on the day it is read. + SeedCloses("20251231", ("etsy", 120m), ("defi", 129.50m), ("aapl", 250m), ("spy", 24.23m)); + using var downloader = Downloader(); + var period = new DateTime(2025, 12, 31); + var filed = new DateTime(2026, 2, 14); + bool Keeps(string cusip, string ticker, params double[] prices) => + downloader.KeepsCrosswalkGroup(cusip, ListedSince1980(ticker), period, filed, prices.ToList()); + + // Etsy's convertible notes reached Etsy's stock: 171 million of its 298 million shares. + Assert.IsFalse(Keeps("29786AAJ5", "ETSY"), "a note reported as principal carries no price"); + Assert.IsFalse(Keeps("29786AAJ5", "ETSY", 1.02, 0.98), "a note reported as shares trades near par"); + Assert.IsTrue(Keeps("46435GAA0", "SPY", 24.23, 24.20), + "a CUSIP with letters whose issuer has no stock of its own is judged on its prices"); + + // DeFi Technologies at $2 reached the $129.50 Hashdex DEFI ETF through its ticker. + Assert.IsFalse(Keeps("244916102", "DEFI", 2.11, 2.05, 2.11), "three managers at $2 are another company"); + Assert.IsTrue(Keeps("037833100", "AAPL", 25.0), "one manager's odd price is a slip, not another company"); + Assert.IsTrue(Keeps("037833100", "AAPL", 0.25, 0.25, 250.1), "the same prices in thousands match"); + Assert.IsTrue(Keeps("037833100", "AAPL"), "a day of option lines only cannot be checked and stays"); + Assert.IsTrue(Keeps("594918104", "MSFT", 2.0, 2.0, 2.0), "nor can a security the close file does not carry"); + + // Avanos on 5 February 2026: four managers in thousands and four in dollars. Their median + // sat half way between the two units and dropped the group. + Assert.IsTrue(Keeps("037833100", "AAPL", 0.25, 0.25, 0.25, 0.25, 250.0, 250.1, 249.9, 740.8), + "a day that mixes the two units is still the security"); + } + + [Test] + public void ALineThatSitsOnNoUnitStepIsNotScaled() + { + // A price about a hundred times below the close is neither dollars nor thousands: a bond at + // par against its issuer's stock, or another company. Scaling it by the nearest step put + // Seagate's December 2025 quarter at $413 billion. + SeedCloses("20251231", ("aapl", 250m)); + var filing = new DateTime(2026, 2, 14); + var quarter = new DateTime(2025, 12, 31); + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "250000", "1000", "SH"), + Line("0000000000-00-000002", "037833100", "2500", "1000", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", filing, quarter, cik: 1), + Submission("0000000000-00-000002", filing, quarter, cik: 2)); + + Assert.AreEqual(250000m + 2500m, holdings.Values.Single().ReportedValue, + "the $2.50 line keeps its filing's dollar factor instead of becoming $2.5 million"); + } + + [Test] + public void ABrokenFilingIsNeverScaledUp() + { + // Every line reads $1,000 a share, since SSHPRNAMT carries VALUE in thousands. Two of the + // four prices land on a unit step by chance, not most, so the filing shows no unit. Before + // 2023 the rule multiplied it by a thousand, and one such manager put Alphabet's September + // 2020 quarter 9.5 percent above its close. + SeedCloses("20200930", ("aapl", 100m), ("msft", 200m), ("nvda", 500m), ("spy", 330m)); + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", "1000000", "1000", "SH"), + Line("0000000000-00-000001", "594918104", "1000000", "1000", "SH"), + Line("0000000000-00-000001", "67066G104", "1000000", "1000", "SH"), + Line("0000000000-00-000001", "78462F103", "1000000", "1000", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", new DateTime(2020, 11, 6), new DateTime(2020, 9, 30), cik: 1)); + + Assert.AreEqual(4, holdings.Count); + Assert.IsTrue(holdings.Values.All(holding => holding.ReportedValue == 1000000m), + string.Join(", ", holdings.Values.Select(holding => holding.ReportedValue))); + } + + [TestCase("20200814", "20200630", "20200630", "250000", "2500", 752500)] // dollars before 2023: the rule made the $2.50 line $2,500 + [TestCase("20230214", "20221231", "20221230", "250", "25", 750025)] // thousands in 2023: the filing's unit made it 1,000 times larger + public void ALineOnNoStepIsNeverScaledPastItsFilingOrItsDate(string filed, string quarter, string closeDay, + string onStep, string offStep, decimal expected) + { + // Three lines prove the filing's unit and a fourth sits on no step. It cannot be checked, so + // it takes the smaller of the filing's unit and the rule of the filing date: either one alone + // inflated one era by hundreds of billions. + SeedCloses(closeDay, ("aapl", 250m)); + var holdings = ReadInfoTable( + new[] + { + Line("0000000000-00-000001", "037833100", onStep, "1000", "SH"), + Line("0000000000-00-000001", "037833100", onStep, "1000", "SH"), + Line("0000000000-00-000001", "037833100", onStep, "1000", "SH"), + Line("0000000000-00-000001", "037833100", offStep, "1000", "SH") + }, + resolve: true, + Submission("0000000000-00-000001", Time.ParseDate(filed), Time.ParseDate(quarter), cik: 1)); + + Assert.AreEqual(expected, holdings.Values.Single().ReportedValue); + } + + [TestCase(0.0, 0)] + [TestCase(-3.0, -1)] + [TestCase(3.1, 1)] + [TestCase(-2.9, -1)] + public void AnOffsetNearAThousandfoldStepIsAUnit(double offset, int step) + { + Assert.AreEqual(step, SEC13FDownloader.UnitStep(offset)); + } + + [TestCase(-1.5)] // half way between dollars and thousands + [TestCase(-2.0)] // a hundred times below: a bond at par against a stock + [TestCase(1.79)] + [TestCase(-6.0)] // a millionfold step is a share count slip, not a unit + public void AnOffsetOnNoStepIsNoUnit(double offset) + { + Assert.IsNull(SEC13FDownloader.UnitStep(offset)); + } + + [Test] + public void TheCloseIsNeverReadAfterTheFilingDate() + { + // A period typed ahead of its filing date would otherwise read a price nobody had when + // the filing was made. The quarter ends on a Tuesday; filed the Sunday before, the + // Friday's close is the newest one there was. + SeedCloses("20260327", ("aapl", 250m)); + var coarse = Path.Combine(_root, "data", "equity", "usa", "fundamental", "coarse"); + File.WriteAllText(Path.Combine(coarse, "20260331.csv"), $"{ListedSince1980("AAPL")},AAPL,999,1,1,True,1,1\n"); + var prices = new SEC13FClosePrices(coarse); + + Assert.AreEqual(250m, prices.Close(ListedSince1980("AAPL"), new DateTime(2026, 3, 31), new DateTime(2026, 3, 29))); + Assert.AreEqual(999m, prices.Close(ListedSince1980("AAPL"), new DateTime(2026, 3, 31), new DateTime(2026, 4, 2))); + } + + // ---- Reading a day from EDGAR --------------------------------------------------------------- + + [Test] + public void TheDailyIndexYieldsTheHoldingsReportsAndTheirAmendmentsOnly() + { + // Notices carry no information table and the data sets' reader skips them too. + var index = string.Join("\n", + "Form Type Company Name CIK Date Filed File Name", + "---------------------------------------------------------", + "10-K SOMETHING ELSE INC 1111111 20260814 edgar/data/1111111/0001111111-26-000001.txt", + "13F-HR &PARTNERS 107136 20260814 edgar/data/107136/0001214659-26-010148.txt", + "13F-HR/A FUND 1 ADVISERS LLC 2222222 20260814 edgar/data/2222222/0002222222-26-000003.txt", + "13F-NT NOTICE FILER LP 3333333 20260814 edgar/data/3333333/0003333333-26-000004.txt"); + + var entries = SEC13FEdgarDay.ParseIndex(index); + + Assert.AreEqual(new[] { "0001214659-26-010148", "0002222222-26-000003" }, + entries.Select(entry => entry.Accession).ToArray()); + Assert.AreEqual(new[] { "13F-HR", "13F-HR/A" }, entries.Select(entry => entry.FormType).ToArray()); + Assert.AreEqual(new[] { 107136, 2222222 }, entries.Select(entry => entry.Cik).ToArray()); + Assert.AreEqual(new DateTime(2026, 8, 14), entries[0].Filed); + } + + [Test] + public void AFilingIsReadFromItsSubmissionFile() + { + // The period and the confidential flag come from the primary document, the lines from the + // information table, whatever namespace the filer's software wrote. + var filing = SEC13FEdgarDay.ParseFiling(SampleEntry(), SampleSubmission()); + + Assert.AreEqual("0001214659-26-010148", filing.Accession); + Assert.AreEqual(new DateTime(2026, 6, 30), filing.Period); + Assert.IsTrue(filing.ConfidentialOmitted); + Assert.AreEqual(2, filing.Lines.Count); + Assert.AreEqual(new[] { "88025U109", null, "274974", "7172", "SH", null, null, null, "7172", "0", "0" }, filing.Lines[0]); + Assert.AreEqual(new[] { "037833100", "COM", "1000", "50", "SH", "Put", "DFND", "1, 2", "0", "50", null }, filing.Lines[1]); + + // The filing manager's name, not the signer's. + Assert.AreEqual("WEALTH ADVISORS, INC.", filing.ManagerName); + Assert.AreEqual("RESTATEMENT", filing.AmendmentType); + Assert.AreEqual("1", filing.AmendmentNumber); + Assert.AreEqual(new DateTime(2026, 5, 15), filing.DateReported); + } + + [Test] + public void ADaysArchiveIsReadByTheProcessorLikeADataSet() + { + // Pushed through the processor rather than read back table by table: the day's archive + // once lacked the cover page and four columns the processor had come to require, and a + // test listing the columns itself stayed green while every EDGAR day threw. + SeedMapFiles("aapl"); + var day = new DateTime(2026, 8, 14); + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + null, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + + ProcessEdgarDay(downloader, day); + downloader.FinalizeSecurityFiles(); + + var row = EntryLines(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "aapl.zip"), "20260814.csv").Single(); + Assert.AreEqual("20260814,0001214659-26-010148,107136,20260630,13F-HR,RESTATEMENT,1,COM,50,SH,1000,0,P,DFND,1;2,0,50,,1,20260515", row); + + // The comma the name carries comes out as one space, not the two the replacement leaves. + Assert.AreEqual(new[] { "107136,WEALTH ADVISORS INC." }, + File.ReadAllLines(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "managers.csv"))); + } + + [Test] + public void AnOptionCusipGivesALineTheSideItLeftEmpty() + { + // The option's CUSIP reaches the underlying's file, where a line without PUTCALL would + // read as that many shares of the stock. Issue 90 is a call and 95 a put. + var apple = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "AAPL", Market.USA); + SeedMapFileRows(("aapl", new[] { "19801212,aapl", "20501231,aapl" })); + SeedSecurityDatabase($"{apple},03783310,BBG000B9XRY4,2046251,US0378331005,320193"); + + var day = new DateTime(2026, 8, 14); + var filing = OptionFiling(day); + filing.Lines.Add(["037833900", "COM", "1000", "50", "SH", null, "SOLE", null, "50", "0", "0"]); + filing.Lines.Add(["037833956", "COM", "1000", "50", "SH", null, "SOLE", null, "50", "0", "0"]); + filing.Lines.Add(["037833956", "COM", "1000", "50", "SH", "Call", "SOLE", null, "50", "0", "0"]); + + var rows = PublishedRows(day, filing, "aapl"); + + Assert.AreEqual(new[] { "C", "P", "C" }, rows.Select(row => row.Split(',')[12]).ToArray(), + "a side the line states is kept"); + } + + [TestCase("1000", "1000", TestName = "a whole value is published without a decimal point")] + [TestCase("1000.50", "1000.50", TestName = "a value with cents keeps them")] + [TestCase("99999999999999999999", "99999999999999999999", TestName = "a value past long.MaxValue is published, not thrown on")] + public void AValueIsPublishedAsTheFilerWroteIt(string filed, string published) + { + // Filers type nonsense into VALUE. Cast to a long, a number past its range threw out of + // the formatter and stopped the run over one line. + var apple = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "AAPL", Market.USA); + SeedMapFileRows(("aapl", new[] { "19801212,aapl", "20501231,aapl" })); + SeedSecurityDatabase($"{apple},03783310,BBG000B9XRY4,2046251,US0378331005,320193"); + + var day = new DateTime(2026, 8, 14); + var filing = OptionFiling(day); + filing.Lines.Add(["037833100", "COM", filed, "50", "SH", null, "SOLE", null, "50", "0", "0"]); + + Assert.AreEqual(published, PublishedRows(day, filing, "aapl").Single().Split(',')[10]); + } + + [Test] + public void AnOptionOnAFundFamilyIsResolvedByThePriceOfItsLine() + { + // Every fund of a family shares one option CUSIP, so the issuer does not say which fund a + // line is written on. The line reports the underlying's value and shares, and so its price. + var iwm = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "IWM", Market.USA); + var eem = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "EEM", Market.USA); + SeedMapFiles("iwm", "eem"); + SeedSecurityDatabase( + $"{iwm},46428765,BBG000CGC9C4,2622059,US4642876555,1100663", + $"{eem},46428723,BBG000M0P5L2,2801669,US4642872349,1100663"); + SeedCoarse("20260630", (iwm, 220m), (eem, 45m)); + + var day = new DateTime(2026, 8, 14); + var filing = OptionFiling(day); + filing.Lines.Add(["464287905", "RUSSELL 2000 ETF", "22000", "100", "SH", "Call", "SOLE", null, "0", "0", "100"]); + filing.Lines.Add(["464287905", "MSCI EMERG MKT", "4500", "100", "SH", null, "SOLE", null, "0", "0", "100"]); + filing.Lines.Add(["464287905", "SOMETHING ELSE", "9900", "100", "SH", "Call", "SOLE", null, "0", "0", "100"]); + + Assert.AreEqual(1, PublishedRows(day, filing, "iwm").Count); + var emerging = PublishedRows(day, null, "eem").Single().Split(','); + Assert.AreEqual("MSCI EMERG MKT", emerging[7]); + Assert.AreEqual("C", emerging[12], "the side comes from the CUSIP"); + } + + [Test] + public void AnOptionLineNamedByItsCloseStillTakesItsFilingsUnit() + { + // The close names the fund, not the unit. An option line has no price of its own to hold + // against a close, so reading the unit off the fund it matched would state two units for + // one filing: the same manager's lines on a single-issue CUSIP never see a close. The + // filing's unit is the one every option line takes, right or wrong, for all of them. + var iwm = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "IWM", Market.USA); + var eem = SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), "EEM", Market.USA); + SeedMapFiles("iwm", "eem"); + SeedSecurityDatabase( + $"{iwm},46428765,BBG000CGC9C4,2622059,US4642876555,1100663", + $"{eem},46428723,BBG000M0P5L2,2801669,US4642872349,1100663"); + SeedCoarse("20260630", (iwm, 220m), (eem, 45m)); + + var day = new DateTime(2026, 8, 14); + var filing = OptionFiling(day); + filing.Lines.Add(["464287905", "RUSSELL 2000 ETF", "22", "100", "SH", "Call", "SOLE", null, "0", "0", "100"]); + + var row = PublishedRows(day, filing, "iwm").Single().Split(','); + Assert.AreEqual("22", row[10], "the manager's number is published untouched"); + Assert.AreEqual("0", row[11], "the filing is after 2023, so its rule says dollars"); + } + + [TestCase(220, 220, true)] + [TestCase(221, 220, true)] + [TestCase(224, 220, false)] + [TestCase(0.221, 220, true)] + [TestCase(200, 220, false)] + [TestCase(45, 220, false)] + public void AnOptionLineNamesAFundOnlyWithinHalfAPercentOfItsClose(double price, double close, bool expected) + { + Assert.AreEqual(expected, SEC13FDownloader.MatchesClose((decimal)price, (decimal)close)); + } + + [TestCase("20260903", "20260830", TestName = "the lookback reaches further than the last day folded in")] + [TestCase("20260908", "20260830", TestName = "the last day folded in is inside the lookback")] + public void AnIncrementalRunStartsAtTheLookback(string lastFolded, string expected) + { + var shelf = PublishedShelf(); + File.WriteAllLines(Path.Combine(shelf, "edgar-days.txt"), ["#from 20260601", lastFolded]); + + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 9)); + downloader.ReadEdgarState(); + + Assert.AreEqual(expected, downloader.FirstDayToCatchUp(new DateTime(2026, 9, 9)).ToString("yyyyMMdd")); + } + + [Test] + public void AManagerKeepsTheNameOfItsLatestFilingWhateverOrderTheDaysAreRead() + { + // A day whose index came late is read after newer ones, so the last name a run sees is + // not the most recently filed. Taking it would roll the published name back to an older + // one until the manager files again. + SeedMapFiles("aapl"); + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + null, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + + ProcessEdgarDay(downloader, new DateTime(2026, 8, 14), NamedFiling(new DateTime(2026, 8, 14), "NEWCO ASSET MGMT")); + ProcessEdgarDay(downloader, new DateTime(2026, 8, 10), NamedFiling(new DateTime(2026, 8, 10), "OLDCO ASSET MGMT")); + downloader.FinalizeSecurityFiles(); + + Assert.AreEqual(new[] { "7,NEWCO ASSET MGMT" }, + File.ReadAllLines(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "managers.csv"))); + } + + /// One filing of CIK 7 on a day, holding a line that resolves, under the name given. + private static SEC13FEdgarDay.Filing NamedFiling(DateTime day, string name, int cik = 7) + { + var filing = new SEC13FEdgarDay.Filing + { + Accession = $"{cik:0000000000}-26-{day:MMdd}01", SubmissionType = "13F-HR", Cik = cik, Filed = day, + Period = new DateTime(2026, 6, 30), ManagerName = name + }; + + filing.Lines.Add(["037833100", "COM", "1000", "50", "SH", null, "SOLE", null, "50", "0", "0"]); + return filing; + } + + [Test] + public void ADayThatStagedNoRowStillPublishesTheManagersItRead() + { + // A quiet day can carry only filings whose every line fails to resolve. The day is + // recorded as folded in all the same, so no later run reads its cover pages again: a + // name not written here is lost until the manager files a line that does resolve. + SeedMapFiles("msft"); + var day = new DateTime(2026, 8, 14); + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + day, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + + ProcessEdgarDay(downloader, day, NamedFiling(day, "NEWCO ASSET MGMT")); + downloader.FinalizeSecurityFiles(); + + var destination = Path.Combine(_root, "out", SEC13FHolding.ReportFolder); + Assert.IsEmpty(Directory.GetFiles(destination, "*.zip"), "nothing resolved"); + Assert.AreEqual(new[] { "7,NEWCO ASSET MGMT" }, File.ReadAllLines(Path.Combine(destination, "managers.csv"))); + } + + [Test] + public void AManagerThatFiledAgainKeepsItsPublishedName() + { + // The run before this one folded in 09-09 and published the name the manager filed that + // day. This run reaches back to 09-08, whose index EDGAR published late, where the same + // manager filed under its older name. managers.csv carries no date to say the published + // name is the newer one, so the manager's own published rows are what say it. + var shelf = LateDayShelf(publishedName: "NEWCO ASSET MGMT", filedOn0909: 7); + + Assert.AreEqual(new[] { "7,NEWCO ASSET MGMT" }, FoldInTheLateDay(shelf, "OLDCO ASSET MGMT")); + } + + [Test] + public void AManagerThatDidNotFileAgainTakesTheNameOfTheLateDay() + { + // A manager files once a quarter, so the late day usually carries its only filing of the + // window, and then that filing is the one that names it. Deciding from the last day the + // run folded in rather than from this manager's own rows threw the name away for every + // manager that had not filed since, which on a late deadline day is nearly all of them. + var shelf = LateDayShelf(publishedName: "OLDCO ASSET MGMT", filedOn0909: 99); + + Assert.AreEqual(new[] { "7,NEWCO ASSET MGMT" }, FoldInTheLateDay(shelf, "NEWCO ASSET MGMT")); + } + + [Test] + public void AManagerIsNamedByItsLatestFilingWhenTwoLateDaysAreFoldedInAtOnce() + { + // Two indexes came late, so this run folds in 09-03 and 09-08 at once. CIK 7's published + // rows are of 09-04, inside the window the scan covers but older than the 09-08 filing + // this run read, so 09-08 is what names the manager. Knowing only which managers have + // published rows in the window, without the date of each, would keep the older name here + // and lose the rename, which is the whole reason the date is read off the rows. + SeedMapFiles("aapl"); + var shelf = PublishedShelf(); + File.WriteAllLines(Path.Combine(shelf, "edgar-days.txt"), ["#from 20260601", "20260904", "20260909"]); + File.WriteAllLines(Path.Combine(shelf, "managers.csv"), ["7,OLDCO ASSET MGMT"]); + SeedPublishedZip(shelf, "aapl", 7, "20260904"); + + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + new DateTime(2026, 9, 11), Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + downloader.ReadEdgarState(); + + ProcessEdgarDay(downloader, new DateTime(2026, 9, 3), + NamedFiling(new DateTime(2026, 9, 3), "OTHER MANAGER LP", cik: 99)); + ProcessEdgarDay(downloader, new DateTime(2026, 9, 8), + NamedFiling(new DateTime(2026, 9, 8), "NEWCO ASSET MGMT")); + downloader.FinalizeSecurityFiles(); + + Assert.AreEqual(new[] { "7,NEWCO ASSET MGMT", "99,OTHER MANAGER LP" }, + File.ReadAllLines(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "managers.csv"))); + } + + /// + /// A published history reaching 09-09, naming CIK 7, whose rows of 09-09 were filed by + /// . The day 09-08 is missing: EDGAR published its index late. + /// + private string LateDayShelf(string publishedName, int filedOn0909) + { + SeedMapFiles("aapl"); + var shelf = PublishedShelf(); + File.WriteAllLines(Path.Combine(shelf, "edgar-days.txt"), ["#from 20260601", "20260909"]); + File.WriteAllLines(Path.Combine(shelf, "managers.csv"), [$"7,{publishedName}"]); + SeedPublishedZip(shelf, "aapl", filedOn0909, "20260909"); + return shelf; + } + + /// Folds 09-08 into that history, with CIK 7 filing under the name given. + private string[] FoldInTheLateDay(string shelf, string name) + { + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + new DateTime(2026, 9, 11), Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + downloader.ReadEdgarState(); + + ProcessEdgarDay(downloader, new DateTime(2026, 9, 8), NamedFiling(new DateTime(2026, 9, 8), name)); + downloader.FinalizeSecurityFiles(); + + return File.ReadAllLines(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "managers.csv")); + } + + [Test] + public void AnIncrementalRunReachesBackToTheDaysAnOutageMissed() + { + // EDGAR blocked the runner for a fortnight, so every run in between failed. Reading only + // the ten day lookback would step over the days in the gap and return success, and no + // later run would ever reach them. + var shelf = PublishedShelf(); + File.WriteAllLines(Path.Combine(shelf, "edgar-days.txt"), ["#from 20260601", "20260814"]); + + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 9)); + downloader.ReadEdgarState(); + + var days = downloader.EdgarDaysToRead( + downloader.FirstDayToCatchUp(new DateTime(2026, 9, 9)), new DateTime(2026, 9, 9)); + + Assert.AreEqual("20260817", days[0].ToString("yyyyMMdd"), "the day after the last one folded in"); + Assert.AreEqual("20260909", days[^1].ToString("yyyyMMdd")); + Assert.IsFalse(days.Contains(new DateTime(2026, 8, 14)), "a day already folded in is not read again"); + } + + /// + /// Holdings filings per day, counted over the June to August 2026 data set: the five weekdays + /// leading to the 45 day deadline, the deadline itself, and a quiet day for the rest. + /// + private static int FilingsOn(DateTime day) => day.ToString("yyyyMMdd") switch + { + "20260810" => 355, + "20260811" => 449, + "20260812" => 461, + "20260813" => 742, + "20260814" => 1835, + _ => 65 + }; + + [Test] + public void AGapWiderThanARunIsClosedWithoutOutweighingAnOrdinaryRun() + { + // Nineteen weekdays of outage, the 45 day deadline among them, do not fit in the run's + // hour. Read whole they fail it, and since nothing is published until the last of them + // is read, the next run starts over one day further behind: the gap never closes and the + // data set never moves again. Read by weight, every run finishes and the gap shrinks. + var shelf = PublishedShelf(); + var deployment = new DateTime(2026, 9, 9); + var folded = new List { "20260807" }; + var runs = 0; + + while (folded[^1] != "20260909") + { + Assert.Less(++runs, 20, "the gap has to close"); + File.WriteAllLines(Path.Combine(shelf, "edgar-days.txt"), folded.Prepend("#from 20260601")); + + using var downloader = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), deployment); + downloader.ReadEdgarState(); + + // What ProcessEdgarDays does with what EdgarDaysToRead offers it. + var read = 0; + var fetched = 0; + var first = 0; + foreach (var day in downloader.EdgarDaysToRead(downloader.FirstDayToCatchUp(deployment), deployment)) + { + if (FilingsOn(day) > downloader.FilingBudget(read, fetched)) + { + break; + } + + first = read == 0 ? FilingsOn(day) : first; + fetched += FilingsOn(day); + read++; + folded.Add(day.ToString("yyyyMMdd")); + } + + // The budget, or the first day alone where that day is heavier than the budget: + // one exempt day and the rest inside what is left of it. A run that reached the + // exemption twice would fetch more than this and still close the gap. + Assert.Greater(read, 0, "a run that reads nothing never closes the gap"); + Assert.LessOrEqual(fetched, Math.Max(SEC13FDownloader.MaxFilingsPerRun, first), + "no run outweighs the heaviest day an ordinary run already carries"); + } + } + + [Test] + public void TheFirstDayOfARunIsReadWhateverItWeighs() + { + // A day is the unit of work: it cannot be read in half, and the heaviest of them is what + // an ordinary run does every quarter anyway. Budgeted like any other, the deadline day + // would never be read and the gap behind it would never close. + using var daily = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), new DateTime(2026, 9, 9)); + + Assert.AreEqual(int.MaxValue, daily.FilingBudget(read: 0, fetched: 0)); + Assert.AreEqual(SEC13FDownloader.MaxFilingsPerRun - 1835, daily.FilingBudget(read: 1, fetched: 1835)); + Assert.Less(daily.FilingBudget(read: 2, fetched: SEC13FDownloader.MaxFilingsPerRun), 1, "nothing is left"); + + using var rebuild = new SEC13FDownloader( + Path.Combine(_root, "out"), Path.Combine(_root, "processed"), null, Path.Combine(_root, "raw")); + + Assert.AreEqual(int.MaxValue, rebuild.FilingBudget(read: 40, fetched: 500000), + "the rebuild states the day it reaches, so it reads its whole window"); + } + + [Test] + public void ADayThatDoesNotFitIsLeftWholeAndNothingOfItIsFetched() + { + // The index costs one round trip and every filing another, so the weight of a day is + // known before the expensive part. Half a day read is worse than none: the archive is + // written once and a later run would take it for the whole day. + var index = string.Join('\n', Enumerable.Range(1, 40).Select(filing => + $"13F-HR SOME MANAGER LP {filing} 20260814 edgar/data/{filing}/0000000000-26-{filing:000000}.txt")); + + var fetches = new List(); + var built = SEC13FEdgarDay.Build(new DateTime(2026, 8, 14), _root, + _ => new HashSet { "2026", "QTR3", "form.20260814.idx" }, + url => + { + fetches.Add(url); + return url.EndsWith(".idx", StringComparison.Ordinal) ? index : throw new InvalidOperationException(url); + }, + filingBudget: 39); + + Assert.IsTrue(built.OverBudget); + Assert.IsNull(built.Path, "nothing was written, so the next run reads the day whole"); + Assert.AreEqual(40, built.Filings, "and the caller is told what it would have cost"); + Assert.AreEqual(1, fetches.Count, "the index and not one filing"); + } + + [Test] + public void TheFilingNamesItsOwnFilerRatherThanTheIndex() + { + // The index lists an accession once per CIK it names, and the first line of it can be a + // co-filer, so the daily path would file the positions under the wrong manager and split + // its history from what the data sets published. + var filing = SEC13FEdgarDay.ParseFiling( + new SECEdgarIndex.Entry("13F-HR", 50, new DateTime(2026, 8, 14), "edgar/data/50/x.txt"), + SampleSubmission().Replace("", + "0000000100")); + + Assert.AreEqual(100, filing.Cik); + } + + [Test] + public void AFilingWithNoCredentialsKeepsTheIndexCik() + { + Assert.AreEqual(107136, SEC13FEdgarDay.ParseFiling(SampleEntry(), SampleSubmission()).Cik); + } + + [Test] + public void AFilingThatCannotBeReadIsSkippedRatherThanFailingTheDay() + { + // Thrown, one bad filing fails the day, the day is never recorded, and every run after + // it meets the same filing: the data set stops until someone ships code. + var built = BuildDayOfThree(unreadable: 1); + + Assert.IsNotNull(built.Path); + Assert.AreEqual(2, built.Filings); + } + + [Test] + public void ADayOfFilingsThatCannotBeReadStillThrows() + { + // Filing after filing unreadable is a layout change, not a bad filer, and publishing the + // day emptied of what it held would be worse than failing. + Assert.Throws(() => BuildDayOfThree(unreadable: 3)); + } + + /// One EDGAR day of three filings, the first of them broken. + private SEC13FEdgarDay.Built BuildDayOfThree(int unreadable) + { + var day = new DateTime(2026, 8, 14); + var index = string.Join('\n', Enumerable.Range(1, 3).Select(filing => + $"13F-HR SOME MANAGER LP {filing} 20260814 edgar/data/{filing}/0000000000-26-00000{filing}.txt")); + + var broken = 0; + return SEC13FEdgarDay.Build(day, Path.Combine(_root, "day-of-three"), + _ => new HashSet { "2026", "QTR3", "form.20260814.idx" }, + url => url.EndsWith(".idx", StringComparison.Ordinal) + ? index + : ++broken <= unreadable ? "no primary document here" : SampleSubmission()); + } + + [Test] + public void ANamePublishedWithItsCommaIsCleanedWhenItIsFoldedIn() + { + // An earlier release wrote the names with their commas, so the file on the shelf carries + // lines of three fields. A run that folded them in untouched would republish them, and a + // manager that does not file again would keep its broken line for good. + SeedMapFiles("aapl"); + var shelf = PublishedShelf(); + SeedPublishedZip(shelf, "aapl", "20240215"); + File.WriteAllLines(Path.Combine(shelf, "managers.csv"), ["2230,ADAMS DIVERSIFIED EQUITY FUND, INC."]); + + var day = new DateTime(2026, 8, 14); + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + day, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + + ProcessEdgarDay(downloader, day); + downloader.FinalizeSecurityFiles(); + + var published = File.ReadAllLines( + Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "managers.csv")); + + Assert.IsTrue(published.Contains("2230,ADAMS DIVERSIFIED EQUITY FUND INC."), + $"the folded in name reads as {published.FirstOrDefault(line => line.StartsWith("2230,"))}"); + Assert.IsEmpty(published.Where(line => line.Count(character => character == ',') != 1).ToList(), + "a folded in name still carries a separator"); + } + + [Test] + public void AnIncrementalRunKeepsThePublishedDatesOfASecurity() + { + // The destination starts empty and replaces what is published, so a run that wrote only + // its own dates would cut the security's history down to them. + SeedMapFiles("aapl"); + SeedPublishedZip(PublishedShelf(), "aapl", "20240215"); + + var day = new DateTime(2026, 8, 14); + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + day, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + + ProcessEdgarDay(downloader, day); + downloader.FinalizeSecurityFiles(); + + var destination = Path.Combine(_root, "out", SEC13FHolding.ReportFolder); + using var zip = ZipFile.OpenRead(Path.Combine(destination, "aapl.zip")); + Assert.AreEqual(new[] { "20240215.csv", "20260814.csv" }, zip.Entries.Select(entry => entry.Name).OrderBy(name => name).ToArray()); + Assert.IsFalse(File.Exists(Path.Combine(destination, "aapl.csv")), "the date index is gone"); + } + + [Test] + public void AnIncrementalRunKeepsThePublishedRowsOfADateItWritesInto() + { + // A date the run has rows for is not the run's to rewrite: what is published for it can + // come from a filing this read does not carry, since the daily index and the quarterly + // data sets do not list the same filings for a day. Replacing the entry with this run's + // rows drops every other manager's positions of that date, and the run reports success. + SeedMapFiles("aapl"); + var day = new DateTime(2026, 8, 13); + SeedPublishedEntry(PublishedShelf(), "aapl", "20260813", + "0000000001-26-000001", "0000000002-26-000002"); + + PublishEdgarDay(day, NamedFiling(day, "NEWCO ASSET MGMT")); + + var accessions = PublishedEntryLines("aapl", "20260813.csv").Select(line => line.Split(',')[1]).OrderBy(x => x).ToArray(); + Assert.AreEqual( + new[] { "0000000001-26-000001", "0000000002-26-000002", "0000000007-26-081301" }, accessions, + "the two published rows and the new one"); + } + + [Test] + public void AFilingReadTwiceIsNotPublishedTwice() + { + // The same filing read again replaces its own lines rather than adding a second copy, so + // a day re-read after a publish that failed half way is still idempotent. + SeedMapFiles("aapl"); + var day = new DateTime(2026, 8, 13); + SeedPublishedEntry(PublishedShelf(), "aapl", "20260813", + "0000000001-26-000001", "0000000007-26-081301"); + + PublishEdgarDay(day, NamedFiling(day, "NEWCO ASSET MGMT")); + + var accessions = PublishedEntryLines("aapl", "20260813.csv").Select(line => line.Split(',')[1]).ToArray(); + Assert.AreEqual(2, accessions.Length); + Assert.AreEqual(1, accessions.Count(accession => accession == "0000000007-26-081301"), "one copy"); + } + + [Test] + public void ADailyArchiveStampsItsRowsWithTheDayItWasRead() + { + // An EDGAR daily index can list a filing whose own date is an earlier day: one 13F-HR in + // the 2026 Q2 and Q3 indexes, 2026-04-27 listed on 04-28. The row carries the day the job + // could have it, which is the day the index named it, so nothing is visible in a backtest + // before it was public. A later rebuild reads that accession from the data sets and + // publishes it under its own filing date, so the row moves by a day. + SeedMapFiles("aapl"); + var day = new DateTime(2026, 4, 28); + + PublishEdgarDay(day, NamedFiling(new DateTime(2026, 4, 27), "BACKDATED ASSET MGMT")); + + using var zip = ZipFile.OpenRead(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, "aapl.zip")); + Assert.AreEqual(new[] { "20260428.csv" }, zip.Entries.Select(entry => entry.Name).ToArray()); + Assert.AreEqual("20260428", PublishedEntryLines("aapl", "20260428.csv").Single().Split(',')[0]); + } + + /// An incremental run over one EDGAR day, published into the destination. + private void PublishEdgarDay(DateTime day, SEC13FEdgarDay.Filing filing) + { + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + day, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = UnitTestCrosswalk(); + + ProcessEdgarDay(downloader, day, filing); + downloader.FinalizeSecurityFiles(); + } + + /// The lines of one published entry of a ticker's zip in the destination. + private string[] PublishedEntryLines(string ticker, string entry) + { + using var zip = ZipFile.OpenRead(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, $"{ticker}.zip")); + using var reader = new StreamReader(zip.GetEntry(entry).Open()); + return reader.ReadToEnd().Split('\n', StringSplitOptions.RemoveEmptyEntries); + } + + /// A published zip whose entry for one date holds a row of each accession given. + private static void SeedPublishedEntry(string shelf, string ticker, string date, params string[] accessions) + { + using var zip = ZipFile.Open(Path.Combine(shelf, $"{ticker}.zip"), ZipArchiveMode.Create); + using var writer = new StreamWriter(zip.CreateEntry($"{date}.csv").Open()) { NewLine = "\n" }; + foreach (var accession in accessions) + { + writer.WriteLine($"{date},{accession},1,20260630,13F-HR,,,COM,1,SH,1,0,,SOLE,,1,0,0,0,"); + } + } + + [Test] + public void AnEdgarDayIsPublishedOnlyWhenItsQuarterListsItsIndex() + { + // EDGAR answers 403 both for an index that does not exist and for a reader it has blocked, + // so whether a day is out comes from its listings. A folder EDGAR has not created yet + // answers 403 too, so it is looked up in its parent first: requesting any folder missing + // from this map throws, and fails the test. + const string root = "https://www.sec.gov/Archives/edgar/daily-index/"; + var listings = new Dictionary> + { + [root] = new HashSet { "2025", "2026" }, + [root + "2026/"] = new HashSet { "QTR1", "QTR2", "QTR3" }, + [root + "2026/QTR3/"] = new HashSet { "form.20260904.idx", "form.20260908.idx" } + }; + + Assert.IsTrue(SECEdgarIndex.IsIndexPublished(new DateTime(2026, 9, 8), url => listings[url])); + Assert.IsFalse(SECEdgarIndex.IsIndexPublished(new DateTime(2026, 9, 7), url => listings[url]), "Labor Day"); + Assert.IsFalse(SECEdgarIndex.IsIndexPublished(new DateTime(2026, 10, 1), url => listings[url]), "a new quarter"); + Assert.IsFalse(SECEdgarIndex.IsIndexPublished(new DateTime(2027, 1, 4), url => listings[url]), "a new year"); + } + + [Test] + public void AnEdgarBlockFailsTheDayInsteadOfSkippingIt() + { + // Taken for "no index", a blocked day was never recorded and fell out of reach of the + // daily run's lookback, so the rebuild lost it for good. + HttpRequestException Blocked() => new("Forbidden", null, HttpStatusCode.Forbidden); + + Assert.Throws(() => SEC13FEdgarDay.Build(new DateTime(2026, 9, 8), _root, + _ => throw Blocked(), _ => throw Blocked())); + } + + [TestCase(HttpStatusCode.NotFound, true)] + [TestCase(HttpStatusCode.ServiceUnavailable, true)] + [TestCase(HttpStatusCode.TooManyRequests, true)] + [TestCase(HttpStatusCode.Forbidden, false)] + public void OnlyABlockIsNotAskedAgain(HttpStatusCode status, bool retried) + { + // Every file requested is one the SEC lists, so a 404 is a hiccup worth asking again: a + // filing in the 24 July 2026 index answered 404 during a rebuild and 200 afterwards. + Assert.AreEqual(retried, SECEdgarClient.IsWorthRetrying(new HttpRequestException("", null, status))); + } + + [Test] + public void AnEdgarListingYieldsItsNames() + { + const string listing = @"{""directory"":{""item"":[{""last-modified"":""09\/08\/2026 10:02:29 PM""," + + @"""name"":""form.20260908.idx"",""type"":""file"",""href"":""form.20260908.idx"",""size"":""782 KB""}]," + + @"""name"":""daily-index\/2026\/QTR3\/"",""parent-dir"":""..\/""}}"; + + Assert.AreEqual(new[] { "form.20260908.idx" }, SECEdgarIndex.ListingNames(listing).ToArray()); + Assert.Throws(() => SECEdgarIndex.ListingNames("{}")); + } + + private static SECEdgarIndex.Entry SampleEntry() + { + return new SECEdgarIndex.Entry("13F-HR", 107136, new DateTime(2026, 8, 14), + "edgar/data/107136/0001214659-26-010148.txt"); + } + + /// A full submission file cut down to the two documents the reader uses. + private static string SampleSubmission() + { + return string.Join("\n", + "0001214659-26-010148.txt : 20260814", + "", + "13F-HR", + "", + "", + "", + "" + + "13F-HR/A06-30-2026" + + "true1" + + "RESTATEMENT05-15-2026" + + "WEALTH ADVISORS, INC." + + "Someone Elsetrue" + + "", + "", + "", + "", + "", + "INFORMATION TABLE", + "", + "", + "" + + "88025U109274974" + + "7172SH" + + "717200" + + "" + + "COM0378331001000" + + "50SH" + + "PutDFND" + + "1, 2050" + + "", + "", + "", + "", + ""); + } + + // ---- Fixtures ----------------------------------------------------------------------------- + + + /// One INFOTABLE.tsv line, in the column order the header below declares. + private static string Line(string accession, string cusip, string value, string amount, string shareType, + string putCall = "", string votingSole = "0", string votingShared = "0", + string titleOfClass = "COM", string discretion = "SOLE", string otherManager = "", + string votingNone = "0") + { + return string.Join('\t', accession, cusip, titleOfClass, value, amount, shareType, putCall, + discretion, otherManager, votingSole, votingShared, votingNone); + } + + /// One SUBMISSION row, keyed by its accession number the way the processor keys it. + private static KeyValuePair Submission(string accession, + DateTime filingDate, DateTime period, int cik = 1) + { + return new KeyValuePair(accession, + new SEC13FDownloader.Submission + { + Cik = cik, + FilingDate = filingDate, + Period = period + }); + } + + /// + /// Runs the real INFOTABLE reader over an in-memory archive. The unit break and the amendment + /// rule live inside that loop, so a test that rebuilt them here would only be asserting + /// itself; this drives the shipped code over the table shape the SEC publishes. + /// + private Dictionary ReadInfoTable( + string[] lines, params KeyValuePair[] submissions) + { + return ReadInfoTable(lines, false, submissions); + } + + /// + /// The same reader, with the four CUSIPs the unit tests use resolvable through the crosswalk + /// when is set, so their lines are held against the closes + /// SeedCloses wrote. Unset, no CUSIP resolves and every line falls back to its filing's rule. + /// + private Dictionary ReadInfoTable( + string[] lines, bool resolve, params KeyValuePair[] submissions) + { + var text = new StringBuilder() + .AppendLine(string.Join('\t', "ACCESSION_NUMBER", "CUSIP", "TITLEOFCLASS", "VALUE", + "SSHPRNAMT", "SSHPRNAMTTYPE", "PUTCALL", "INVESTMENTDISCRETION", "OTHERMANAGER", + "VOTING_AUTH_SOLE", "VOTING_AUTH_SHARED", "VOTING_AUTH_NONE")); + + foreach (var line in lines) + { + text.AppendLine(line); + } + + using var buffer = new MemoryStream(); + using (var writing = new ZipArchive(buffer, ZipArchiveMode.Create, true)) + using (var entry = new StreamWriter(writing.CreateEntry("INFOTABLE.tsv").Open())) + { + entry.Write(text.ToString()); + } + + buffer.Position = 0; + using var archive = new ZipArchive(buffer, ZipArchiveMode.Read); + using var downloader = Downloader(); + downloader.TickerCrosswalk = resolve + ? UnitTestCrosswalk() + : new Dictionary(); + + return downloader.ReadInfoTable( + archive, + new SEC13FDownloader.Archive("2024q1_form13f.zip", "https://localhost/2024q1_form13f.zip", + new DateTime(2024, 1, 1), new DateTime(2024, 3, 31)), + submissions.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal)); + } + + /// The CUSIPs the unit tests name, to the tickers SeedCloses lists. + private static Dictionary UnitTestCrosswalk() + { + var observed = new DateTime(2026, 1, 1); + return new Dictionary + { + ["037833100"] = new("AAPL", observed), + ["594918104"] = new("MSFT", observed), + ["67066G104"] = new("NVDA", observed), + ["78462F103"] = new("SPY", observed) + }; + } + + /// + /// Writes one trading day's coarse file with these closes, and map files listing the tickers + /// over every date used, so a crosswalk CUSIP resolves to the identifier the file is keyed by. + /// + private void SeedCloses(string day, params (string Ticker, decimal Close)[] closes) + { + SeedMapFiles("aapl", "msft", "nvda", "spy", "defi", "etsy"); + + var coarse = Path.Combine(_root, "data", "equity", "usa", "fundamental", "coarse"); + Directory.CreateDirectory(coarse); + File.WriteAllLines(Path.Combine(coarse, $"{day}.csv"), closes.Select(pair => + $"{ListedSince1980(pair.Ticker)},{pair.Ticker.ToUpperInvariant()}," + + $"{pair.Close.ToString(System.Globalization.CultureInfo.InvariantCulture)},100,1000,True,1,1")); + } + + /// The identifier SeedMapFiles gives a ticker: listed on its first row, 1980-12-12. + private static SecurityIdentifier ListedSince1980(string ticker) + { + return SecurityIdentifier.GenerateEquity(new DateTime(1980, 12, 12), ticker.ToUpperInvariant(), Market.USA); + } + + + private static SEC13FEdgarDay.Filing OptionFiling(DateTime day) + { + return new SEC13FEdgarDay.Filing + { + Accession = "0000000000-26-000001", SubmissionType = "13F-HR", Cik = 1, Filed = day, + Period = new DateTime(2026, 6, 30), ManagerName = "A MANAGER" + }; + } + + /// Runs a filing through the processor, when given one, and reads a ticker's rows of the day. + private List PublishedRows(DateTime day, SEC13FEdgarDay.Filing filing, string ticker) + { + if (filing != null) + { + using var downloader = new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), + null, Path.Combine(_root, "raw")); + downloader.TickerCrosswalk = new Dictionary(); + + ProcessEdgarDay(downloader, day, filing); + downloader.FinalizeSecurityFiles(); + } + + return EntryLines(Path.Combine(_root, "out", SEC13FHolding.ReportFolder, $"{ticker}.zip"), $"{day:yyyyMMdd}.csv"); + } + + /// One trading day's coarse file with these closes, keyed by security. + private void SeedCoarse(string day, params (SecurityIdentifier Security, decimal Close)[] closes) + { + var coarse = Path.Combine(_root, "data", "equity", "usa", "fundamental", "coarse"); + Directory.CreateDirectory(coarse); + File.WriteAllLines(Path.Combine(coarse, $"{day}.csv"), closes.Select(pair => + $"{pair.Security},{pair.Security.Symbol},{pair.Close.ToString(System.Globalization.CultureInfo.InvariantCulture)},100,1000,True,1,1")); + } + + /// Writes the sample filing as the EDGAR archive of a day and folds it in, as a run does. + private void ProcessEdgarDay(SEC13FDownloader downloader, DateTime day, SEC13FEdgarDay.Filing filing = null) + { + var cache = Path.Combine(_root, "raw", SEC13FHolding.ReportFolder, "archives"); + Directory.CreateDirectory(cache); + using (var stream = File.Create(Path.Combine(cache, SEC13FEdgarDay.ArchiveName(day)))) + { + SEC13FEdgarDay.WriteArchive(stream, [filing ?? SEC13FEdgarDay.ParseFiling(SampleEntry(), SampleSubmission())]); + } + + downloader.ProcessArchive(new SEC13FDownloader.Archive( + SEC13FEdgarDay.ArchiveName(day), "https://localhost/day", day, day, IsDaily: true)); + downloader.FlushPendingRows(); + } + + /// A published security zip holding one entry per date given. + private static void SeedPublishedZip(string shelf, string ticker, params string[] dates) + { + SeedPublishedZip(shelf, ticker, 1, dates); + } + + /// A published security zip whose every row was filed by one manager. + private static void SeedPublishedZip(string shelf, string ticker, int cik, params string[] dates) + { + using var zip = ZipFile.Open(Path.Combine(shelf, $"{ticker}.zip"), ZipArchiveMode.Create); + foreach (var date in dates) + { + using var writer = new StreamWriter(zip.CreateEntry($"{date}.csv").Open()); + writer.Write($"{date},0000000000-24-000001,{cik},20231231,13F-HR,,,COM,1,SH,1,0,,SOLE,,1,0,0,0,\n"); + } + } + + private static List EntryLines(string zipPath, string entry) + { + using var zip = ZipFile.OpenRead(zipPath); + using var reader = new StreamReader(zip.GetEntry(entry).Open()); + return reader.ReadToEnd().Split('\n', StringSplitOptions.RemoveEmptyEntries).ToList(); + } + + /// + /// Points LEAN's data folder at a map file archive this test wrote, holding one row per + /// ticker that spans every date used here. + /// + /// Publication turns each ticker into a SecurityIdentifier point in time, and + /// LocalZipMapFileProvider throws outright when it finds no archive at all, so without this + /// the test could only be skipped on a machine with no LEAN data checkout, which is most of + /// them. The lookup date is pinned rather than left to walk back from yesterday, so the + /// fixture does not expire. + /// + private void SeedMapFiles(params string[] tickers) + { + SeedMapFileRows(tickers + .Select(ticker => (ticker, new[] { $"19801212,{ticker}", $"20501231,{ticker}" })) + .ToArray()); + } + + /// A downloader writing into this test's own temporary directories. + private SEC13FDownloader Downloader() + { + return new SEC13FDownloader(Path.Combine(_root, "out"), Path.Combine(_root, "processed"), null); + } + } +} diff --git a/tests/SEC13FTests.cs b/tests/SEC13FTests.cs new file mode 100644 index 0000000..ce6bd5d --- /dev/null +++ b/tests/SEC13FTests.cs @@ -0,0 +1,483 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using QuantConnect.Data; +using QuantConnect.Data.UniverseSelection; +using QuantConnect.DataSource; +using QuantConnect.Python; + +namespace QuantConnect.DataLibrary.Tests +{ + /// + /// Unit tests for and . Every case + /// parses an in-line sample line, so the fixture needs no data on disk and is safe to run in CI. + /// The one test that touches the processed output is skipped when that folder is not there yet. + /// + [TestFixture] + public class SEC13FTests + { + // Frozen line layout, twenty columns: + // FilingDate,Accession,ManagerCik,PeriodEnd,FormType,AmendmentType,AmendmentNumber, + // TitleOfClass,Amount,AmountType,ReportedValue,ValueScale,PutCall,Discretion, + // OtherManager,VotingSole,VotingShared,VotingNone,ConfidentialOmitted,DateReported + private const string FullLine = + "20260814,0001067983-26-000012,1067983,20260630,13F-HR,,,COM,80664820,SH," + + "23341172315,0,,SOLE,,80664820,0,0,0,"; + + // The same position with every optional column withheld, which must give nulls and empties + // rather than zeros and defaults. + private const string EmptyValuesLine = + "20260814,0001067983-26-000012,1067983,20260630,13F-HR,,,,,,,0,,,,,,,0,"; + + private static SubscriptionDataConfig Config(string ticker = "AAPL") + { + return new SubscriptionDataConfig( + typeof(SEC13FHoldings), + Symbol.Create(ticker, SecurityType.Base, Market.USA), + Resolution.Daily, + TimeZones.NewYork, + TimeZones.NewYork, + false, false, false); + } + + /// Reads one line through the factory, the way the folding reader does. + private static SEC13FHolding Read(string line, string ticker = "AAPL") + { + return new SEC13FHolding().Reader(Config(ticker), line, DateTime.UtcNow, false) as SEC13FHolding; + } + + /// Builds the collection LEAN would fold a file's lines into. + private static SEC13FHoldings Fold(params string[] lines) + { + var records = lines.Select(line => Read(line)).Where(record => record != null).ToList(); + return new SEC13FHoldings + { + Symbol = Config().Symbol, + Time = records[0].Time, + EndTime = records[0].EndTime, + Data = records.Cast().ToList() + }; + } + + [Test] + public void ReaderParsesEveryColumn() + { + var holding = Read(FullLine); + + Assert.AreEqual(new DateTime(2026, 8, 14), holding.Time); + Assert.AreEqual("0001067983-26-000012", holding.AccessionNumber); + Assert.AreEqual(1067983, holding.ManagerCik); + Assert.AreEqual(new DateTime(2026, 6, 30), holding.PeriodEnd); + Assert.AreEqual("13F-HR", holding.FormType); + Assert.AreEqual("", holding.AmendmentType); + Assert.IsNull(holding.AmendmentNumber); + Assert.AreEqual("COM", holding.TitleOfClass); + Assert.AreEqual(80664820m, holding.Amount); + Assert.AreEqual("SH", holding.AmountType); + Assert.AreEqual(23341172315m, holding.ReportedValue); + Assert.AreEqual(0, holding.ValueScale); + Assert.IsNull(holding.PutCall); + Assert.AreEqual("SOLE", holding.InvestmentDiscretion); + Assert.AreEqual("", holding.OtherManager); + Assert.AreEqual(80664820m, holding.VotingSole); + Assert.AreEqual(0m, holding.VotingShared); + Assert.AreEqual(0m, holding.VotingNone); + Assert.IsFalse(holding.ConfidentialOmitted); + Assert.IsNull(holding.DateReported); + } + + [Test] + public void ThePointCoversItsFilingDateAndIsEmittedWhenThatDayEnds() + { + // LEAN emits a point at its end time, not at its time, so this pair is what decides when + // an algorithm sees a filing: a filing made on the 14th reaches it at 00:00 on the 15th, + // after EDGAR has finished listing the 14th at about 22:05 ET. Reading the end time as + // anything but the moment of delivery is the mistake this test exists to prevent. + var holding = Read(FullLine); + + Assert.AreEqual(new DateTime(2026, 8, 14), holding.Time, "covers its filing date"); + Assert.AreEqual(new DateTime(2026, 8, 15), holding.EndTime, "delivered when that day ends"); + Assert.AreEqual(TimeSpan.FromDays(1), holding.EndTime - holding.Time, + "a whole day of filings arrives at once, never partway through the day itself"); + } + + [Test] + public void PeriodEndIsUnrelatedToTheFilingDate() + { + // A quarter is reported up to 45 days after it ends, and an amendment can restate one + // years later, so the reported quarter is carried and never derived from the timestamp. + var holding = Read(FullLine); + + Assert.AreEqual(new DateTime(2026, 6, 30), holding.PeriodEnd); + Assert.Greater(holding.Time, holding.PeriodEnd); + } + + [TestCase("20130101", "20130101")] + [TestCase("20260630", "20130101")] + public void AnyFilingLagIsLegitimateData(string filingDate, string periodEnd) + { + // A filing made the day its quarter ended and one made thirteen years late are both + // real: neither is treated as an error. + var line = FullLine.Replace("20260630", periodEnd).Replace("20260814", filingDate); + var holding = Read(line); + + Assert.AreEqual(DateTime.ParseExact(filingDate, "yyyyMMdd", null), holding.Time); + Assert.AreEqual(DateTime.ParseExact(periodEnd, "yyyyMMdd", null), holding.PeriodEnd); + } + + [Test] + public void EmptyNumericColumnsBecomeNullNotZero() + { + // A withheld reading and a reported zero are different facts and stay distinguishable. + var holding = Read(EmptyValuesLine); + + Assert.IsNull(holding.Amount); + Assert.IsNull(holding.ReportedValue); + Assert.IsNull(holding.VotingSole); + Assert.IsNull(holding.VotingShared); + Assert.IsNull(holding.VotingNone); + Assert.IsNull(holding.MarketValue); + } + + [Test] + public void ReportedValueIsLeftAsFiledAndMarketValueAppliesTheScale() + { + // The SEC asked for thousands before 2023 and whole dollars after, and filers on both + // sides ignore the instruction, so the number is published as filed with the scale + // beside it rather than multiplied into it. + var inThousands = Read(FullLine.Replace("23341172315,0,", "23341172,3,")); + + Assert.AreEqual(23341172m, inThousands.ReportedValue); + Assert.AreEqual(3, inThousands.ValueScale); + Assert.AreEqual(23341172000m, inThousands.MarketValue); + Assert.AreEqual(23341172000m, inThousands.Value); + } + + [Test] + public void AValueOverstatedAThousandfoldIsBroughtBackDown() + { + // The scale runs both ways. Three SPY lines of the March 2023 quarter carried a value a + // thousand times the price, and a flag that only said "in thousands" would have had + // nowhere to put them, leaving them wrong by six orders of magnitude. + var overstated = Read(FullLine.Replace("23341172315,0,", "23341172315,-3,")); + + Assert.AreEqual(23341172315m, overstated.ReportedValue); + Assert.AreEqual(-3, overstated.ValueScale); + Assert.AreEqual(23341172.315m, overstated.MarketValue); + } + + [Test] + public void MarketValueIsDerivedAndNeverStored() + { + // Computed from the two columns that are stored, so it cannot drift out of step with + // them the way a third stored column could. + Assert.IsNull(typeof(SEC13FHolding).GetProperty(nameof(SEC13FHolding.MarketValue)).SetMethod); + } + + [TestCase("C", OptionRight.Call)] + [TestCase("P", OptionRight.Put)] + public void AnOptionLineCarriesItsSide(string column, OptionRight expected) + { + var holding = Read(FullLine.Replace("23341172315,0,,SOLE", $"23341172315,0,{column},SOLE")); + + Assert.AreEqual(expected, holding.PutCall); + } + + [Test] + public void AShareLineHasNoOptionSide() + { + Assert.IsNull(Read(FullLine).PutCall); + } + + [Test] + public void ADateReportedIsReadWhenTheFilingCarriesOne() + { + // Filled on about two filings in a thousand, where it marks positions that were + // withheld under confidential treatment and released later. + var holding = Read(FullLine + "20260214"); + + Assert.AreEqual(new DateTime(2026, 2, 14), holding.DateReported); + } + + [Test] + public void ATruncatedLineIsSkippedRatherThanThrown() + { + // An exception out of Reader ends the algorithm, so a short line yields nothing. + Assert.IsNull(Read("20260814,0001067983-26-000012,1067983")); + } + + [Test] + public void AnExtraColumnStillParses() + { + // The column count is tested as "fewer than", so a column appended in a later revision + // of the file leaves every existing one readable instead of muting the dataset. + var holding = Read(FullLine + ",something-new"); + + Assert.AreEqual(80664820m, holding.Amount); + } + + [Test] + public void GetSourceIsAnEntryOfThePerSecurityZip() + { + var source = new SEC13FHoldings() + .GetSource(Config("aapl"), new DateTime(2026, 8, 14), false); + + Assert.AreEqual(SubscriptionTransportMedium.LocalFile, source.TransportMedium); + Assert.AreEqual(FileFormat.FoldingCollection, source.Format); + StringAssert.EndsWith(Path.Combine("alternative", "sec", "13f", "aapl.zip#20260814.csv"), source.Source); + } + + [Test] + public void TheManagersNameComesFromManagersCsv() + { + var previous = Globals.DataFolder; + var root = Path.Combine(Path.GetTempPath(), $"sec-13f-names-{Guid.NewGuid():N}"); + var folder = Path.Combine(root, "alternative", "sec", "13f"); + Directory.CreateDirectory(folder); + File.WriteAllLines(Path.Combine(folder, "managers.csv"), ["1067983,BERKSHIRE HATHAWAY INC"]); + + try + { + Configuration.Config.Set("data-folder", root); + Globals.Reset(); + SEC13FManagerNameProvider.Reset(); + + Assert.AreEqual("BERKSHIRE HATHAWAY INC", Read(FullLine).ManagerName); + Assert.IsNull(Read(FullLine.Replace(",1067983,", ",42,")).ManagerName); + } + finally + { + Configuration.Config.Set("data-folder", previous); + Globals.Reset(); + SEC13FManagerNameProvider.Reset(); + Directory.Delete(root, true); + } + } + + [Test] + public void AMissingManagersFileIsNotTakenForTheDaysAnswer() + { + // The file is read once a day, since it only gains managers overnight. A read that found + // nothing is not that day's answer: stamped as one, a single missed fetch would leave + // every name null until midnight. + var previous = Globals.DataFolder; + var retry = SEC13FManagerNameProvider.RetryInterval; + var root = Path.Combine(Path.GetTempPath(), $"sec-13f-names-{Guid.NewGuid():N}"); + var folder = Path.Combine(root, "alternative", "sec", "13f"); + var file = Path.Combine(folder, "managers.csv"); + Directory.CreateDirectory(folder); + + try + { + Configuration.Config.Set("data-folder", root); + Globals.Reset(); + SEC13FManagerNameProvider.Reset(); + + // A read that found the file stamps the day, and Reset drops that stamp along with + // the names. Left behind it would stand as the answer for the day this test is + // about, and whether an earlier test in the process stamped it would decide the + // outcome here. + File.WriteAllLines(file, ["1067983,BERKSHIRE HATHAWAY INC"]); + Assert.AreEqual("BERKSHIRE HATHAWAY INC", Read(FullLine).ManagerName, "the day is stamped"); + File.Delete(file); + SEC13FManagerNameProvider.Reset(); + + Assert.IsNull(Read(FullLine).ManagerName, "there is no file yet"); + + File.WriteAllLines(file, ["1067983,BERKSHIRE HATHAWAY INC"]); + SEC13FManagerNameProvider.RetryInterval = TimeSpan.Zero; + + Assert.AreEqual("BERKSHIRE HATHAWAY INC", Read(FullLine).ManagerName, "and now there is"); + } + finally + { + SEC13FManagerNameProvider.RetryInterval = retry; + Configuration.Config.Set("data-folder", previous); + Globals.Reset(); + SEC13FManagerNameProvider.Reset(); + Directory.Delete(root, true); + } + } + + [Test] + public void TheCollectionCarriesEveryPositionOfTheDay() + { + // Two managers reporting the same security on the same day give two records in one + // point. Nothing about them is combined. + var other = FullLine.Replace("1067983", "1350694").Replace("80664820", "1000000"); + var point = Fold(FullLine, other); + + Assert.AreEqual(2, point.Data.Count); + CollectionAssert.AreEquivalent( + new[] { 1067983, 1350694 }, + point.Data.Cast().Select(holding => holding.ManagerCik)); + } + + [Test] + public void OneManagerReportingTwiceStaysTwoRecords() + { + // The rules let a manager report a security on more than one line when the discretion + // differs, and Berkshire does exactly that with Moody's. Folding the two together would + // be deriving a number no filing states. + var second = FullLine.Replace("SOLE", "DFND").Replace("80664820,SH", "500000,SH"); + var point = Fold(FullLine, second); + + Assert.AreEqual(2, point.Data.Count); + CollectionAssert.AreEqual( + new[] { "SOLE", "DFND" }, + point.Data.Cast().Select(holding => holding.InvestmentDiscretion)); + } + + [Test] + public void EveryRecordCarriesThePointsSymbol() + { + var point = Fold(FullLine, FullLine.Replace("1067983", "1350694")); + + Assert.IsTrue(point.Data.Cast().All(holding => holding.Symbol == point.Symbol)); + } + + [Test] + public void TheCollectionDeclaresNoReadingsOfItsOwn() + { + // LEAN builds the collection itself and sets only its symbol and timestamps, so a + // measure declared on it would silently stay null. This is the guard against someone + // adding one back. + var declared = typeof(SEC13FHoldings) + .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) + .Select(property => property.Name) + .ToList(); + + CollectionAssert.IsEmpty(declared, + "SEC13FHoldings must delegate to the factory; a property here would never be filled"); + } + + [Test] + public void CloneCopiesEveryProperty() + { + var original = Read(FullLine); + var clone = (SEC13FHolding)original.Clone(); + + foreach (var property in typeof(SEC13FHolding) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => property.CanRead && property.GetIndexParameters().Length == 0)) + { + Assert.AreEqual(property.GetValue(original), property.GetValue(clone), property.Name); + } + } + + [Test] + public void CloneCopiesTheRecordsRatherThanSharingThem() + { + var point = Fold(FullLine); + var clone = (SEC13FHoldings)point.Clone(); + + Assert.AreNotSame(point.Data[0], clone.Data[0]); + Assert.AreEqual( + ((SEC13FHolding)point.Data[0]).AccessionNumber, + ((SEC13FHolding)clone.Data[0]).AccessionNumber); + } + + [Test] + public void ClassificationMatchesTheSpec() + { + var factory = new SEC13FHolding(); + var collection = new SEC13FHoldings(); + + foreach (var type in new BaseData[] { factory, collection }) + { + Assert.IsTrue(type.RequiresMapping(), "linked to equities, so renames apply"); + Assert.IsTrue(type.IsSparseData(), "a security is reported only on the days managers file"); + Assert.AreEqual(Resolution.Daily, type.DefaultResolution()); + CollectionAssert.AreEqual(new[] { Resolution.Daily }, type.SupportedResolutions()); + Assert.AreEqual(TimeZones.NewYork, type.DataTimeZone()); + } + } + + [Test] + public void ToStringNamesTheManagerAndThePosition() + { + StringAssert.Contains("1067983", Read(FullLine).ToString()); + StringAssert.Contains("80664820", Read(FullLine).ToString()); + } + + [Test] + public void AJsonRoundTripKeepsEveryProperty() + { + // Compared by reflection rather than field by field, so a property added later is + // covered without anyone remembering to extend this test. + var original = Read(FullLine); + var restored = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(original)); + + foreach (var property in typeof(SEC13FHolding) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(property => property.CanRead && property.GetIndexParameters().Length == 0)) + { + Assert.AreEqual(property.GetValue(original), property.GetValue(restored), property.Name); + } + } + + [Test] + public void EveryProcessedRowParses() + { + // Reads the processor's own output when it is there, which is the only case that proves + // the writer and the reader agree on the layout. + // From the test binaries rather than the working directory, which the runner chooses. + var folder = Path.Combine(TestContext.CurrentContext.TestDirectory, + "..", "..", "..", "..", "output", "alternative", "sec", "13f"); + if (!Directory.Exists(folder)) + { + Assert.Ignore($"No processed output at {folder}"); + } + + var zips = Directory.GetFiles(folder, "*.zip"); + if (zips.Length == 0) + { + Assert.Ignore($"No per security zips in {folder}"); + } + + var rows = 0; + foreach (var path in zips) + { + using var zip = System.IO.Compression.ZipFile.OpenRead(path); + foreach (var entry in zip.Entries) + { + using var reader = new StreamReader(entry.Open()); + string line; + while ((line = reader.ReadLine()) != null) + { + var holding = Read(line, Path.GetFileNameWithoutExtension(path)); + Assert.IsNotNull(holding, $"{path}#{entry.Name}: {line}"); + + // The entry is named after the filing date every line in it carries. + Assert.AreEqual(Path.GetFileNameWithoutExtension(entry.Name), + holding.Time.ToString("yyyyMMdd"), $"{path}#{entry.Name}"); + Assert.IsFalse(holding.OtherManager.Contains(";;"), $"{path}#{entry.Name}: {line}"); + rows++; + } + } + } + + Assert.Greater(rows, 0, "the output holds no rows"); + } + } +} diff --git a/tests/Tests.csproj b/tests/Tests.csproj index c3a31c6..e8637ae 100644 --- a/tests/Tests.csproj +++ b/tests/Tests.csproj @@ -11,6 +11,8 @@ + + PreserveNewest