Skip to content

Commit 576ee2d

Browse files
committed
fix: update massive-sdk skill
1 parent 8db50f8 commit 576ee2d

1 file changed

Lines changed: 208 additions & 80 deletions

File tree

skills/massive-sdk/SKILL.md

Lines changed: 208 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,116 @@
11
---
22
name: massive-sdk
33
description: Fetch financial market data (stocks, options, crypto, forex, economy, fundamentals) using the Massive Python SDK. Use when agents need stock prices, aggregates, snapshots, financials, indicators, or any market data. Supports all Massive.com/Polygon.io endpoints.
4-
allowed-tools: Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py *), Bash(python3 -c *)
4+
allowed-tools: Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py *), Bash(python3 -c *), Bash(python3 - *), Bash(python3 *)
55
---
66

77
# Massive Python SDK (Financial Market Data)
88

99
The `massive` Python package provides typed access to the full Massive.com (formerly Polygon.io) REST API. The API key is proxied through the backend -- never use a raw API key.
1010

11-
## Authentication
11+
## Sandbox Environment
12+
13+
- **Python 3.12** with full standard library (including `math`, `statistics`, `datetime`, `json`, `csv`, `collections`, `bisect`, `itertools`)
14+
- **`uv` package installer** is available -- run `uv pip install pandas numpy` if you need data analysis libraries (they are NOT pre-installed)
15+
- **No pandas/numpy/scipy/matplotlib by default** -- use stdlib for calculations, or install with `uv pip install <package>` first
16+
- **Bash** is available at `/usr/bin/bash`
17+
- **Node.js 24** and **pnpm** are available
1218

13-
The SDK connects through the backend proxy. Pass the existing `OPENROUTER_API_KEY` (proxy JWT token) as the api_key -- the backend validates it and injects the real Massive API key.
19+
## Authentication
1420

1521
```python
1622
import os
1723
from massive import RESTClient
1824

19-
# The proxy base URL is derived from OPENROUTER_BASE_URL
20-
# e.g., https://backend.example.com/api/llm-proxy -> https://backend.example.com/api/massive-proxy
2125
proxy_base = os.environ["OPENROUTER_BASE_URL"].replace("/api/llm-proxy", "/api/massive-proxy")
22-
token = os.environ["OPENROUTER_API_KEY"]
23-
24-
client = RESTClient(api_key=token, base=proxy_base)
26+
client = RESTClient(api_key=os.environ["OPENROUTER_API_KEY"], base=proxy_base)
2527
```
2628

27-
## Quick Start
29+
## Getting the Current/Latest Price (recommended approach)
30+
31+
The most reliable way to get a ticker's current price that works on the basic plan regardless of market hours:
2832

2933
```python
3034
import os
35+
from datetime import date, timedelta
3136
from massive import RESTClient
3237

3338
proxy_base = os.environ["OPENROUTER_BASE_URL"].replace("/api/llm-proxy", "/api/massive-proxy")
3439
client = RESTClient(api_key=os.environ["OPENROUTER_API_KEY"], base=proxy_base)
3540

36-
# Daily OHLCV bars
37-
aggs = client.get_aggs("AAPL", 1, "day", "2026-01-01", "2026-03-31")
38-
for a in aggs:
39-
print(a.open, a.high, a.low, a.close, a.volume, a.timestamp)
40-
41-
# Ticker details
42-
details = client.get_ticker_details("AAPL")
43-
print(details.name, details.market_cap, details.description)
41+
# 1. Previous close -- always available, returns a LIST (access [0])
42+
prev = client.get_previous_close_agg("AAPL")
43+
p = prev[0]
44+
print(f"Last close: ${p.close} O:{p.open} H:{p.high} L:{p.low} Vol:{p.volume} VWAP:{p.vwap}")
4445

45-
# Technical indicators (returns SingleIndicatorResults -- access .values)
46-
sma = client.get_sma("AAPL", timespan="day", window=20, limit=10)
47-
for v in sma.values:
48-
print(v.timestamp, v.value)
46+
# 2. Recent daily bars for context (returned in chronological order, oldest first)
47+
today = date.today()
48+
aggs = client.get_aggs("AAPL", 1, "day", str(today - timedelta(days=7)), str(today))
49+
for a in aggs:
50+
print(f" {a.timestamp}: O:{a.open} H:{a.high} L:{a.low} C:{a.close} V:{a.volume}")
4951

50-
# MACD (returns MACDIndicatorResults -- access .values)
51-
macd = client.get_macd("AAPL", timespan="day", limit=5)
52-
for v in macd.values:
53-
print(v.value, v.signal, v.histogram)
52+
# 3. Market status
53+
status = client.get_market_status()
54+
print(f"Market: {status.market}, Server time: {status.server_time}")
5455
```
5556

56-
## Discovering Methods and Models
57-
58-
Before writing code, use the discovery script to find the right method and understand its parameters and return types:
59-
60-
```bash
61-
# List ALL methods grouped by category
62-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py methods
63-
64-
# Get full signature + return model fields for a specific method
65-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py method get_aggs
66-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py method list_financials_income_statements
67-
68-
# Search by keyword (searches both methods and models)
69-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py search inflation
70-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py search snapshot
71-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py search earnings
57+
**Why this approach:** `get_last_trade`/`get_last_quote` require a higher-tier plan (NOT_AUTHORIZED on basic). `get_snapshot_ticker` returns None/zeroed fields when market is closed. `get_previous_close_agg` + `get_aggs` always work.
7258

73-
# Inspect a return model's fields and types
74-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py model aggs.Agg
75-
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py model snapshot.TickerSnapshot
76-
```
59+
## Method Index with Field Names
7760

78-
**Always run `discover.py method <name>` before using a method you haven't used before.** It shows you exact parameter names, types, which are required vs optional, and all fields on the return model.
79-
80-
## Method Index (compact reference)
61+
All field values are **floats** unless noted. Timestamps are **int** (Unix epoch milliseconds). Dates are **str** (ISO format "YYYY-MM-DD").
8162

8263
### Aggregates (OHLCV price data)
83-
- `get_aggs(ticker, multiplier, timespan, from_, to)` -> List[Agg] -- single ticker bars
64+
65+
- `get_aggs(ticker, multiplier, timespan, from_, to)` -> **List[Agg]** -- returns bars in **chronological order** (oldest first)
8466
- `list_aggs(ticker, multiplier, timespan, from_, to)` -> Iterator[Agg] -- auto-paginates
8567
- `get_grouped_daily_aggs(date)` -> List[GroupedDailyAgg] -- all tickers for one day
8668
- `get_daily_open_close_agg(ticker, date)` -> DailyOpenCloseAgg
87-
- `get_previous_close_agg(ticker)` -> PreviousCloseAgg
69+
- `get_previous_close_agg(ticker)` -> **List[PreviousCloseAgg]** -- returns a list, access `[0]` for the single result
70+
71+
**Agg fields:** `open`, `high`, `low`, `close`, `volume`, `vwap`, `timestamp` (int, epoch ms), `transactions` (int), `otc` (bool)
8872

8973
### Indicators
90-
- `get_sma(ticker)` -> SingleIndicatorResults -- access `.values` (list of IndicatorValue: timestamp, value)
74+
75+
- `get_sma(ticker)` -> SingleIndicatorResults -- access `.values` (list of IndicatorValue)
9176
- `get_ema(ticker)` -> SingleIndicatorResults
9277
- `get_rsi(ticker)` -> SingleIndicatorResults
93-
- `get_macd(ticker)` -> MACDIndicatorResults -- access `.values` (list: value, signal, histogram)
78+
- `get_macd(ticker)` -> MACDIndicatorResults -- access `.values` (list of MACDValue)
79+
80+
**IndicatorValue fields:** `timestamp`, `value`
81+
**MACDValue fields:** `value`, `signal`, `histogram`
9482

9583
### Snapshots
96-
- `get_snapshot_ticker(market_type, ticker)` -> TickerSnapshot (day, prev_day, last_trade, last_quote)
84+
85+
- `get_snapshot_ticker(market_type, ticker)` -> TickerSnapshot -- **WARNING:** `last_trade` and `last_quote` will be `None` and `day` fields zeroed when market is closed. Always null-check before accessing nested fields.
9786
- `get_snapshot_all(market_type)` -> List[TickerSnapshot]
9887
- `list_universal_snapshots()` -> Iterator[UniversalSnapshot]
9988
- `list_snapshot_options_chain(underlying_asset)` -> Iterator[OptionContractSnapshot]
10089

90+
**TickerSnapshot fields:** `ticker` (str), `day` (Agg), `prev_day` (Agg), `last_trade` (LastTrade or None), `last_quote` (LastQuote or None), `min` (MinuteSnapshot), `todays_change`, `todays_change_percent`, `updated` (int), `fair_market_value`
91+
10192
### Economy
102-
- `list_treasury_yields()` -> Iterator[TreasuryYield] -- yield_1_month through yield_30_year
103-
- `list_inflation()` -> Iterator[FedInflation] -- cpi, pce, year-over-year
93+
94+
- `list_treasury_yields()` -> Iterator[TreasuryYield]
95+
- `list_inflation()` -> Iterator[FedInflation]
10496
- `list_inflation_expectations()` -> Iterator[FedInflationExpectations]
10597
- `list_labor_market_indicators()` -> Iterator[FedLaborMarket]
10698

99+
All economy methods accept date filters: `date=`, `date_gt=`, `date_gte=`, `date_lt=`, `date_lte=`, `limit=`, `sort=`, `order=`.
100+
101+
**TreasuryYield fields:** `date` (str), `yield_1_month`, `yield_3_month`, `yield_6_month`, `yield_1_year`, `yield_2_year`, `yield_3_year`, `yield_5_year`, `yield_7_year`, `yield_10_year`, `yield_20_year`, `yield_30_year`
102+
103+
**FedInflation fields:** `date` (str), `cpi`, `cpi_core`, `cpi_year_over_year`, `pce`, `pce_core`, `pce_spending` -- numeric fields may be `None` for recent/incomplete periods
104+
107105
### Financials (requires higher-tier plan)
106+
108107
- `list_financials_income_statements()` -> Iterator[FinancialIncomeStatement]
109108
- `list_financials_balance_sheets()` -> Iterator[FinancialBalanceSheet]
110109
- `list_financials_cash_flow_statements()` -> Iterator[FinancialCashFlowStatement]
111110
- `list_financials_ratios()` -> Iterator[FinancialRatio]
112111

113112
### Reference
113+
114114
- `get_ticker_details()` -> TickerDetails -- name, market_cap, description, sic_code, etc.
115115
- `list_tickers()` -> Iterator[Ticker]
116116
- `list_ticker_news()` -> Iterator[TickerNews]
@@ -121,67 +121,195 @@ python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py model snapshot.TickerSnapshot
121121
- `list_short_interest()` -> List[ShortInterest]
122122

123123
### Options
124+
124125
- `get_options_contract(ticker)` -> OptionsContract
125-
- `list_options_contracts()` -> Iterator[OptionsContract]
126+
- `list_options_contracts()` -> Iterator[OptionsContract] -- filter with `underlying_ticker=`, `contract_type=` ("call"/"put"), `expiration_date=`, `expiration_date_lte=`, `strike_price=`, `limit=`
127+
128+
**OptionsContract fields:** `ticker` (str, OCC format), `underlying_ticker` (str), `contract_type` (str: "call" or "put"), `expiration_date` (str), `strike_price`, `shares_per_contract`, `exercise_style` (str), `primary_exchange` (str)
129+
130+
**OptionContractSnapshot fields** (from `list_snapshot_options_chain`): `break_even_price`, `implied_volatility` (float, decimal ratio e.g. 0.30 = 30%), `open_interest`, `fair_market_value`, plus nested objects:
131+
- `details`: `contract_type` (str: "call"/"put"), `expiration_date` (str), `strike_price`, `ticker` (str), `exercise_style` (str), `shares_per_contract`
132+
- `greeks`: `delta`, `gamma`, `theta`, `vega` -- **can be `None`** for illiquid/far-OTM contracts or when market is closed. Always check `if c.greeks is not None` before accessing.
133+
- `day`: `open`, `high`, `low`, `close`, `volume`, `vwap`, `change`, `change_percent`, `previous_close`, `last_updated` (int) -- **can be `None`** when market is closed. Always check `if c.day is not None` before accessing fields.
134+
- `last_quote`: bid/ask data (may be None when market closed)
135+
- `last_trade`: last trade data (may be None when market closed)
136+
- `underlying_asset`: underlying ticker info
126137

127-
### Trades & Quotes
128-
- `get_last_trade(ticker)` -> LastTrade
129-
- `list_trades(ticker)` -> Iterator[Trade]
130-
- `get_last_quote(ticker)` -> LastQuote
131-
- `list_quotes(ticker)` -> Iterator[Quote]
138+
### Trades & Quotes (requires higher-tier plan)
139+
140+
- `get_last_trade(ticker)` -> LastTrade -- **NOT_AUTHORIZED on basic plan**
141+
- `list_trades(ticker)` -> Iterator[Trade] -- **NOT_AUTHORIZED on basic plan**
142+
- `get_last_quote(ticker)` -> LastQuote -- **NOT_AUTHORIZED on basic plan**
143+
- `list_quotes(ticker)` -> Iterator[Quote] -- **NOT_AUTHORIZED on basic plan**
132144

133145
### Forex & Crypto
146+
134147
- `get_last_forex_quote(from_, to)` -> LastForexQuote
135148
- `get_real_time_currency_conversion(from_, to)` -> RealTimeCurrencyConversion
136149
- `get_last_crypto_trade(from_, to)` -> CryptoTrade
137150
- Crypto/forex aggregates: use `get_aggs("X:BTCUSD", ...)` or `get_aggs("C:EURUSD", ...)`
138151

139152
### Other
140-
- `get_market_status()` -> MarketStatus
153+
154+
- `get_market_status()` -> MarketStatus -- `market` (str: "open"/"closed"), `server_time` (str), `exchanges` (nested)
141155
- `get_market_holidays()` -> List[MarketHoliday]
142156
- `list_stocks_filings_index()` -> Iterator[FilingIndex] -- SEC filings
143-
- VX (experimental): `client.vx.list_ipos()`, `client.vx.list_stock_financials()`
144157

145-
## Key Patterns
158+
## Examples by Category
159+
160+
### Multi-ticker comparison (stdlib only)
161+
162+
```python
163+
import os, math, time
164+
from datetime import date, timedelta
165+
from massive import RESTClient
166+
167+
proxy_base = os.environ["OPENROUTER_BASE_URL"].replace("/api/llm-proxy", "/api/massive-proxy")
168+
client = RESTClient(api_key=os.environ["OPENROUTER_API_KEY"], base=proxy_base)
169+
170+
tickers = ["AAPL", "MSFT", "GOOGL", "AMZN"]
171+
today = date.today()
172+
start = str(today - timedelta(days=45)) # overshoot to get ~30 trading days
173+
174+
for ticker in tickers:
175+
aggs = client.get_aggs(ticker, 1, "day", start, str(today))
176+
time.sleep(0.5) # rate limit
177+
closes = [a.close for a in aggs][-31:]
178+
ret = (closes[-1] - closes[0]) / closes[0] * 100
179+
daily_rets = [(closes[i] - closes[i-1]) / closes[i-1] for i in range(1, len(closes))]
180+
mean_r = sum(daily_rets) / len(daily_rets)
181+
vol = math.sqrt(sum((r - mean_r)**2 for r in daily_rets) / (len(daily_rets) - 1)) * math.sqrt(252) * 100
182+
sharpe = (mean_r / (vol / 100 / math.sqrt(252))) * math.sqrt(252) if vol > 0 else 0
183+
print(f"{ticker}: Return={ret:+.2f}% AnnVol={vol:.1f}% Sharpe={sharpe:.2f}")
184+
```
185+
186+
### Treasury yields vs inflation (real yield analysis)
146187

147-
### Chaining data from multiple sources
148188
```python
149189
import os
150190
from massive import RESTClient
151191

152192
proxy_base = os.environ["OPENROUTER_BASE_URL"].replace("/api/llm-proxy", "/api/massive-proxy")
153193
client = RESTClient(api_key=os.environ["OPENROUTER_API_KEY"], base=proxy_base)
154194

155-
# Fetch two tickers and merge
195+
# Treasury yields are daily, inflation is monthly -- both have .date (str "YYYY-MM-DD")
196+
yields = list(client.list_treasury_yields(limit=30, sort="date", order="desc"))
197+
inflation = list(client.list_inflation(limit=12, sort="date", order="desc"))
198+
199+
# Latest inflation reading for real yield calc
200+
latest_cpi_yoy = inflation[0].cpi_year_over_year if inflation else 0
201+
202+
print(f"Latest CPI YoY: {latest_cpi_yoy:.2f}%")
203+
print(f"\n{'Date':<12} {'10Y Nominal':>12} {'Real Yield':>11}")
204+
for y in yields[:10]:
205+
real = y.yield_10_year - latest_cpi_yoy
206+
print(f"{y.date:<12} {y.yield_10_year:>11.3f}% {real:>10.3f}%")
207+
```
208+
209+
### Options chain analysis
210+
211+
```python
212+
import os
213+
from datetime import date, timedelta
214+
from massive import RESTClient
215+
216+
proxy_base = os.environ["OPENROUTER_BASE_URL"].replace("/api/llm-proxy", "/api/massive-proxy")
217+
client = RESTClient(api_key=os.environ["OPENROUTER_API_KEY"], base=proxy_base)
218+
219+
cutoff = str(date.today() + timedelta(days=30))
220+
chain = list(client.list_snapshot_options_chain("AAPL"))
221+
222+
puts, calls = 0, 0
223+
volume_by_strike = {}
224+
225+
for c in chain:
226+
# Use c.details for contract info, c.day for volume (may be None when market closed)
227+
if c.details.expiration_date > cutoff:
228+
continue
229+
if c.details.contract_type == "call":
230+
calls += 1
231+
elif c.details.contract_type == "put":
232+
puts += 1
233+
strike = c.details.strike_price
234+
vol = c.day.volume if c.day is not None else 0
235+
volume_by_strike[strike] = volume_by_strike.get(strike, 0) + (vol or 0)
236+
237+
print(f"Put/Call Ratio: {puts/calls:.2f} ({puts} puts / {calls} calls)")
238+
print("\nTop 10 strikes by volume:")
239+
for strike, vol in sorted(volume_by_strike.items(), key=lambda x: -x[1])[:10]:
240+
print(f" ${strike}: {vol:,.0f}")
241+
```
242+
243+
## Discovering Methods and Models
244+
245+
Use `discover.py` when you need details beyond what's documented above (e.g., less common models, exact parameter signatures):
246+
247+
```bash
248+
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py methods # list all methods
249+
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py method get_aggs # full signature + return model
250+
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py model aggs.Agg # model fields
251+
python3 ${CLAUDE_SKILL_DIR}/scripts/discover.py search snapshot # keyword search
252+
```
253+
254+
## Key Patterns
255+
256+
### Process data in code, return only summaries
257+
258+
Avoid printing raw API responses. Process and filter data in Python, then print only the final summary. This keeps model context small:
259+
260+
```python
261+
# BAD -- dumps raw data into context
262+
aggs = client.get_aggs("AAPL", 1, "day", "2026-01-01", "2026-03-31")
263+
print(aggs) # hundreds of lines
264+
265+
# GOOD -- process in code, print summary only
266+
aggs = client.get_aggs("AAPL", 1, "day", "2026-01-01", "2026-03-31")
267+
closes = [a.close for a in aggs]
268+
print(f"AAPL: {len(closes)} bars, range ${min(closes):.2f}-${max(closes):.2f}, latest ${closes[-1]:.2f}")
269+
```
270+
271+
### Chaining data from multiple sources
272+
273+
```python
274+
# Fetch two tickers and merge by timestamp
156275
aapl = client.get_aggs("AAPL", 1, "day", "2026-01-01", "2026-03-31")
157276
msft = client.get_aggs("MSFT", 1, "day", "2026-01-01", "2026-03-31")
158277

159-
df_a = [{"t": a.timestamp, "aapl": a.close} for a in aapl]
160-
df_m = [{"t": m.timestamp, "msft": m.close} for m in msft]
161-
# Merge and analyze as needed
278+
aapl_by_ts = {a.timestamp: a.close for a in aapl}
279+
msft_by_ts = {m.timestamp: m.close for m in msft}
280+
281+
common_ts = sorted(set(aapl_by_ts) & set(msft_by_ts))
282+
for ts in common_ts[-5:]:
283+
print(f"{ts}: AAPL=${aapl_by_ts[ts]:.2f} MSFT=${msft_by_ts[ts]:.2f}")
162284
```
163285

