Skip to content

Commit 8c2bd1d

Browse files
[bot] Add missing endpoints and sync with OpenAPI spec (#1056)
Adds endpoints documented on massive.com that the client was missing, and syncs existing methods with the OpenAPI spec. **New endpoints** - `list_options_open_interest` — `/options/v1/open-interest` - `list_options_unusual_trades` — `/options/v1/unusual-trades` - `list_stocks_insider_activity` — `/stocks/insiders/vX/activity` - `list_stocks_insider_holdings` — `/stocks/insiders/vX/holdings` **Synced with the OpenAPI spec** - `*_all_of` filters are now sent as `.all_of` - Forex snapshot `last_quote` ask/bid prices are now populated - Added response fields the models were dropping - Added missing query params (additive, no breaking changes) Tested against the live API, including pagination.
1 parent c791110 commit 8c2bd1d

16 files changed

Lines changed: 351 additions & 11 deletions

‎massive/rest/base.py‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,16 +208,18 @@ def _get_params(
208208
elif isinstance(val, datetime):
209209
val = int(val.timestamp() * self.time_mult(datetime_res))
210210
if val is not None:
211-
for ext in ["lt", "lte", "gt", "gte", "any_of"]:
211+
for ext in ["lt", "lte", "gt", "gte", "any_of", "all_of"]:
212212
if argname.endswith(f"_{ext}"):
213213
# lop off ext, then rebuild argname with ext,
214214
# using ., and not _ (removesuffix would work)
215215
# but that is python 3.9+
216216
argname = argname[: -len(f"_{ext}")] + f".{ext}"
217-
# any_of accepts a comma-separated str (per the spec) or a
218-
# list of values; only join the latter, since joining a str
219-
# would split it into single characters
220-
if argname.endswith("any_of") and not isinstance(val, str):
217+
# any_of/all_of accept a comma-separated str (per the spec)
218+
# or a list of values; only join the latter, since joining
219+
# a str would split it into single characters
220+
if argname.endswith(("any_of", "all_of")) and not isinstance(
221+
val, str
222+
):
221223
val = ",".join(val)
222224
params[argname] = val
223225

‎massive/rest/benzinga.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ def list_benzinga_consensus_ratings(
139139
date_lt: Optional[Union[str, date]] = None,
140140
date_lte: Optional[Union[str, date]] = None,
141141
limit: Optional[int] = None,
142+
date_any_of: Optional[Union[str, List[str]]] = None,
142143
params: Optional[Dict[str, Any]] = None,
143144
raw: bool = False,
144145
options: Optional[RequestOptionBuilder] = None,

‎massive/rest/futures.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ def list_futures_contracts(
9191
last_trade_date_lte: Optional[str] = None,
9292
limit: Optional[int] = None,
9393
sort: Optional[str] = None,
94+
provider_id: Optional[str] = None,
95+
provider_product_id: Optional[str] = None,
9496
params: Optional[Dict[str, Any]] = None,
9597
raw: bool = False,
9698
options: Optional[RequestOptionBuilder] = None,
@@ -146,6 +148,7 @@ def list_futures_products(
146148
type_any_of: Optional[str] = None,
147149
limit: Optional[int] = None,
148150
sort: Optional[str] = None,
151+
provider_id: Optional[str] = None,
149152
params: Optional[Dict[str, Any]] = None,
150153
raw: bool = False,
151154
options: Optional[RequestOptionBuilder] = None,

‎massive/rest/models/aggs.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class PreviousCloseAgg:
107107
timestamp: Optional[float] = None
108108
volume: Optional[float] = None
109109
vwap: Optional[float] = None
110+
transactions: Optional[int] = None
110111

111112
@staticmethod
112113
def from_dict(d):
@@ -119,4 +120,5 @@ def from_dict(d):
119120
d.get("t", None),
120121
d.get("v", None),
121122
d.get("vw", None),
123+
d.get("n", None),
122124
)

‎massive/rest/models/contracts.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,30 @@ def from_dict(d):
5151
ticker=d.get("ticker", None),
5252
underlying_ticker=d.get("underlying_ticker", None),
5353
)
54+
55+
56+
@modelclass
57+
class OptionsOpenInterest:
58+
"Daily open interest for a single options contract."
59+
60+
ticker: Optional[str] = None
61+
underlying_ticker: Optional[str] = None
62+
root: Optional[str] = None
63+
contract_type: Optional[str] = None
64+
expiration_date: Optional[str] = None
65+
strike_price: Optional[float] = None
66+
date: Optional[str] = None
67+
open_interest: Optional[int] = None
68+
69+
@staticmethod
70+
def from_dict(d):
71+
return OptionsOpenInterest(
72+
ticker=d.get("ticker"),
73+
underlying_ticker=d.get("underlying_ticker"),
74+
root=d.get("root"),
75+
contract_type=d.get("contract_type"),
76+
expiration_date=d.get("expiration_date"),
77+
strike_price=d.get("strike_price"),
78+
date=d.get("date"),
79+
open_interest=d.get("open_interest"),
80+
)

‎massive/rest/models/etf_global.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class EtfGlobalConstituent:
8484
asset_class: Optional[str] = None
8585
composite_ticker: Optional[str] = None
8686
constituent_name: Optional[str] = None
87+
constituent_rank: Optional[int] = None
8788
constituent_ticker: Optional[str] = None
8889
country_of_exchange: Optional[str] = None
8990
currency_traded: Optional[str] = None
@@ -105,6 +106,7 @@ def from_dict(d):
105106
asset_class=d.get("asset_class"),
106107
composite_ticker=d.get("composite_ticker"),
107108
constituent_name=d.get("constituent_name"),
109+
constituent_rank=d.get("constituent_rank"),
108110
constituent_ticker=d.get("constituent_ticker"),
109111
country_of_exchange=d.get("country_of_exchange"),
110112
currency_traded=d.get("currency_traded"),

‎massive/rest/models/financials.py‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,3 +1306,91 @@ def from_dict(d):
13061306
issuer_name=d.get("issuer_name"),
13071307
ticker=d.get("ticker"),
13081308
)
1309+
1310+
1311+
@modelclass
1312+
class InsiderActivity:
1313+
"Daily insider trading activity for an issuer, aggregated across all of its insiders' open-market purchases and sales."
1314+
1315+
issuer_cik: Optional[str] = None
1316+
issuer_name: Optional[str] = None
1317+
tickers: Optional[List[str]] = None
1318+
transaction_date: Optional[str] = None
1319+
last_filing_date: Optional[str] = None
1320+
purchase_count: Optional[int] = None
1321+
purchaser_count: Optional[int] = None
1322+
purchase_shares: Optional[float] = None
1323+
purchase_value: Optional[float] = None
1324+
sale_count: Optional[int] = None
1325+
seller_count: Optional[int] = None
1326+
sale_shares: Optional[float] = None
1327+
sale_value: Optional[float] = None
1328+
net_shares: Optional[float] = None
1329+
net_value: Optional[float] = None
1330+
planned_purchase_value: Optional[float] = None
1331+
planned_sale_value: Optional[float] = None
1332+
1333+
@staticmethod
1334+
def from_dict(d):
1335+
return InsiderActivity(
1336+
issuer_cik=d.get("issuer_cik"),
1337+
issuer_name=d.get("issuer_name"),
1338+
tickers=d.get("tickers"),
1339+
transaction_date=d.get("transaction_date"),
1340+
last_filing_date=d.get("last_filing_date"),
1341+
purchase_count=d.get("purchase_count"),
1342+
purchaser_count=d.get("purchaser_count"),
1343+
purchase_shares=d.get("purchase_shares"),
1344+
purchase_value=d.get("purchase_value"),
1345+
sale_count=d.get("sale_count"),
1346+
seller_count=d.get("seller_count"),
1347+
sale_shares=d.get("sale_shares"),
1348+
sale_value=d.get("sale_value"),
1349+
net_shares=d.get("net_shares"),
1350+
net_value=d.get("net_value"),
1351+
planned_purchase_value=d.get("planned_purchase_value"),
1352+
planned_sale_value=d.get("planned_sale_value"),
1353+
)
1354+
1355+
1356+
@modelclass
1357+
class InsiderHolding:
1358+
"An insider of an issuer (one reporting owner per issuer) with their current reported holdings."
1359+
1360+
issuer_cik: Optional[str] = None
1361+
issuer_name: Optional[str] = None
1362+
tickers: Optional[List[str]] = None
1363+
owner_cik: Optional[str] = None
1364+
owner_name: Optional[str] = None
1365+
officer_title: Optional[str] = None
1366+
roles: Optional[List[str]] = None
1367+
shares_owned: Optional[float] = None
1368+
shares_owned_direct: Optional[float] = None
1369+
shares_owned_indirect: Optional[float] = None
1370+
ownership_pct: Optional[float] = None
1371+
position_count: Optional[int] = None
1372+
filing_count: Optional[int] = None
1373+
first_filing_date: Optional[str] = None
1374+
last_filing_date: Optional[str] = None
1375+
oldest_position_date: Optional[str] = None
1376+
1377+
@staticmethod
1378+
def from_dict(d):
1379+
return InsiderHolding(
1380+
issuer_cik=d.get("issuer_cik"),
1381+
issuer_name=d.get("issuer_name"),
1382+
tickers=d.get("tickers"),
1383+
owner_cik=d.get("owner_cik"),
1384+
owner_name=d.get("owner_name"),
1385+
officer_title=d.get("officer_title"),
1386+
roles=d.get("roles"),
1387+
shares_owned=d.get("shares_owned"),
1388+
shares_owned_direct=d.get("shares_owned_direct"),
1389+
shares_owned_indirect=d.get("shares_owned_indirect"),
1390+
ownership_pct=d.get("ownership_pct"),
1391+
position_count=d.get("position_count"),
1392+
filing_count=d.get("filing_count"),
1393+
first_filing_date=d.get("first_filing_date"),
1394+
last_filing_date=d.get("last_filing_date"),
1395+
oldest_position_date=d.get("oldest_position_date"),
1396+
)

‎massive/rest/models/quotes.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ def from_dict(d):
5252
sequence_number=d.get("q", None),
5353
sip_timestamp=d.get("t", None),
5454
participant_timestamp=d.get("y", None),
55-
ask_price=d.get("P", None),
55+
# stocks send P/p; forex snapshot quotes send a/b
56+
ask_price=d.get("P", d.get("a")),
5657
ask_size=d.get("S", None),
5758
ask_exchange=d.get("X", None),
5859
conditions=d.get("c", None),
5960
indicators=d.get("i", None),
60-
bid_price=d.get("p", None),
61+
bid_price=d.get("p", d.get("b")),
6162
bid_size=d.get("s", None),
6263
bid_exchange=d.get("x", None),
6364
tape=d.get("z", None),
@@ -102,13 +103,15 @@ class RealTimeCurrencyConversion:
102103
initial_amount: Optional[float] = None
103104
last: Optional[ForexQuote] = None
104105
to: Optional[str] = None
106+
symbol: Optional[str] = None
105107

106108
@staticmethod
107109
def from_dict(d):
108110
return RealTimeCurrencyConversion(
109111
converted=d.get("converted", None),
110-
from_=d.get("from_", None),
112+
from_=d.get("from", d.get("from_")),
111113
initial_amount=d.get("initialAmount", None),
112114
last=None if "last" not in d else ForexQuote.from_dict(d["last"]),
113115
to=d.get("to", None),
116+
symbol=d.get("symbol"),
114117
)

‎massive/rest/models/snapshot.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class LastQuoteOptionContractSnapshot:
170170
last_updated: Optional[int] = None
171171
midpoint: Optional[float] = None
172172
timeframe: Optional[str] = None
173+
ask_exchange: Optional[int] = None
174+
bid_exchange: Optional[int] = None
173175

174176
@staticmethod
175177
def from_dict(d):

‎massive/rest/models/tickers.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class TickerDetails:
112112
name: Optional[str] = None
113113
phone_number: Optional[str] = None
114114
primary_exchange: Optional[str] = None
115+
round_lot: Optional[int] = None
115116
share_class_figi: Optional[str] = None
116117
share_class_shares_outstanding: Optional[int] = None
117118
sic_code: Optional[str] = None
@@ -147,6 +148,7 @@ def from_dict(d):
147148
name=d.get("name", None),
148149
phone_number=d.get("phone_number", None),
149150
primary_exchange=d.get("primary_exchange", None),
151+
round_lot=d.get("round_lot", None),
150152
share_class_figi=d.get("share_class_figi", None),
151153
share_class_shares_outstanding=d.get(
152154
"share_class_shares_outstanding", None

0 commit comments

Comments
 (0)