diff --git a/aiofmp/cachedclient/registry.py b/aiofmp/cachedclient/registry.py index 76eb644..5704f3a 100644 --- a/aiofmp/cachedclient/registry.py +++ b/aiofmp/cachedclient/registry.py @@ -541,6 +541,32 @@ def build_default_registry() -> EndpointRegistry: ) ) + # --- Insider trades, per symbol --- + # + # `latest_insider_trades` above is a GLOBAL feed. Callers harvesting a + # universe symbol by symbol use `search_insider_trades` instead, and without + # an entry here every one of those calls goes uncached: a nightly harvest of + # ~5,900 symbols measured 60KB per page across ~12 pages each, about 4.3GB a + # night against a plan that meters bytes on a rolling window. + registry.register( + CacheableEndpoint( + category="insider_trades", + method="search_insider_trades", + api_endpoint="insider-trading/search", + pattern=TemporalPattern.PAGE_WALK, + # Sharded per symbol, unlike the global feed above. + entity_key_args=["symbol"], + page_param="page", + limit_param="limit", + # Form 4 allows two business days to file, so the filing date is + # what orders the feed -- transactionDate runs behind it and would + # make the walk look out of order. + walk_date_field="filingDate", + default_page_size=100, + call_params=["symbol", "page", "limit"], + ) + ) + # --- Form 13F (global walk; per-CIK shard on write) --- registry.register( CacheableEndpoint( diff --git a/tests/test_cachedclient/test_page_walk_registry.py b/tests/test_cachedclient/test_page_walk_registry.py index 58b3c8c..6656a2d 100644 --- a/tests/test_cachedclient/test_page_walk_registry.py +++ b/tests/test_cachedclient/test_page_walk_registry.py @@ -153,6 +153,26 @@ def test_insider_trades_latest_registered(self) -> None: assert ep.page_param == "page" assert ep.walk_date_field == "filingDate" + def test_insider_trades_search_registered(self) -> None: + """The per-symbol feed, distinct from the global `latest` one above. + + A harvest that walks a universe symbol by symbol uses this method, so + without an entry every call bypasses the cache -- the case that made a + caller's nightly run spend ~4.3GB against a byte-metered plan. + """ + reg = build_default_registry() + ep = reg.get("insider_trades", "search_insider_trades") + assert ep is not None + assert ep.pattern == TemporalPattern.PAGE_WALK + assert ep.api_endpoint == "insider-trading/search" + # Sharded per symbol, unlike the global feed. + assert ep.entity_key_args == ["symbol"] + assert ep.page_param == "page" + assert ep.limit_param == "limit" + # Form 4 allows two business days to file, so filingDate orders the + # feed; transactionDate runs behind it. + assert ep.walk_date_field == "filingDate" + def test_form13f_latest_filings_registered(self) -> None: reg = build_default_registry() ep = reg.get("form13f", "latest_filings")