164286
### Filtering with optional params
165-
Most `list_*` methods support filter params like `ticker=`, `limit=`, `sort=`, date ranges (`timestamp_gt=`, etc.). Run `discover.py method <name>` to see available filters.
287+
288+
Most `list_*` methods support filter params: `ticker=`, `limit=`, `sort=`, `order=`, date ranges (`date_gt=`, `date_lte=`, etc.).
166289

167290
```python
168-
# Dividends for a specific ticker
169291
divs = list(client.list_dividends(ticker="AAPL", limit=4))
170-
171-
# News with date filter
172292
news = list(client.list_ticker_news(ticker="AAPL", limit=10))
173293
```
174294

175295
### Iterator vs List methods
296+
176297
- `list_*` methods return iterators that auto-paginate -- use with `list()` or `for` loops
177298
- `get_*` methods return a single object or a list (no pagination)
178-
- **Watch out for rate limits** when iterating large result sets -- add `limit=` param
299+
- **Always use `limit=`** to avoid unbounded pagination
179300

180301
## Critical Gotchas
181302

182-
1. **Indicator methods return wrapper objects, NOT iterables.** Access `.values` to get the list.
183-
2. **Parameter names use `tickers` (plural)** for financials methods, not `ticker`.
184-
3. **Rate limits:** add `time.sleep(0.5)` between rapid-fire calls, or use `limit=` to reduce pagination.
185-
4. **`get_grouped_daily_aggs` returns empty for non-trading days** (weekends/holidays) -- no error.
186-
5. **Crypto tickers** use `X:` prefix (e.g., `X:BTCUSD`), **forex** uses `C:` prefix (e.g., `C:EURUSD`).
187-
6. **Some endpoints require higher-tier plans** -- you'll get a `BadResponse` with `NOT_AUTHORIZED`.
303+
1. **`get_previous_close_agg` returns a list**, not a single object. Access `[0]` for the result.
304+
2. **`get_aggs` returns bars in chronological order** (oldest first). The last element is the most recent bar.
305+
3. **`get_snapshot_ticker` fields can be `None`/zeroed when market is closed.** `last_trade` and `last_quote` will be `None`, `day` OHLCV will be all zeros. Always null-check: `if snap.last_trade is not None:`.
306+
4. **`get_last_trade`, `get_last_quote`, `list_trades`, `list_quotes` require a higher-tier plan.** They throw `BadResponse` with `NOT_AUTHORIZED` on the basic plan. Use `get_previous_close_agg` + `get_aggs` instead.
307+
5. **Financials endpoints also require higher-tier plans** -- same `NOT_AUTHORIZED` error.
308+
6. **Indicator methods return wrapper objects, NOT iterables.** Access `.values` to get the list.
309+
7. **Parameter names use `tickers` (plural)** for financials methods, not `ticker`.
310+
8. **Rate limits:** add `time.sleep(0.5)` between rapid-fire calls, or use `limit=` to reduce pagination.
311+
9. **`get_grouped_daily_aggs` returns empty for non-trading days** (weekends/holidays) -- no error.
312+
10. **Crypto tickers** use `X:` prefix (e.g., `X:BTCUSD`), **forex** uses `C:` prefix (e.g., `C:EURUSD`).
313+
11. **All timestamps are Unix epoch milliseconds (int).** Convert with `datetime.fromtimestamp(ts / 1000)`.
314+
12. **Economy data frequencies differ:** treasury yields are daily, inflation is monthly. Align dates when combining.
315+
13. **Options `contract_type` values are lowercase strings:** `"call"` or `"put"`.

0 commit comments

Comments
 (0)