diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d7105d15..4d083eed 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @justinpolygon @penelopus @davidwf-polygonio +* @justinpolygon @suever @kschoche @lukeoleson @mmoghaddam385 @jbonzo diff --git a/massive/rest/economy.py b/massive/rest/economy.py index f8d58272..4c4b251f 100644 --- a/massive/rest/economy.py +++ b/massive/rest/economy.py @@ -8,6 +8,8 @@ TreasuryYield, FedInflationExpectations, FedLaborMarket, + EUMerchantAggregate, + EUMerchantHierarchy, ) from .models.common import Sort, Order from .models.request import RequestOptionBuilder @@ -168,3 +170,103 @@ def list_labor_market_indicators( raw=raw, options=options, ) + + def list_eu_merchant_aggregates( + self, + transaction_date: Optional[Union[str, date]] = None, + transaction_date_gt: Optional[Union[str, date]] = None, + transaction_date_gte: Optional[Union[str, date]] = None, + transaction_date_lt: Optional[Union[str, date]] = None, + transaction_date_lte: Optional[Union[str, date]] = None, + name: Optional[str] = None, + name_any_of: Optional[str] = None, + name_gt: Optional[str] = None, + name_gte: Optional[str] = None, + name_lt: Optional[str] = None, + name_lte: Optional[str] = None, + user_country: Optional[str] = None, + user_country_any_of: Optional[str] = None, + channel: Optional[str] = None, + channel_any_of: Optional[str] = None, + consumer_type: Optional[str] = None, + consumer_type_any_of: Optional[str] = None, + parent_name: Optional[str] = None, + parent_name_any_of: Optional[str] = None, + parent_name_gt: Optional[str] = None, + parent_name_gte: Optional[str] = None, + parent_name_lt: Optional[str] = None, + parent_name_lte: Optional[str] = None, + limit: Optional[int] = None, + sort: Optional[Union[str, Sort]] = None, + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[EUMerchantAggregate], HTTPResponse]: + """ + Aggregated consumer transactions from European credit card panels (UK, DE, FR, IT, ES, AT). + Each row represents daily credit card, debit card, or open banking transactions + (7-day lag from transaction date) at a tagged merchant or payment processor. + + Includes ticker (Bloomberg) and industry mapping for ~250 US public companies. + User counts provided across 8- and 28-day windows for normalization. + """ + url = "/consumer-spending/eu/v1/merchant-aggregates" + + return self._paginate( + path=url, + params=self._get_params(self.list_eu_merchant_aggregates, locals()), + deserializer=EUMerchantAggregate.from_dict, + raw=raw, + result_key="results", + options=options, + ) + + def list_eu_merchant_hierarchy( + self, + lookup_name: Optional[str] = None, + lookup_name_any_of: Optional[str] = None, + lookup_name_gt: Optional[str] = None, + lookup_name_gte: Optional[str] = None, + lookup_name_lt: Optional[str] = None, + lookup_name_lte: Optional[str] = None, + ticker: Optional[str] = None, + ticker_any_of: Optional[str] = None, + ticker_gt: Optional[str] = None, + ticker_gte: Optional[str] = None, + ticker_lt: Optional[str] = None, + ticker_lte: Optional[str] = None, + listing_status: Optional[str] = None, + listing_status_any_of: Optional[str] = None, + active_from: Optional[Union[str, date]] = None, + active_from_gt: Optional[Union[str, date]] = None, + active_from_gte: Optional[Union[str, date]] = None, + active_from_lt: Optional[Union[str, date]] = None, + active_from_lte: Optional[Union[str, date]] = None, + active_to: Optional[Union[str, date]] = None, + active_to_gt: Optional[Union[str, date]] = None, + active_to_gte: Optional[Union[str, date]] = None, + active_to_lt: Optional[Union[str, date]] = None, + active_to_lte: Optional[Union[str, date]] = None, + limit: Optional[int] = None, + sort: Optional[Union[str, Sort]] = None, + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[EUMerchantHierarchy], HTTPResponse]: + """ + Reference data mapping merchants to parent companies, tickers, sectors, + and industries across Fable's European consumer transaction panel. + + Use lookup_name + active_from/active_to to join with merchant-aggregates + for point-in-time queries. + """ + url = "/consumer-spending/eu/v1/merchant-hierarchy" + + return self._paginate( + path=url, + params=self._get_params(self.list_eu_merchant_hierarchy, locals()), + deserializer=EUMerchantHierarchy.from_dict, + raw=raw, + result_key="results", + options=options, + ) diff --git a/massive/rest/futures.py b/massive/rest/futures.py index 9ef4e603..8973c7af 100644 --- a/massive/rest/futures.py +++ b/massive/rest/futures.py @@ -21,7 +21,7 @@ class FuturesClient(BaseClient): """ Client for the Futures REST Endpoints - (aligned with the paths from /futures/vX/...) + (aligned with the paths from /futures/v1/...) """ def list_futures_aggregates( @@ -40,7 +40,7 @@ def list_futures_aggregates( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesAgg], HTTPResponse]: """ - Endpoint: GET /futures/vX/aggs/{ticker} + Endpoint: GET /futures/v1/aggs/{ticker} Get aggregates for a futures contract in a given time range. This endpoint returns data that includes: @@ -48,7 +48,7 @@ def list_futures_aggregates( - volume, dollar_volume, etc. If `next_url` is present, it will be paginated. """ - url = f"/futures/vX/aggs/{ticker}" + url = f"/futures/v1/aggs/{ticker}" return self._paginate( path=url, params=self._get_params(self.list_futures_aggregates, locals()), @@ -96,11 +96,11 @@ def list_futures_contracts( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesContract], HTTPResponse]: """ - Endpoint: GET /futures/vX/contracts + Endpoint: GET /futures/v1/contracts The Contracts endpoint returns a paginated list of futures contracts. """ - url = "/futures/vX/contracts" + url = "/futures/v1/contracts" return self._paginate( path=url, params=self._get_params(self.list_futures_contracts, locals()), @@ -151,11 +151,11 @@ def list_futures_products( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesProduct], HTTPResponse]: """ - Endpoint: GET /futures/vX/products + Endpoint: GET /futures/v1/products Returns a list of futures products (including combos). """ - url = "/futures/vX/products" + url = "/futures/v1/products" return self._paginate( path=url, params=self._get_params(self.list_futures_products, locals()), @@ -184,11 +184,11 @@ def list_futures_quotes( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesQuote], HTTPResponse]: """ - Endpoint: GET /futures/vX/quotes/{ticker} + Endpoint: GET /futures/v1/quotes/{ticker} Get quotes for a contract in a given time range (paginated). """ - url = f"/futures/vX/quotes/{ticker}" + url = f"/futures/v1/quotes/{ticker}" return self._paginate( path=url, params=self._get_params(self.list_futures_quotes, locals()), @@ -217,11 +217,11 @@ def list_futures_trades( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesTrade], HTTPResponse]: """ - Endpoint: GET /futures/vX/trades/{ticker} + Endpoint: GET /futures/v1/trades/{ticker} Get trades for a contract in a given time range (paginated). """ - url = f"/futures/vX/trades/{ticker}" + url = f"/futures/v1/trades/{ticker}" return self._paginate( path=url, params=self._get_params(self.list_futures_trades, locals()), @@ -239,7 +239,6 @@ def list_futures_schedules( product_code_lt: Optional[str] = None, product_code_lte: Optional[str] = None, session_end_date: Optional[str] = None, - session_end_date_any_of: Optional[str] = None, session_end_date_gt: Optional[str] = None, session_end_date_gte: Optional[str] = None, session_end_date_lt: Optional[str] = None, @@ -257,12 +256,12 @@ def list_futures_schedules( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesSchedule], HTTPResponse]: """ - Endpoint: GET /futures/vX/schedules + Endpoint: GET /futures/v1/schedules Returns a list of trading schedules for multiple futures products on a specific date. If `next_url` is present, this is paginated. """ - url = "/futures/vX/schedules" + url = "/futures/v1/schedules" return self._paginate( path=url, params=self._get_params(self.list_futures_schedules, locals()), @@ -284,7 +283,7 @@ def list_futures_market_statuses( raw: bool = False, options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesMarketStatus], HTTPResponse]: - url = "/futures/vX/market-status" + url = "/futures/v1/market-status" return self._paginate( path=url, params=self._get_params(self.list_futures_market_statuses, locals()), @@ -313,7 +312,7 @@ def get_futures_snapshot( raw: bool = False, options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesSnapshot], HTTPResponse]: - url = "/futures/vX/snapshot" + url = "/futures/v1/snapshot" return self._paginate( path=url, params=self._get_params(self.get_futures_snapshot, locals()), @@ -330,11 +329,11 @@ def list_futures_exchanges( options: Optional[RequestOptionBuilder] = None, ) -> Union[Iterator[FuturesExchange], HTTPResponse]: """ - Endpoint: GET /futures/vX/exchanges + Endpoint: GET /futures/v1/exchanges US futures exchanges and trading venues including major derivatives exchanges (CME, CBOT, NYMEX, COMEX) and other futures market infrastructure for commodity, financial, and other derivative contract trading. """ - url = "/futures/vX/exchanges" + url = "/futures/v1/exchanges" return self._paginate( path=url, params=self._get_params(self.list_futures_exchanges, locals()), diff --git a/massive/rest/models/economy.py b/massive/rest/models/economy.py index e6ae8d4f..97386235 100644 --- a/massive/rest/models/economy.py +++ b/massive/rest/models/economy.py @@ -104,3 +104,126 @@ def from_dict(d): labor_force_participation_rate=d.get("labor_force_participation_rate"), unemployment_rate=d.get("unemployment_rate"), ) + + +@modelclass +class EUMerchantAggregate: + """ + Aggregated consumer transactions from European credit card panels. + Each row represents daily credit card, debit card, or open banking transactions + (7-day lag) at a tagged merchant or payment processor. + """ + + channel: Optional[str] = None + consumer_type: Optional[str] = None + eight_day_rolling_category_accounts: Optional[int] = None + eight_day_rolling_total_accounts: Optional[int] = None + mcc_group: Optional[str] = None + merchant_industry: Optional[str] = None + merchant_ticker: Optional[str] = None + name: Optional[str] = None + parent_name: Optional[str] = None + published_date: Optional[str] = None + spend_in_distinct_account_key_count: Optional[int] = None + spend_in_spend: Optional[float] = None + spend_in_transaction_count: Optional[int] = None + spend_out_distinct_account_key_count: Optional[int] = None + spend_out_spend: Optional[float] = None + spend_out_transaction_count: Optional[int] = None + total_accounts: Optional[int] = None + total_spend: Optional[float] = None + total_transactions: Optional[int] = None + transaction_currency: Optional[str] = None + transaction_date: Optional[str] = None + twenty_eight_day_rolling_category_accounts: Optional[int] = None + twenty_eight_day_rolling_total_accounts: Optional[int] = None + type: Optional[str] = None + user_country: Optional[str] = None + + @staticmethod + def from_dict(d): + return EUMerchantAggregate( + channel=d.get("channel"), + consumer_type=d.get("consumer_type"), + eight_day_rolling_category_accounts=d.get( + "eight_day_rolling_category_accounts" + ), + eight_day_rolling_total_accounts=d.get("eight_day_rolling_total_accounts"), + mcc_group=d.get("mcc_group"), + merchant_industry=d.get("merchant_industry"), + merchant_ticker=d.get("merchant_ticker"), + name=d.get("name"), + parent_name=d.get("parent_name"), + published_date=d.get("published_date"), + spend_in_distinct_account_key_count=d.get( + "spend_in_distinct_account_key_count" + ), + spend_in_spend=d.get("spend_in_spend"), + spend_in_transaction_count=d.get("spend_in_transaction_count"), + spend_out_distinct_account_key_count=d.get( + "spend_out_distinct_account_key_count" + ), + spend_out_spend=d.get("spend_out_spend"), + spend_out_transaction_count=d.get("spend_out_transaction_count"), + total_accounts=d.get("total_accounts"), + total_spend=d.get("total_spend"), + total_transactions=d.get("total_transactions"), + transaction_currency=d.get("transaction_currency"), + transaction_date=d.get("transaction_date"), + twenty_eight_day_rolling_category_accounts=d.get( + "twenty_eight_day_rolling_category_accounts" + ), + twenty_eight_day_rolling_total_accounts=d.get( + "twenty_eight_day_rolling_total_accounts" + ), + type=d.get("type"), + user_country=d.get("user_country"), + ) + + +@modelclass +class EUMerchantHierarchy: + """ + Reference data mapping merchants to parent companies, tickers, sectors, + and industries across Fable's European consumer transaction panel. + """ + + active_from: Optional[str] = None + active_to: Optional[str] = None + category: Optional[str] = None + grandparent_name: Optional[str] = None + grandparent_ticker: Optional[str] = None + great_grandparent_name: Optional[str] = None + great_grandparent_ticker: Optional[str] = None + industry: Optional[str] = None + industry_group: Optional[str] = None + listing_status: Optional[str] = None + lookup_name: Optional[str] = None + normalized_name: Optional[str] = None + parent_name: Optional[str] = None + parent_ticker: Optional[str] = None + sector: Optional[str] = None + sub_industry: Optional[str] = None + ticker: Optional[str] = None + + @staticmethod + def from_dict(d): + return EUMerchantHierarchy( + active_from=d.get("active_from"), + active_to=d.get("active_to"), + category=d.get("category"), + grandparent_name=d.get("grandparent_name"), + grandparent_ticker=d.get("grandparent_ticker"), + great_grandparent_name=d.get("great_grandparent_name"), + great_grandparent_ticker=d.get("great_grandparent_ticker"), + industry=d.get("industry"), + industry_group=d.get("industry_group"), + listing_status=d.get("listing_status"), + lookup_name=d.get("lookup_name"), + normalized_name=d.get("normalized_name"), + parent_name=d.get("parent_name"), + parent_ticker=d.get("parent_ticker"), + sector=d.get("sector"), + sub_industry=d.get("sub_industry"), + ticker=d.get("ticker"), + ) diff --git a/massive/rest/models/financials.py b/massive/rest/models/financials.py index a2989f15..3266da1d 100644 --- a/massive/rest/models/financials.py +++ b/massive/rest/models/financials.py @@ -922,6 +922,117 @@ def from_dict(d): ) +@modelclass +class Disclosure: + """ + A single tagged disclosure within an SEC 8-K filing. A filing can produce + multiple disclosures, each classified into primary, secondary, and tertiary + categories with a supporting text excerpt. + """ + + accession_number: Optional[str] = None + cik: Optional[str] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + primary_category: Optional[str] = None + secondary_category: Optional[str] = None + supporting_text: Optional[str] = None + tertiary_category: Optional[str] = None + tickers: Optional[List[str]] = None + + @staticmethod + def from_dict(d): + return Disclosure( + accession_number=d.get("accession_number"), + cik=d.get("cik"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + primary_category=d.get("primary_category"), + secondary_category=d.get("secondary_category"), + supporting_text=d.get("supporting_text"), + tertiary_category=d.get("tertiary_category"), + tickers=d.get("tickers"), + ) + + +@modelclass +class DisclosureTaxonomy: + """ + A single 8-K disclosure classification, part of the complete list of + classifications used in the 8-K disclosures endpoint. + """ + + description: Optional[str] = None + primary_category: Optional[str] = None + secondary_category: Optional[str] = None + taxonomy: Optional[str] = None + tertiary_category: Optional[str] = None + + @staticmethod + def from_dict(d): + return DisclosureTaxonomy( + description=d.get("description"), + primary_category=d.get("primary_category"), + secondary_category=d.get("secondary_category"), + taxonomy=d.get("taxonomy"), + tertiary_category=d.get("tertiary_category"), + ) + + +@modelclass +@dataclass +class Filing13F: + """SEC Form 13F filings data showing institutional investment manager holdings.""" + + accession_number: Optional[str] = None + cusip: Optional[str] = None + file_number: Optional[str] = None + filer_cik: Optional[str] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + film_number: Optional[str] = None + form_type: Optional[str] = None + investment_discretion: Optional[str] = None + issuer_name: Optional[str] = None + market_value: Optional[int] = None + other_managers: Optional[List[str]] = None + period: Optional[str] = None + put_call: Optional[str] = None + shares_or_principal_amount: Optional[int] = None + shares_or_principal_type: Optional[str] = None + title_of_class: Optional[str] = None + voting_authority_none: Optional[int] = None + voting_authority_shared: Optional[int] = None + voting_authority_sole: Optional[int] = None + + @staticmethod + def from_dict(d: Optional[Dict[str, Any]]) -> "Filing13F": + if not d: + return Filing13F() + return Filing13F( + accession_number=d.get("accession_number"), + cusip=d.get("cusip"), + file_number=d.get("file_number"), + filer_cik=d.get("filer_cik"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + film_number=d.get("film_number"), + form_type=d.get("form_type"), + investment_discretion=d.get("investment_discretion"), + issuer_name=d.get("issuer_name"), + market_value=d.get("market_value"), + other_managers=d.get("other_managers"), + period=d.get("period"), + put_call=d.get("put_call"), + shares_or_principal_amount=d.get("shares_or_principal_amount"), + shares_or_principal_type=d.get("shares_or_principal_type"), + title_of_class=d.get("title_of_class"), + voting_authority_none=d.get("voting_authority_none"), + voting_authority_shared=d.get("voting_authority_shared"), + voting_authority_sole=d.get("voting_authority_sole"), + ) + + @modelclass class FilingSection: """SEC document text section from a 10-K/10-Q (raw text content).""" @@ -947,6 +1058,206 @@ def from_dict(d): ) +@modelclass +@dataclass +class FilingFootnote: + """Footnote from SEC Form 3/4 filings.""" + + id: Optional[str] = None + description: Optional[str] = None + + @staticmethod + def from_dict(d: Optional[Dict[str, Any]]) -> "FilingFootnote": + if not d: + return FilingFootnote() + return FilingFootnote( + id=d.get("id"), + description=d.get("description"), + ) + + +@modelclass +@dataclass +class FilingForm3: + """SEC Form 3 filings reporting initial statements of beneficial ownership of securities. + Filed by corporate insiders (directors, officers, and 10%+ shareholders) when they first acquire a position. + """ + + accession_number: Optional[str] = None + aff_10b5_one: Optional[bool] = None + date_of_original_submission: Optional[str] = None + direct_or_indirect: Optional[str] = None + exercise_date: Optional[str] = None + exercise_price: Optional[float] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + footnotes: Optional[List[FilingFootnote]] = None + form_type: Optional[str] = None + is_director: Optional[bool] = None + is_officer: Optional[bool] = None + is_other: Optional[bool] = None + is_ten_percent_owner: Optional[bool] = None + issuer_cik: Optional[str] = None + issuer_name: Optional[str] = None + nature_of_ownership: Optional[str] = None + not_subject_to_section_16: Optional[bool] = None + officer_title: Optional[str] = None + owner_cik: Optional[str] = None + owner_name: Optional[str] = None + period_of_report: Optional[str] = None + remarks: Optional[str] = None + security_title: Optional[str] = None + security_type: Optional[str] = None + shares_owned: Optional[float] = None + tickers: Optional[List[str]] = None + underlying_security_shares: Optional[float] = None + underlying_security_title: Optional[str] = None + + @staticmethod + def from_dict(d: Optional[Dict[str, Any]]) -> "FilingForm3": + if not d: + return FilingForm3() + footnotes = d.get("footnotes") + return FilingForm3( + accession_number=d.get("accession_number"), + aff_10b5_one=d.get("aff_10b5_one"), + date_of_original_submission=d.get("date_of_original_submission"), + direct_or_indirect=d.get("direct_or_indirect"), + exercise_date=d.get("exercise_date"), + exercise_price=d.get("exercise_price"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + footnotes=( + [FilingFootnote.from_dict(f) for f in footnotes] + if footnotes is not None + else None + ), + form_type=d.get("form_type"), + is_director=d.get("is_director"), + is_officer=d.get("is_officer"), + is_other=d.get("is_other"), + is_ten_percent_owner=d.get("is_ten_percent_owner"), + issuer_cik=d.get("issuer_cik"), + issuer_name=d.get("issuer_name"), + nature_of_ownership=d.get("nature_of_ownership"), + not_subject_to_section_16=d.get("not_subject_to_section_16"), + officer_title=d.get("officer_title"), + owner_cik=d.get("owner_cik"), + owner_name=d.get("owner_name"), + period_of_report=d.get("period_of_report"), + remarks=d.get("remarks"), + security_title=d.get("security_title"), + security_type=d.get("security_type"), + shares_owned=d.get("shares_owned"), + tickers=d.get("tickers"), + underlying_security_shares=d.get("underlying_security_shares"), + underlying_security_title=d.get("underlying_security_title"), + ) + + +@modelclass +@dataclass +class FilingForm4: + """SEC Form 4 filings reporting changes in beneficial ownership of securities. + Filed by corporate insiders (directors, officers, and 10%+ shareholders) within two business days of a transaction. + """ + + accession_number: Optional[str] = None + aff_10b5_one: Optional[bool] = None + date_of_original_submission: Optional[str] = None + deemed_execution_date: Optional[str] = None + direct_or_indirect: Optional[str] = None + equity_swap_involved: Optional[bool] = None + exercise_date: Optional[str] = None + exercise_price: Optional[float] = None + expiration_date: Optional[str] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + footnotes: Optional[List[FilingFootnote]] = None + form_type: Optional[str] = None + is_director: Optional[bool] = None + is_officer: Optional[bool] = None + is_other: Optional[bool] = None + is_ten_percent_owner: Optional[bool] = None + issuer_cik: Optional[str] = None + issuer_name: Optional[str] = None + nature_of_ownership: Optional[str] = None + not_subject_to_section_16: Optional[bool] = None + officer_title: Optional[str] = None + owner_cik: Optional[str] = None + owner_name: Optional[str] = None + period_of_report: Optional[str] = None + record_type: Optional[str] = None + remarks: Optional[str] = None + security_title: Optional[str] = None + security_type: Optional[str] = None + shares_owned_following_transaction: Optional[float] = None + tickers: Optional[List[str]] = None + transaction_acquired_disposed: Optional[str] = None + transaction_code: Optional[str] = None + transaction_date: Optional[str] = None + transaction_price_per_share: Optional[float] = None + transaction_shares: Optional[float] = None + transaction_timeliness: Optional[str] = None + transaction_value: Optional[float] = None + underlying_security_shares: Optional[float] = None + underlying_security_title: Optional[str] = None + + @staticmethod + def from_dict(d: Optional[Dict[str, Any]]) -> "FilingForm4": + if not d: + return FilingForm4() + footnotes = d.get("footnotes") + return FilingForm4( + accession_number=d.get("accession_number"), + aff_10b5_one=d.get("aff_10b5_one"), + date_of_original_submission=d.get("date_of_original_submission"), + deemed_execution_date=d.get("deemed_execution_date"), + direct_or_indirect=d.get("direct_or_indirect"), + equity_swap_involved=d.get("equity_swap_involved"), + exercise_date=d.get("exercise_date"), + exercise_price=d.get("exercise_price"), + expiration_date=d.get("expiration_date"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + footnotes=( + [FilingFootnote.from_dict(f) for f in footnotes] + if footnotes is not None + else None + ), + form_type=d.get("form_type"), + is_director=d.get("is_director"), + is_officer=d.get("is_officer"), + is_other=d.get("is_other"), + is_ten_percent_owner=d.get("is_ten_percent_owner"), + issuer_cik=d.get("issuer_cik"), + issuer_name=d.get("issuer_name"), + nature_of_ownership=d.get("nature_of_ownership"), + not_subject_to_section_16=d.get("not_subject_to_section_16"), + officer_title=d.get("officer_title"), + owner_cik=d.get("owner_cik"), + owner_name=d.get("owner_name"), + period_of_report=d.get("period_of_report"), + record_type=d.get("record_type"), + remarks=d.get("remarks"), + security_title=d.get("security_title"), + security_type=d.get("security_type"), + shares_owned_following_transaction=d.get( + "shares_owned_following_transaction" + ), + tickers=d.get("tickers"), + transaction_acquired_disposed=d.get("transaction_acquired_disposed"), + transaction_code=d.get("transaction_code"), + transaction_date=d.get("transaction_date"), + transaction_price_per_share=d.get("transaction_price_per_share"), + transaction_shares=d.get("transaction_shares"), + transaction_timeliness=d.get("transaction_timeliness"), + transaction_value=d.get("transaction_value"), + underlying_security_shares=d.get("underlying_security_shares"), + underlying_security_title=d.get("underlying_security_title"), + ) + + @modelclass class Filing8K: """Parsed 8-K filing with item-level text content.""" diff --git a/massive/rest/models/futures.py b/massive/rest/models/futures.py index d6502844..c2be928c 100644 --- a/massive/rest/models/futures.py +++ b/massive/rest/models/futures.py @@ -1,4 +1,4 @@ -from typing import Optional, List +from typing import Optional, List, Union from ...modelclass import modelclass @@ -6,7 +6,7 @@ class FuturesAgg: """ A single aggregate bar for a futures contract in a given time window. - Corresponds to /futures/vX/aggs/{ticker}. + Corresponds to /futures/v1/aggs/{ticker}. """ ticker: Optional[str] = None @@ -42,7 +42,7 @@ def from_dict(d): class FuturesContract: """ Represents a single futures contract (or a 'combo' contract). - Corresponds to /futures/vX/contracts endpoints. + Corresponds to /futures/v1/contracts endpoints. """ ticker: Optional[str] = None @@ -90,7 +90,7 @@ def from_dict(d): class FuturesProduct: """ Represents a single futures product (or product 'combo'). - Corresponds to /futures/vX/products endpoints. + Corresponds to /futures/v1/products endpoints. """ product_code: Optional[str] = None @@ -138,7 +138,7 @@ def from_dict(d): class FuturesQuote: """ Represents a futures NBBO quote within a given time range. - Corresponds to /futures/vX/quotes/{ticker} + Corresponds to /futures/v1/quotes/{ticker} """ ticker: Optional[str] = None @@ -150,6 +150,7 @@ class FuturesQuote: bid_price: Optional[float] = None bid_size: Optional[float] = None bid_timestamp: Optional[int] = None + channel: Optional[int] = None report_sequence: Optional[int] = None sequence_number: Optional[int] = None @@ -165,6 +166,7 @@ def from_dict(d): bid_price=d.get("bid_price"), bid_size=d.get("bid_size"), bid_timestamp=d.get("bid_timestamp"), + channel=d.get("channel"), report_sequence=d.get("report_sequence"), sequence_number=d.get("sequence_number"), ) @@ -174,12 +176,13 @@ def from_dict(d): class FuturesTrade: """ Represents a futures trade within a given time range. - Corresponds to /futures/vX/trades/{ticker} + Corresponds to /futures/v1/trades/{ticker} """ ticker: Optional[str] = None timestamp: Optional[int] = None session_end_date: Optional[str] = None + channel: Optional[int] = None price: Optional[float] = None size: Optional[float] = None report_sequence: Optional[int] = None @@ -191,6 +194,7 @@ def from_dict(d): ticker=d.get("ticker"), timestamp=d.get("timestamp"), session_end_date=d.get("session_end_date"), + channel=d.get("channel"), price=d.get("price"), size=d.get("size"), report_sequence=d.get("report_sequence"), @@ -202,7 +206,7 @@ def from_dict(d): class FuturesSchedule: """ Represents a single schedule event for a given session_end_date and product. - Corresponds to /futures/vX/schedules + Corresponds to /futures/v1/schedules """ event: Optional[str] = None @@ -248,13 +252,17 @@ def from_dict(d): @modelclass class FuturesSnapshotDetails: open_interest: Optional[int] = None - settlement_date: Optional[int] = None + settlement_date: Optional[Union[str, int]] = None + ticker: Optional[str] = None + product_code: Optional[str] = None @staticmethod def from_dict(d): return FuturesSnapshotDetails( open_interest=d.get("open_interest"), settlement_date=d.get("settlement_date"), + ticker=d.get("ticker"), + product_code=d.get("product_code"), ) @@ -354,6 +362,7 @@ def from_dict(d): class FuturesSnapshot: ticker: Optional[str] = None product_code: Optional[str] = None + details: Optional[FuturesSnapshotDetails] = None last_minute: Optional[FuturesSnapshotMinute] = None last_quote: Optional[FuturesSnapshotQuote] = None @@ -362,14 +371,11 @@ class FuturesSnapshot: @staticmethod def from_dict(d): + details_dict = d.get("details") or {} return FuturesSnapshot( - ticker=d.get("ticker"), - product_code=d.get("product_code"), - details=( - FuturesSnapshotDetails.from_dict(d.get("details", {})) - if d.get("details") - else None - ), + ticker=d.get("ticker") or details_dict.get("ticker"), + product_code=d.get("product_code") or details_dict.get("product_code"), + details=FuturesSnapshotDetails.from_dict(details_dict), last_minute=( FuturesSnapshotMinute.from_dict(d.get("last_minute", {})) if d.get("last_minute") @@ -397,7 +403,7 @@ def from_dict(d): class FuturesExchange: """ Represents a futures exchange or trading venue. - Corresponds to /futures/vX/exchanges endpoint. + Corresponds to /futures/v1/exchanges endpoint. """ acronym: Optional[str] = None diff --git a/massive/rest/reference.py b/massive/rest/reference.py index 06ad037f..b6886c55 100644 --- a/massive/rest/reference.py +++ b/massive/rest/reference.py @@ -28,8 +28,13 @@ ShortVolume, RiskFactor, RiskFactorTaxonomy, + Disclosure, + DisclosureTaxonomy, FilingSection, Filing8K, + Filing13F, + FilingForm3, + FilingForm4, FilingIndex, ) from urllib3 import HTTPResponse @@ -859,6 +864,88 @@ def list_stocks_taxonomies_risk_factors( options=options, ) + def list_stocks_filings_8k_disclosures( + self, + cik: Optional[str] = None, + cik_any_of: Optional[str] = None, + tickers: Optional[str] = None, + tickers_all_of: Optional[str] = None, + tickers_any_of: Optional[str] = None, + filing_date: Optional[Union[str, date]] = None, + filing_date_any_of: Optional[str] = None, + filing_date_gt: Optional[Union[str, date]] = None, + filing_date_gte: Optional[Union[str, date]] = None, + filing_date_lt: Optional[Union[str, date]] = None, + filing_date_lte: Optional[Union[str, date]] = None, + tertiary_category: Optional[str] = None, + limit: Optional[int] = 100, + sort: Optional[Union[str, Sort]] = "filing_date.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[Disclosure], HTTPResponse]: + """ + SEC 8-K filing disclosure categorization. A single 8-K filing can produce + multiple rows when it covers multiple disclosure types, each classified into + primary, secondary, and tertiary categories with a supporting text excerpt. + The full classification list is available at /stocks/taxonomies/vX/disclosures. + """ + url = "/stocks/filings/8-K/vX/disclosures" + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_8k_disclosures, locals()), + result_key="results", + deserializer=Disclosure.from_dict, + raw=raw, + options=options, + ) + + def list_stocks_taxonomies_disclosures( + self, + taxonomy: Optional[str] = None, + taxonomy_any_of: Optional[str] = None, + taxonomy_gt: Optional[str] = None, + taxonomy_gte: Optional[str] = None, + taxonomy_lt: Optional[str] = None, + taxonomy_lte: Optional[str] = None, + primary_category: Optional[str] = None, + primary_category_any_of: Optional[str] = None, + primary_category_gt: Optional[str] = None, + primary_category_gte: Optional[str] = None, + primary_category_lt: Optional[str] = None, + primary_category_lte: Optional[str] = None, + secondary_category: Optional[str] = None, + secondary_category_any_of: Optional[str] = None, + secondary_category_gt: Optional[str] = None, + secondary_category_gte: Optional[str] = None, + secondary_category_lt: Optional[str] = None, + secondary_category_lte: Optional[str] = None, + tertiary_category: Optional[str] = None, + tertiary_category_any_of: Optional[str] = None, + tertiary_category_gt: Optional[str] = None, + tertiary_category_gte: Optional[str] = None, + tertiary_category_lt: Optional[str] = None, + tertiary_category_lte: Optional[str] = None, + limit: Optional[int] = 200, + sort: Optional[Union[str, Sort]] = "taxonomy.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[DisclosureTaxonomy], HTTPResponse]: + """ + The complete list of 8-K disclosure classifications used in the 8-K + disclosures endpoint. + """ + url = "/stocks/taxonomies/vX/disclosures" + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_taxonomies_disclosures, locals()), + result_key="results", + deserializer=DisclosureTaxonomy.from_dict, + raw=raw, + options=options, + ) + def list_stocks_filings_10k_sections( self, cik: Optional[str] = None, @@ -991,3 +1078,131 @@ def list_stocks_filings_index( raw=raw, options=options, ) + + def list_stocks_filings_13f( + self, + filer_cik: Optional[str] = None, + filer_cik_any_of: Optional[str] = None, + filing_date: Optional[Union[str, date]] = None, + filing_date_gt: Optional[Union[str, date]] = None, + filing_date_gte: Optional[Union[str, date]] = None, + filing_date_lt: Optional[Union[str, date]] = None, + filing_date_lte: Optional[Union[str, date]] = None, + limit: Optional[int] = 100, + sort: Optional[Union[str, Sort]] = "filing_date.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[Filing13F], HTTPResponse]: + """ + SEC Form 13F filings data showing institutional investment manager holdings. + Form 13F is required to be filed quarterly by institutional investment managers + with at least $100 million in qualifying assets under management. + """ + url = "/stocks/filings/vX/13-F" + + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_13f, locals()), + result_key="results", + deserializer=Filing13F.from_dict, + raw=raw, + options=options, + ) + + def list_stocks_filings_form_3( + self, + issuer_cik: Optional[str] = None, + issuer_cik_any_of: Optional[str] = None, + owner_cik: Optional[str] = None, + owner_cik_any_of: Optional[str] = None, + tickers: Optional[str] = None, + tickers_all_of: Optional[str] = None, + tickers_any_of: Optional[str] = None, + form_type: Optional[str] = None, + filing_date: Optional[Union[str, date]] = None, + filing_date_gt: Optional[Union[str, date]] = None, + filing_date_gte: Optional[Union[str, date]] = None, + filing_date_lt: Optional[Union[str, date]] = None, + filing_date_lte: Optional[Union[str, date]] = None, + max_ticker: Optional[str] = None, + max_ticker_any_of: Optional[str] = None, + max_ticker_gt: Optional[str] = None, + max_ticker_gte: Optional[str] = None, + max_ticker_lt: Optional[str] = None, + max_ticker_lte: Optional[str] = None, + min_ticker: Optional[str] = None, + min_ticker_any_of: Optional[str] = None, + min_ticker_gt: Optional[str] = None, + min_ticker_gte: Optional[str] = None, + min_ticker_lt: Optional[str] = None, + min_ticker_lte: Optional[str] = None, + limit: Optional[int] = 100, + sort: Optional[Union[str, Sort]] = "filing_date.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[FilingForm3], HTTPResponse]: + """ + SEC Form 3 filings reporting initial statements of beneficial ownership of securities. + Filed by corporate insiders (directors, officers, and 10%+ shareholders) when they first acquire a position. + """ + url = "/stocks/filings/vX/form-3" + + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_form_3, locals()), + result_key="results", + deserializer=FilingForm3.from_dict, + raw=raw, + options=options, + ) + + def list_stocks_filings_form_4( + self, + issuer_cik: Optional[str] = None, + issuer_cik_any_of: Optional[str] = None, + owner_cik: Optional[str] = None, + owner_cik_any_of: Optional[str] = None, + tickers: Optional[str] = None, + tickers_all_of: Optional[str] = None, + tickers_any_of: Optional[str] = None, + form_type: Optional[str] = None, + transaction_code: Optional[str] = None, + filing_date: Optional[Union[str, date]] = None, + filing_date_gt: Optional[Union[str, date]] = None, + filing_date_gte: Optional[Union[str, date]] = None, + filing_date_lt: Optional[Union[str, date]] = None, + filing_date_lte: Optional[Union[str, date]] = None, + max_ticker: Optional[str] = None, + max_ticker_any_of: Optional[str] = None, + max_ticker_gt: Optional[str] = None, + max_ticker_gte: Optional[str] = None, + max_ticker_lt: Optional[str] = None, + max_ticker_lte: Optional[str] = None, + min_ticker: Optional[str] = None, + min_ticker_any_of: Optional[str] = None, + min_ticker_gt: Optional[str] = None, + min_ticker_gte: Optional[str] = None, + min_ticker_lt: Optional[str] = None, + min_ticker_lte: Optional[str] = None, + limit: Optional[int] = 100, + sort: Optional[Union[str, Sort]] = "filing_date.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[FilingForm4], HTTPResponse]: + """ + SEC Form 4 filings reporting changes in beneficial ownership of securities. + Filed by corporate insiders (directors, officers, and 10%+ shareholders) within two business days of a transaction. + """ + url = "/stocks/filings/vX/form-4" + + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_form_4, locals()), + result_key="results", + deserializer=FilingForm4.from_dict, + raw=raw, + options=options, + ) diff --git a/poetry.lock b/poetry.lock index 96b782f7..8cb653a5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -644,14 +644,14 @@ xmltodict = ">=0.11.0" [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras]