diff --git a/massive/rest/base.py b/massive/rest/base.py index 66c28924..3349d7ef 100644 --- a/massive/rest/base.py +++ b/massive/rest/base.py @@ -68,6 +68,9 @@ def __init__( backoff_factor=0.1, # [0.0s, 0.2s, 0.4s, 0.8s, 1.6s, ...] ) + # https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Timeout + self.timeout = urllib3.Timeout(connect=connect_timeout, read=read_timeout) + # https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html # https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool self.client = urllib3.PoolManager( @@ -76,10 +79,9 @@ def __init__( ca_certs=certifi.where(), cert_reqs="CERT_REQUIRED", retries=retry_strategy, # use the customized Retry instance + timeout=self.timeout, # set timeout for each request ) - self.timeout = urllib3.Timeout(connect=connect_timeout, read=read_timeout) - if verbose: logger.setLevel(logging.DEBUG) self.trace = trace 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..a57f69fb 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()), @@ -257,12 +257,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 +284,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 +313,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 +330,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 2e97eeba..feda1341 100644 --- a/massive/rest/models/financials.py +++ b/massive/rest/models/financials.py @@ -920,3 +920,332 @@ def from_dict(d): 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).""" + + cik: Optional[str] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + period_end: Optional[str] = None + section: Optional[str] = None + text: Optional[str] = None + ticker: Optional[str] = None + + @staticmethod + def from_dict(d): + return FilingSection( + cik=d.get("cik"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + period_end=d.get("period_end"), + section=d.get("section"), + text=d.get("text"), + ticker=d.get("ticker"), + ) + + +@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.""" + + accession_number: Optional[str] = None + cik: Optional[str] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + form_type: Optional[str] = None + items_text: Optional[str] = None + ticker: Optional[str] = None + + @staticmethod + def from_dict(d): + return Filing8K( + accession_number=d.get("accession_number"), + cik=d.get("cik"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + form_type=d.get("form_type"), + items_text=d.get("items_text"), + ticker=d.get("ticker"), + ) + + +@modelclass +class FilingIndex: + """Master index entry for any SEC filing (10-K, 8-K, 10-Q, etc.).""" + + accession_number: Optional[str] = None + cik: Optional[str] = None + filing_date: Optional[str] = None + filing_url: Optional[str] = None + form_type: Optional[str] = None + issuer_name: Optional[str] = None + ticker: Optional[str] = None + + @staticmethod + def from_dict(d): + return FilingIndex( + accession_number=d.get("accession_number"), + cik=d.get("cik"), + filing_date=d.get("filing_date"), + filing_url=d.get("filing_url"), + form_type=d.get("form_type"), + issuer_name=d.get("issuer_name"), + ticker=d.get("ticker"), + ) diff --git a/massive/rest/models/futures.py b/massive/rest/models/futures.py index d6502844..1016fa30 100644 --- a/massive/rest/models/futures.py +++ b/massive/rest/models/futures.py @@ -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 @@ -174,7 +174,7 @@ 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 @@ -202,7 +202,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 @@ -397,7 +397,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/models/snapshot.py b/massive/rest/models/snapshot.py index 3d38abe2..0f6dd160 100644 --- a/massive/rest/models/snapshot.py +++ b/massive/rest/models/snapshot.py @@ -7,7 +7,8 @@ @modelclass class MinuteSnapshot: - "Most recent minute bar." + """Most recent minute bar.""" + accumulated_volume: Optional[float] = None open: Optional[float] = None high: Optional[float] = None @@ -18,20 +19,24 @@ class MinuteSnapshot: otc: Optional[bool] = None timestamp: Optional[int] = None transactions: Optional[int] = None + fractional_volume: Optional[str] = None + fractional_accumulated_volume: Optional[str] = None @staticmethod def from_dict(d): return MinuteSnapshot( - d.get("av", None), - d.get("o", None), - d.get("h", None), - d.get("l", None), - d.get("c", None), - d.get("v", None), - d.get("vw", None), - d.get("otc", None), - d.get("t", None), - d.get("n", None), + accumulated_volume=d.get("av"), + open=d.get("o"), + high=d.get("h"), + low=d.get("l"), + close=d.get("c"), + volume=d.get("v"), + vwap=d.get("vw"), + otc=d.get("otc"), + timestamp=d.get("t"), + transactions=d.get("n"), + fractional_volume=d.get("dv"), + fractional_accumulated_volume=d.get("dav"), ) @@ -318,10 +323,32 @@ class UniversalSnapshotSession: low: Optional[float] = None previous_close: Optional[float] = None volume: Optional[float] = None + vwap: Optional[float] = None + last_updated: Optional[int] = None + fractional_volume: Optional[str] = None @staticmethod def from_dict(d): - return UniversalSnapshotSession(**d) + return UniversalSnapshotSession( + price=d.get("price"), + change=d.get("change"), + change_percent=d.get("change_percent"), + early_trading_change=d.get("early_trading_change"), + early_trading_change_percent=d.get("early_trading_change_percent"), + regular_trading_change=d.get("regular_trading_change"), + regular_trading_change_percent=d.get("regular_trading_change_percent"), + late_trading_change=d.get("late_trading_change"), + late_trading_change_percent=d.get("late_trading_change_percent"), + open=d.get("open"), + close=d.get("close"), + high=d.get("high"), + low=d.get("low"), + previous_close=d.get("previous_close"), + volume=d.get("volume"), + vwap=d.get("vwap"), + last_updated=d.get("last_updated"), + fractional_volume=d.get("decimal_volume"), + ) @modelclass @@ -330,8 +357,10 @@ class UniversalSnapshotLastQuote: ask: Optional[float] = None ask_size: Optional[float] = None + ask_exchange: Optional[int] = None bid: Optional[float] = None bid_size: Optional[float] = None + bid_exchange: Optional[int] = None midpoint: Optional[float] = None exchange: Optional[int] = None timeframe: Optional[str] = None @@ -339,7 +368,18 @@ class UniversalSnapshotLastQuote: @staticmethod def from_dict(d): - return UniversalSnapshotLastQuote(**d) + return UniversalSnapshotLastQuote( + ask=d.get("ask"), + ask_size=d.get("ask_size"), + ask_exchange=d.get("ask_exchange"), + bid=d.get("bid"), + bid_size=d.get("bid_size"), + bid_exchange=d.get("bid_exchange"), + midpoint=d.get("midpoint"), + exchange=d.get("exchange"), + timeframe=d.get("timeframe"), + last_updated=d.get("last_updated"), + ) @modelclass @@ -355,10 +395,51 @@ class UniversalSnapshotLastTrade: last_updated: Optional[int] = None participant_timestamp: Optional[int] = None sip_timestamp: Optional[int] = None + fractional_size: Optional[str] = None + + @staticmethod + def from_dict(d): + return UniversalSnapshotLastTrade( + id=d.get("id"), + price=d.get("price"), + size=d.get("size"), + exchange=d.get("exchange"), + conditions=d.get("conditions"), + timeframe=d.get("timeframe"), + last_updated=d.get("last_updated"), + participant_timestamp=d.get("participant_timestamp"), + sip_timestamp=d.get("sip_timestamp"), + fractional_size=d.get("decimal_size"), + ) + + +@modelclass +class UniversalSnapshotLastMinute: + """Contains the most recent minute-level aggregate for the asset.""" + + open: Optional[float] = None + close: Optional[float] = None + high: Optional[float] = None + low: Optional[float] = None + volume: Optional[float] = None + vwap: Optional[float] = None + transactions: Optional[int] = None + last_updated: Optional[int] = None + fractional_volume: Optional[str] = None @staticmethod def from_dict(d): - return UniversalSnapshotLastTrade(**d) + return UniversalSnapshotLastMinute( + open=d.get("open"), + close=d.get("close"), + high=d.get("high"), + low=d.get("low"), + volume=d.get("volume"), + vwap=d.get("vwap"), + transactions=d.get("transactions"), + last_updated=d.get("last_updated"), + fractional_volume=d.get("decimal_volume"), + ) @modelclass @@ -374,7 +455,14 @@ class UniversalSnapshotUnderlyingAsset: @staticmethod def from_dict(d): - return UniversalSnapshotUnderlyingAsset(**d) + return UniversalSnapshotUnderlyingAsset( + ticker=d.get("ticker"), + price=d.get("price"), + value=d.get("value"), + change_to_break_even=d.get("change_to_break_even"), + timeframe=d.get("timeframe"), + last_updated=d.get("last_updated"), + ) @modelclass @@ -389,18 +477,25 @@ class UniversalSnapshotDetails: @staticmethod def from_dict(d): - return UniversalSnapshotDetails(**d) + return UniversalSnapshotDetails( + contract_type=d.get("contract_type"), + exercise_style=d.get("exercise_style"), + expiration_date=d.get("expiration_date"), + shares_per_contract=d.get("shares_per_contract"), + strike_price=d.get("strike_price"), + ) @modelclass class UniversalSnapshot: - """Contains snapshot data for an asset.""" + """Contains snapshot data for an asset (stocks, options, indices, fx, crypto).""" ticker: Optional[str] = None type: Optional[str] = None session: Optional[UniversalSnapshotSession] = None last_quote: Optional[UniversalSnapshotLastQuote] = None last_trade: Optional[UniversalSnapshotLastTrade] = None + last_minute: Optional[UniversalSnapshotLastMinute] = None greeks: Optional[Greeks] = None underlying_asset: Optional[UniversalSnapshotUnderlyingAsset] = None details: Optional[UniversalSnapshotDetails] = None @@ -412,12 +507,15 @@ class UniversalSnapshot: fair_market_value: Optional[float] = None error: Optional[str] = None message: Optional[str] = None + value: Optional[float] = None + last_updated: Optional[int] = None + timeframe: Optional[str] = None @staticmethod def from_dict(d): return UniversalSnapshot( - ticker=d.get("ticker", None), - type=d.get("type", None), + ticker=d.get("ticker"), + type=d.get("type"), session=( None if "session" not in d @@ -433,7 +531,12 @@ def from_dict(d): if "last_trade" not in d else UniversalSnapshotLastTrade.from_dict(d["last_trade"]) ), - greeks=None if "greeks" not in d else Greeks.from_dict(d["greeks"]), + last_minute=( + None + if "last_minute" not in d + else UniversalSnapshotLastMinute.from_dict(d["last_minute"]) + ), + greeks=(None if "greeks" not in d else Greeks.from_dict(d["greeks"])), underlying_asset=( None if "underlying_asset" not in d @@ -444,12 +547,15 @@ def from_dict(d): if "details" not in d else UniversalSnapshotDetails.from_dict(d["details"]) ), - break_even_price=d.get("break_even_price", None), - implied_volatility=d.get("implied_volatility", None), - open_interest=d.get("open_interest", None), - market_status=d.get("market_status", None), - name=d.get("name", None), - fair_market_value=d.get("fmv", None), - error=d.get("error", None), - message=d.get("message", None), + break_even_price=d.get("break_even_price"), + implied_volatility=d.get("implied_volatility"), + open_interest=d.get("open_interest"), + market_status=d.get("market_status"), + name=d.get("name"), + fair_market_value=d.get("fmv"), + error=d.get("error"), + message=d.get("message"), + value=d.get("value"), + last_updated=d.get("last_updated"), + timeframe=d.get("timeframe"), ) diff --git a/massive/rest/models/trades.py b/massive/rest/models/trades.py index 25b9adc7..e298ed29 100644 --- a/massive/rest/models/trades.py +++ b/massive/rest/models/trades.py @@ -17,6 +17,7 @@ class Trade: tape: Optional[int] = None trf_id: Optional[int] = None trf_timestamp: Optional[int] = None + decimal_size: Optional[str] = None @staticmethod def from_dict(d): @@ -25,7 +26,8 @@ def from_dict(d): @modelclass class LastTrade: - "Contains data for the most recent trade for a given ticker symbol." + """Contains data for the most recent trade for a given ticker symbol.""" + ticker: Optional[str] = None trf_timestamp: Optional[int] = None sequence_number: Optional[float] = None @@ -39,23 +41,25 @@ class LastTrade: size: Optional[float] = None exchange: Optional[int] = None tape: Optional[int] = None + fractional_size: Optional[str] = None @staticmethod def from_dict(d): return LastTrade( - d.get("T", None), - d.get("f", None), - d.get("q", None), - d.get("t", None), - d.get("y", None), - d.get("c", None), - d.get("e", None), - d.get("i", None), - d.get("p", None), - d.get("r", None), - d.get("s", None), - d.get("x", None), - d.get("z", None), + ticker=d.get("T"), + trf_timestamp=d.get("f"), + sequence_number=d.get("q"), + sip_timestamp=d.get("t"), + participant_timestamp=d.get("y"), + conditions=d.get("c"), + correction=d.get("e"), + id=d.get("i"), + price=d.get("p"), + trf_id=d.get("r"), + size=d.get("s"), + exchange=d.get("x"), + tape=d.get("z"), + fractional_size=d.get("ds"), ) diff --git a/massive/rest/reference.py b/massive/rest/reference.py index 446d9f13..69a29479 100644 --- a/massive/rest/reference.py +++ b/massive/rest/reference.py @@ -28,6 +28,12 @@ ShortVolume, RiskFactor, RiskFactorTaxonomy, + FilingSection, + Filing8K, + Filing13F, + FilingForm3, + FilingForm4, + FilingIndex, ) from urllib3 import HTTPResponse from datetime import date @@ -793,21 +799,22 @@ def list_stocks_filings_risk_factors( cik_gte: Optional[str] = None, cik_lt: Optional[str] = None, cik_lte: Optional[str] = None, - limit: Optional[int] = None, - sort: Optional[Union[str, Sort]] = 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[RiskFactor], HTTPResponse]: """ - Endpoint: GET /stocks/filings/vX/risk-factors + Get categorized risk factors extracted from 10-K filings (with supporting_text). """ url = "/stocks/filings/vX/risk-factors" return self._paginate( path=url, params=self._get_params(self.list_stocks_filings_risk_factors, locals()), - raw=raw, + result_key="results", deserializer=RiskFactor.from_dict, + raw=raw, options=options, ) @@ -836,20 +843,282 @@ def list_stocks_taxonomies_risk_factors( tertiary_category_gte: Optional[str] = None, tertiary_category_lt: Optional[str] = None, tertiary_category_lte: Optional[str] = None, - limit: Optional[int] = None, - sort: Optional[Union[str, Sort]] = 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[RiskFactorTaxonomy], HTTPResponse]: """ - Endpoint: GET /stocks/taxonomies/vX/risk-factors + Get the taxonomy/categories used to classify risk factors (kept old name for backward compatibility). """ url = "/stocks/taxonomies/vX/risk-factors" return self._paginate( path=url, params=self._get_params(self.list_stocks_taxonomies_risk_factors, locals()), - raw=raw, + result_key="results", deserializer=RiskFactorTaxonomy.from_dict, + raw=raw, + options=options, + ) + + def list_stocks_filings_10k_sections( + self, + cik: Optional[str] = None, + cik_any_of: Optional[str] = None, + cik_gt: Optional[str] = None, + cik_gte: Optional[str] = None, + cik_lt: Optional[str] = None, + cik_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, + section: Optional[str] = None, + section_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, + period_end: Optional[Union[str, date]] = None, + period_end_gt: Optional[Union[str, date]] = None, + period_end_gte: Optional[Union[str, date]] = None, + period_end_lt: Optional[Union[str, date]] = None, + period_end_lte: Optional[Union[str, date]] = None, + limit: Optional[int] = 100, + sort: Optional[Union[str, Sort]] = "period_end.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[FilingSection], HTTPResponse]: + """ + Get raw text sections from 10-K/10-Q filings (business, risk_factors, etc.). + """ + url = "/stocks/filings/10-K/vX/sections" + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_10k_sections, locals()), + result_key="results", + deserializer=FilingSection.from_dict, + raw=raw, + options=options, + ) + + def list_stocks_filings_8k_text( + self, + cik: Optional[str] = None, + cik_any_of: Optional[str] = None, + cik_gt: Optional[str] = None, + cik_gte: Optional[str] = None, + cik_lt: Optional[str] = None, + cik_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, + form_type: Optional[str] = None, + form_type_any_of: Optional[str] = None, + form_type_gt: Optional[str] = None, + form_type_gte: Optional[str] = None, + form_type_lt: Optional[str] = None, + form_type_lte: 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[Filing8K], HTTPResponse]: + """ + Get parsed 8-K filings (earnings, acquisitions, executive changes, etc.). + """ + url = "/stocks/filings/8-K/vX/text" + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_8k_text, locals()), + result_key="results", + deserializer=Filing8K.from_dict, + raw=raw, + options=options, + ) + + def list_stocks_filings_index( + self, + cik: Optional[str] = None, + cik_any_of: Optional[str] = None, + cik_gt: Optional[str] = None, + cik_gte: Optional[str] = None, + cik_lt: Optional[str] = None, + cik_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, + form_type: Optional[str] = None, + form_type_any_of: Optional[str] = None, + form_type_gt: Optional[str] = None, + form_type_gte: Optional[str] = None, + form_type_lt: Optional[str] = None, + form_type_lte: 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] = 1000, + sort: Optional[Union[str, Sort]] = "filing_date.desc", + params: Optional[Dict[str, Any]] = None, + raw: bool = False, + options: Optional[RequestOptionBuilder] = None, + ) -> Union[Iterator[FilingIndex], HTTPResponse]: + """ + Get the master index of all SEC filings (10-K, 8-K, 10-Q, S-1, 4, etc.). + """ + url = "/stocks/filings/vX/index" + return self._paginate( + path=url, + params=self._get_params(self.list_stocks_filings_index, locals()), + result_key="results", + deserializer=FilingIndex.from_dict, + 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/massive/websocket/models/models.py b/massive/websocket/models/models.py index c0903530..cc3d3c16 100644 --- a/massive/websocket/models/models.py +++ b/massive/websocket/models/models.py @@ -22,6 +22,8 @@ class EquityAgg: start_timestamp: Optional[int] = None end_timestamp: Optional[int] = None otc: Optional[bool] = None + fractional_volume: Optional[str] = None + fractional_accumulated_volume: Optional[str] = None @staticmethod def from_dict(d): @@ -41,6 +43,8 @@ def from_dict(d): d.get("s", None), d.get("e", None), d.get("otc", None), + d.get("dv", None), + d.get("dav", None), ) @@ -86,6 +90,7 @@ class EquityTrade: tape: Optional[int] = None price: Optional[float] = None size: Optional[int] = None + fractional_shares: Optional[str] = None conditions: Optional[List[int]] = None timestamp: Optional[int] = None sequence_number: Optional[int] = None @@ -102,6 +107,7 @@ def from_dict(d): d.get("z", None), d.get("p", None), d.get("s", None), + d.get("ds", None), d.get("c", None), d.get("t", None), d.get("q", None), diff --git a/poetry.lock b/poetry.lock index 4494c57f..8cb653a5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -94,14 +94,14 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, - {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, + {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, + {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, ] [[package]] @@ -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] @@ -950,14 +950,14 @@ files = [ [[package]] name = "types-setuptools" -version = "80.9.0.20251223" +version = "81.0.0.20260209" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_setuptools-80.9.0.20251223-py3-none-any.whl", hash = "sha256:1b36db79d724c2287d83dc052cf887b47c0da6a2fff044378be0b019545f56e6"}, - {file = "types_setuptools-80.9.0.20251223.tar.gz", hash = "sha256:d3411059ae2f5f03985217d86ac6084efea2c9e9cacd5f0869ef950f308169b2"}, + {file = "types_setuptools-81.0.0.20260209-py3-none-any.whl", hash = "sha256:4facf71e3f953f8f5ac0020cd6c1b5e493aaff0183e85830bc34870b6abf8475"}, + {file = "types_setuptools-81.0.0.20260209.tar.gz", hash = "sha256:2c2eb64499b41b672c387f6f45678a28d20a143a81b45a5c77acbfd4da0df3e1"}, ] [[package]] @@ -1117,4 +1117,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "a107a664db6e290f90c70e0ef2f9bd71a553895783c6c62599c6ff9c0f4954d5" +content-hash = "859fb753010770932bb13116107b08bf52ef64a130954852216b405cc219fc21" diff --git a/pyproject.toml b/pyproject.toml index 7372e96b..058444af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ sphinx-rtd-theme = "^3.1.0" # keep this in sync with docs/requirements.txt for readthedocs.org sphinx-autodoc-typehints = "^2.3.0" types-certifi = "^2021.10.8" -types-setuptools = "^80.9.0" +types-setuptools = "^81.0.0" pook = "^2.1.4" orjson = "^3.11.5"