docs: update planning artifacts and project docs after phase 1
Updates Python version references to >=3.11, refreshes research docs with Tastytrade naming corrections, and syncs planning config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b80fa9ecd
commit
8b7e9fc649
|
|
@ -1,37 +1,39 @@
|
|||
{
|
||||
"model_profile": "quality",
|
||||
"commit_docs": true,
|
||||
"parallelization": true,
|
||||
"parallelization": false,
|
||||
"search_gitignored": false,
|
||||
"brave_search": false,
|
||||
"firecrawl": false,
|
||||
"exa_search": false,
|
||||
"git": {
|
||||
"branching_strategy": "none",
|
||||
"phase_branch_template": "gsd/phase-{phase}-{slug}",
|
||||
"milestone_branch_template": "gsd/{milestone}-{slug}",
|
||||
"quick_branch_template": null
|
||||
"branching_strategy": "none"
|
||||
},
|
||||
"workflow": {
|
||||
"research": true,
|
||||
"plan_check": true,
|
||||
"verifier": true,
|
||||
"nyquist_validation": true,
|
||||
"auto_advance": true,
|
||||
"node_repair": true,
|
||||
"auto_advance": false,
|
||||
"node_repair": false,
|
||||
"node_repair_budget": 2,
|
||||
"ui_phase": true,
|
||||
"ui_safety_gate": true,
|
||||
"text_mode": false,
|
||||
"research_before_questions": false,
|
||||
"discuss_mode": "discuss",
|
||||
"skip_discuss": false
|
||||
"skip_discuss": false,
|
||||
"_auto_chain_active": false
|
||||
},
|
||||
"hooks": {
|
||||
"context_warnings": true
|
||||
},
|
||||
"agent_skills": {},
|
||||
"resolve_model_ids": "omit",
|
||||
"mode": "yolo",
|
||||
"granularity": "fine"
|
||||
"mode": "safe",
|
||||
"granularity": "fine",
|
||||
"_planning_notes": {
|
||||
"mode": "Use 'safe' for normal work; only use aggressive/yolo-style settings in disposable sandboxes.",
|
||||
"parallelization_auto_advance_node_repair": "Disabled by default so production-adjacent runs do not auto-execute parallel waves or self-heal nodes without review."
|
||||
}
|
||||
}
|
||||
|
|
@ -26,10 +26,10 @@ must_haves:
|
|||
artifacts:
|
||||
- path: "tradingagents/dataflows/tradier_common.py"
|
||||
provides: "Auth, base URL, rate limit error, HTTP helper"
|
||||
exports: ["get_api_key", "get_base_url", "make_tradier_request", "TradierRateLimitError"]
|
||||
exports: ["get_api_key", "get_base_url", "make_tradier_request", "make_tradier_request_with_retry", "TradierRateLimitError"]
|
||||
- path: "tradingagents/dataflows/tradier.py"
|
||||
provides: "Tradier vendor module with options chain retrieval"
|
||||
exports: ["OptionsContract", "OptionsChain", "get_options_expirations", "get_options_chain"]
|
||||
exports: ["OptionsContract", "OptionsChain", "get_options_expirations", "get_options_chain", "get_options_chain_structured", "clear_options_cache"]
|
||||
key_links:
|
||||
- from: "tradingagents/dataflows/tradier.py"
|
||||
to: "tradingagents/dataflows/tradier_common.py"
|
||||
|
|
@ -113,7 +113,7 @@ Create `tradingagents/dataflows/tradier_common.py` with:
|
|||
- Build URL: `f"{get_base_url()}{path}"`
|
||||
- Headers: `{"Authorization": f"Bearer {get_api_key()}", "Accept": "application/json"}`
|
||||
- Execute `requests.get(url, headers=headers, params=params or {})`
|
||||
- Check `response.headers.get("X-Ratelimit-Available")` -- if not None and `int(remaining) <= 0`, raise `TradierRateLimitError` with message including `X-Ratelimit-Expiry` header value
|
||||
- Read `available = response.headers.get("X-Ratelimit-Available")`. If `available is not None`, try `int(available)`. On `ValueError` or `TypeError`, raise `TradierRateLimitError` including the **raw** `X-Ratelimit-Available` value and `response.headers.get("X-Ratelimit-Expiry")`. If conversion succeeds and the integer is `<= 0`, raise `TradierRateLimitError` including `X-Ratelimit-Expiry`
|
||||
- Check `response.status_code == 429` -- raise `TradierRateLimitError("Tradier rate limit exceeded (HTTP 429)")`
|
||||
- Call `response.raise_for_status()`
|
||||
- Return `response.json()`
|
||||
|
|
@ -231,7 +231,7 @@ Create `tradingagents/dataflows/tradier.py` with:
|
|||
- `clear_options_cache()` function to reset cache
|
||||
|
||||
7. **get_options_chain(symbol: str, min_dte: int = 0, max_dte: int = 50) -> str** function (per D-03: pre-fetch all expirations):
|
||||
- Check cache: `cache_key = f"{symbol.upper()}:{min_dte}:{max_dte}"`; if in `_options_cache`, return its `.to_dataframe().to_string()`
|
||||
- **Cache stores `OptionsChain` only** (`_options_cache: dict[str, OptionsChain]`). On hit for `cache_key`, return `_options_cache[cache_key].to_dataframe().to_string()` (do not pre-serialize strings in the cache)
|
||||
- Call `get_options_expirations(symbol, min_dte, max_dte)` to get qualifying dates
|
||||
- For each expiration, call `_fetch_chain_for_expiration(symbol, expiration)` and accumulate all OptionsContract instances
|
||||
- Build `OptionsChain(underlying=symbol.upper(), fetch_timestamp=datetime.now().isoformat(), expirations=expirations, contracts=all_contracts)`
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ files_modified:
|
|||
- tradingagents/default_config.py
|
||||
- tradingagents/agents/utils/options_tools.py
|
||||
- .env.example
|
||||
- tests/test_tradier.py
|
||||
- tests/unit/data/test_tradier.py
|
||||
- tests/conftest.py
|
||||
autonomous: true
|
||||
requirements:
|
||||
|
|
@ -35,7 +35,7 @@ must_haves:
|
|||
- path: "tradingagents/agents/utils/options_tools.py"
|
||||
provides: "LangChain @tool functions for options data"
|
||||
exports: ["get_options_chain", "get_options_expirations"]
|
||||
- path: "tests/test_tradier.py"
|
||||
- path: "tests/unit/data/test_tradier.py"
|
||||
provides: "Unit tests for all DATA requirements"
|
||||
min_lines: 100
|
||||
key_links:
|
||||
|
|
@ -57,7 +57,7 @@ must_haves:
|
|||
Integrate Tradier into the existing vendor routing system, create @tool functions for LLM agents, and write comprehensive unit tests covering all Phase 1 requirements.
|
||||
|
||||
Purpose: Without vendor registration, no agent can access Tradier data. Without tests, we cannot verify correctness. This plan wires the Tradier module into the system and proves it works.
|
||||
Output: Updated interface.py and default_config.py, new options_tools.py, comprehensive test suite in tests/test_tradier.py.
|
||||
Output: Updated interface.py and default_config.py, new options_tools.py, comprehensive test suite in tests/unit/data/test_tradier.py.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
|
|
@ -314,7 +314,7 @@ print('ALL CHECKS PASSED')
|
|||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Create comprehensive unit tests for all Phase 1 requirements</name>
|
||||
<files>tests/test_tradier.py, tests/conftest.py</files>
|
||||
<files>tests/unit/data/test_tradier.py, tests/conftest.py</files>
|
||||
<read_first>
|
||||
- tradingagents/dataflows/tradier.py
|
||||
- tradingagents/dataflows/tradier_common.py
|
||||
|
|
@ -331,7 +331,7 @@ print('ALL CHECKS PASSED')
|
|||
- TestDTEFilter (DATA-05): create OptionsChain with contracts at various DTEs, call filter_by_dte(30, 60), verify only contracts in range remain.
|
||||
- TestVendorRegistration (DATA-08): verify "tradier" in VENDOR_LIST, "options_chain" in TOOLS_CATEGORIES, get_options_chain and get_options_expirations in VENDOR_METHODS.
|
||||
- TestRateLimitDetection: mock 429 response, verify TradierRateLimitError raised. Mock response with X-Ratelimit-Available: 0, verify TradierRateLimitError raised.
|
||||
- TestSessionCache: call get_options_chain twice with mock, verify only one API call made.
|
||||
- TestSessionCache: patch the underlying HTTP/request mock; call `get_options_chain_structured("AAPL")` twice — assert `mock.call_count == 1` after the second call (second read is cache hit). Call `clear_options_cache()`, then `get_options_chain_structured("AAPL")` again — assert `mock.call_count == 2` (or increased by exactly one vs post-clear baseline).
|
||||
- TestSandboxURL: set TRADIER_SANDBOX=true, verify get_base_url returns sandbox URL.
|
||||
</behavior>
|
||||
<action>
|
||||
|
|
@ -344,17 +344,22 @@ uv add --dev pytest>=8.0
|
|||
|
||||
```python
|
||||
import pytest
|
||||
from datetime import date, timedelta
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
def _iso_days_out(days: int) -> str:
|
||||
"""Expiration string relative to today so DTE assertions never go stale."""
|
||||
return (date.today() + timedelta(days=days)).isoformat()
|
||||
|
||||
MOCK_EXPIRATIONS_RESPONSE = {
|
||||
"expirations": {
|
||||
"date": ["2026-04-03", "2026-04-10", "2026-04-17", "2026-04-24", "2026-05-15", "2026-07-17"]
|
||||
"date": [_iso_days_out(d) for d in (7, 14, 21, 28, 45)]
|
||||
}
|
||||
}
|
||||
|
||||
MOCK_SINGLE_EXPIRATION_RESPONSE = {
|
||||
"expirations": {
|
||||
"date": "2026-04-17"
|
||||
"date": _iso_days_out(21)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,7 +371,7 @@ MOCK_CHAIN_RESPONSE = {
|
|||
"underlying": "AAPL",
|
||||
"option_type": "call",
|
||||
"strike": 170.0,
|
||||
"expiration_date": "2026-04-17",
|
||||
"expiration_date": _iso_days_out(20),
|
||||
"bid": 5.10,
|
||||
"ask": 5.30,
|
||||
"last": 5.20,
|
||||
|
|
@ -391,7 +396,7 @@ MOCK_CHAIN_RESPONSE = {
|
|||
"underlying": "AAPL",
|
||||
"option_type": "put",
|
||||
"strike": 170.0,
|
||||
"expiration_date": "2026-04-17",
|
||||
"expiration_date": _iso_days_out(20),
|
||||
"bid": 3.40,
|
||||
"ask": 3.60,
|
||||
"last": 3.50,
|
||||
|
|
@ -416,7 +421,7 @@ MOCK_CHAIN_RESPONSE = {
|
|||
"underlying": "AAPL",
|
||||
"option_type": "call",
|
||||
"strike": 175.0,
|
||||
"expiration_date": "2026-04-17",
|
||||
"expiration_date": _iso_days_out(20),
|
||||
"bid": 2.80,
|
||||
"ask": 3.00,
|
||||
"last": 2.90,
|
||||
|
|
@ -448,7 +453,7 @@ MOCK_CHAIN_NO_GREEKS_RESPONSE = {
|
|||
"underlying": "AAPL",
|
||||
"option_type": "call",
|
||||
"strike": 170.0,
|
||||
"expiration_date": "2026-04-17",
|
||||
"expiration_date": _iso_days_out(20),
|
||||
"bid": 5.10,
|
||||
"ask": 5.30,
|
||||
"last": 5.20,
|
||||
|
|
@ -467,7 +472,7 @@ MOCK_SINGLE_CONTRACT_RESPONSE = {
|
|||
"underlying": "AAPL",
|
||||
"option_type": "call",
|
||||
"strike": 170.0,
|
||||
"expiration_date": "2026-04-17",
|
||||
"expiration_date": _iso_days_out(20),
|
||||
"bid": 5.10,
|
||||
"ask": 5.30,
|
||||
"last": 5.20,
|
||||
|
|
@ -484,7 +489,7 @@ MOCK_SINGLE_CONTRACT_RESPONSE = {
|
|||
}
|
||||
```
|
||||
|
||||
**C. Create `tests/test_tradier.py`** with test classes:
|
||||
**C. Create `tests/unit/data/test_tradier.py`** with test classes:
|
||||
|
||||
1. `TestGetExpirations` (DATA-02): patch `tradingagents.dataflows.tradier_common.make_tradier_request` to return `MOCK_EXPIRATIONS_RESPONSE`. Call `get_options_expirations("AAPL", 0, 50)`. Assert result is a list of strings. Assert all dates are within 0-50 DTE of today. Test with `MOCK_SINGLE_EXPIRATION_RESPONSE` to verify Pitfall 5 normalization.
|
||||
|
||||
|
|
@ -502,30 +507,46 @@ MOCK_SINGLE_CONTRACT_RESPONSE = {
|
|||
|
||||
8. `TestRateLimitDetection`: mock `requests.get` to return response with status 429. Verify `TradierRateLimitError` raised. Mock response with `X-Ratelimit-Available: 0` header. Verify `TradierRateLimitError` raised.
|
||||
|
||||
9. `TestSessionCache`: patch API, call `get_options_chain_structured("AAPL")` twice. Assert mock was called only the number of times for first call (not doubled). Call `clear_options_cache()` and verify next call triggers API again.
|
||||
9. `TestSessionCache`: as in **behavior** — after two `get_options_chain_structured("AAPL")` calls, assert underlying mock `call_count == 1`; after `clear_options_cache()` and a third `get_options_chain_structured("AAPL")`, assert `call_count == 2`.
|
||||
|
||||
10. `TestSandboxURL`: patch `os.environ` with `TRADIER_SANDBOX=true`. Assert `get_base_url()` returns `"https://sandbox.tradier.com"`. Unset it. Assert returns `"https://api.tradier.com"`.
|
||||
|
||||
Important: Each test must call `clear_options_cache()` in setUp/teardown to prevent cross-test cache pollution.
|
||||
**Isolation pattern:** every test class should clear the module cache in `setUp` and `tearDown`, e.g.:
|
||||
|
||||
```python
|
||||
class TestGetExpirations:
|
||||
def setup_method(self):
|
||||
from tradingagents.dataflows import tradier as t
|
||||
t.clear_options_cache()
|
||||
|
||||
def teardown_method(self):
|
||||
from tradingagents.dataflows import tradier as t
|
||||
t.clear_options_cache()
|
||||
|
||||
def test_expirations_filtered_by_dte(self):
|
||||
...
|
||||
```
|
||||
|
||||
Apply the same pattern to other classes touching `get_options_chain` / `get_options_chain_structured`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>uv run python -m pytest tests/test_tradier.py -x -v --timeout=30</automated>
|
||||
<automated>uv run python -m pytest tests/unit/data/test_tradier.py -x -v --timeout=30</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- tests/conftest.py exists and contains MOCK_EXPIRATIONS_RESPONSE, MOCK_CHAIN_RESPONSE, MOCK_CHAIN_NO_GREEKS_RESPONSE, MOCK_SINGLE_CONTRACT_RESPONSE, MOCK_SINGLE_EXPIRATION_RESPONSE
|
||||
- tests/test_tradier.py exists and contains at least 10 test methods
|
||||
- tests/test_tradier.py contains class TestGetExpirations
|
||||
- tests/test_tradier.py contains class TestGetOptionsChain
|
||||
- tests/test_tradier.py contains class TestGreeksPresent
|
||||
- tests/test_tradier.py contains class TestGreeksAbsent
|
||||
- tests/test_tradier.py contains class TestIVPresent
|
||||
- tests/test_tradier.py contains class TestDTEFilter
|
||||
- tests/test_tradier.py contains class TestVendorRegistration
|
||||
- tests/test_tradier.py contains class TestRateLimitDetection
|
||||
- tests/test_tradier.py contains class TestSessionCache
|
||||
- tests/test_tradier.py contains class TestSandboxURL
|
||||
- tests/test_tradier.py contains `clear_options_cache` calls for test isolation
|
||||
- `uv run python -m pytest tests/test_tradier.py -x --timeout=30` exits 0 with all tests passing
|
||||
- tests/unit/data/test_tradier.py exists and defines **at least 10 test classes** (one per behavior bullet: TestGetExpirations, TestGetOptionsChain, …), each with one or more `test_*` methods
|
||||
- tests/unit/data/test_tradier.py contains class TestGetExpirations
|
||||
- tests/unit/data/test_tradier.py contains class TestGetOptionsChain
|
||||
- tests/unit/data/test_tradier.py contains class TestGreeksPresent
|
||||
- tests/unit/data/test_tradier.py contains class TestGreeksAbsent
|
||||
- tests/unit/data/test_tradier.py contains class TestIVPresent
|
||||
- tests/unit/data/test_tradier.py contains class TestDTEFilter
|
||||
- tests/unit/data/test_tradier.py contains class TestVendorRegistration
|
||||
- tests/unit/data/test_tradier.py contains class TestRateLimitDetection
|
||||
- tests/unit/data/test_tradier.py contains class TestSessionCache
|
||||
- tests/unit/data/test_tradier.py contains class TestSandboxURL
|
||||
- tests/unit/data/test_tradier.py contains `clear_options_cache` calls for test isolation
|
||||
- `uv run python -m pytest tests/unit/data/test_tradier.py -x --timeout=30` exits 0 with all tests passing
|
||||
</acceptance_criteria>
|
||||
<done>All tests pass. Tests cover: DATA-01 (chain retrieval), DATA-02 (expirations), DATA-03 (Greeks present), DATA-04 (IV present), DATA-05 (DTE filtering), DATA-08 (vendor registration), plus edge cases (no Greeks, single contract, single expiration, rate limits, caching, sandbox URL). No real API calls -- all mocked.</done>
|
||||
</task>
|
||||
|
|
@ -533,7 +554,7 @@ Important: Each test must call `clear_options_cache()` in setUp/teardown to prev
|
|||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `uv run python -m pytest tests/test_tradier.py -x -v --timeout=30` -- all tests pass
|
||||
- `uv run python -m pytest tests/unit/data/test_tradier.py -x -v --timeout=30` -- all tests pass
|
||||
- `uv run python -c "from tradingagents.dataflows.interface import TOOLS_CATEGORIES, VENDOR_LIST; assert 'tradier' in VENDOR_LIST; assert 'options_chain' in TOOLS_CATEGORIES; print('ROUTING OK')"` -- vendor registered
|
||||
- `uv run python -c "from tradingagents.agents.utils.options_tools import get_options_chain, get_options_expirations; print('TOOLS OK')"` -- tool functions importable
|
||||
</verification>
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@ Integrate Tradier as a new data vendor for options chain retrieval with 1st-orde
|
|||
|
||||
### Data Fetching Strategy
|
||||
- **D-03:** Pre-fetch all expirations within DTE range upfront at the start of an analysis run, cache for the session. Do NOT let individual agents make separate API calls for the same data.
|
||||
- **D-04:** Default DTE filter range: 0-50 DTE (covers TastyTrade's 30-50 sweet spot plus weeklies and near-term options)
|
||||
- **D-05:** Always request `greeks=true` from Tradier — there's no reason to skip Greeks when the whole point is options analysis
|
||||
- **D-04:** Default DTE filter range: 0-50 DTE (covers **Tastytrade** methodology sweet spot ~30–50 DTE plus weeklies and near-term options)
|
||||
- **D-05:** Always pass `greeks=true` on chain requests. **Sandbox:** Tradier sandbox often returns **no Greeks** (empty/`null` greeks object). Implementations must still call with `greeks=true` and treat missing fields as `None` without failing. **Production:** expect populated Greeks. Do **not** gate `greeks=true` on `TRADIER_SANDBOX` — only the **response handling** differs.
|
||||
|
||||
### Data Structure
|
||||
- **D-06:** Dual output format: Pandas DataFrame for bulk operations (consistent with existing yfinance pattern) AND typed dataclass (`OptionsContract`, `OptionsChain`) for individual contract access by downstream agents
|
||||
- **D-06:** **Canonical representation:** typed `OptionsContract` / `OptionsChain` in memory; **bulk analysis** uses `OptionsChain.to_dataframe()` (pandas). Optional helpers may add `from_dataframe` / reconstruction utilities if a round-trip is needed; avoid maintaining two divergent sources of truth — derive the DataFrame from the dataclass list.
|
||||
|
||||
### Claude's Discretion
|
||||
- **Caching strategy:** Claude picks the best caching approach. In-memory per-session is simpler; disk TTL helps during development. Choose based on what fits the existing architecture best.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||
>
|
||||
> **Scope:** Rows marked **“Claude's discretion”** / **“You decide”** are discussion-only until copied into **01-CONTEXT.md** (or another canonical doc). Implementation agents should follow **CONTEXT.md**, not un-promoted discussion rows.
|
||||
|
||||
**Date:** 2026-03-29
|
||||
**Phase:** 01-tradier-data-layer
|
||||
|
|
@ -43,8 +45,8 @@
|
|||
| Disk with TTL | Cache to disk with configurable TTL | |
|
||||
| You decide | Claude picks best approach | ✓ |
|
||||
|
||||
**User's choice:** Claude's discretion
|
||||
**Notes:** None
|
||||
**User's choice:** Claude's discretion — **TODO:** promote chosen approach (memory vs disk, TTL) into **01-CONTEXT.md** before execution if it becomes a hard requirement.
|
||||
**Notes:** **Constraints for implementers:** target session scope ≈ one CLI `propagate()` / analysis run; prefer **in-memory** unless persistence is required. If disk cache: document max footprint and TTL (e.g. stale chain tolerance on the order of minutes unless user refreshes). Low-memory environments: in-memory only, no mandatory disk I/O.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -68,7 +70,7 @@
|
|||
| Queue and throttle | Pre-emptive request queue | |
|
||||
| You decide | Claude picks based on existing patterns | ✓ |
|
||||
|
||||
**User's choice:** Claude's discretion
|
||||
**User's choice:** Claude's discretion — **TODO:** mirror final retry/throttle design in **01-CONTEXT.md** when locked.
|
||||
**Notes:** Should follow AlphaVantageRateLimitError pattern
|
||||
|
||||
---
|
||||
|
|
@ -83,7 +85,7 @@
|
|||
| 0-50 DTE (Custom) | User-specified range | ✓ |
|
||||
|
||||
**User's choice:** 0-50 DTE
|
||||
**Notes:** User specified custom range covering near-term through TastyTrade sweet spot
|
||||
**Notes:** User specified custom range covering near-term through **Tastytrade** methodology sweet spot
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -95,15 +97,15 @@
|
|||
| Typed dataclass | Custom OptionsChain dataclass | |
|
||||
| Both | DataFrame for bulk, dataclass for individual access | ✓ |
|
||||
|
||||
**User's choice:** Both
|
||||
**Notes:** Dual format for different consumption patterns
|
||||
**User's choice:** Both — **promoted to CONTEXT D-06:** canonical typed `OptionsChain` / `OptionsContract`; DataFrame via `to_dataframe()` for bulk; single source of truth in the dataclass list (see **01-CONTEXT.md**).
|
||||
**Notes:** Rationale: dataclasses give validation and clear contracts; DataFrames match existing analyst tooling without duplicating mutable parallel state.
|
||||
|
||||
---
|
||||
|
||||
## Claude's Discretion
|
||||
|
||||
- Caching strategy (in-memory vs disk TTL)
|
||||
- Rate limit handling approach
|
||||
- Caching strategy (in-memory vs disk TTL) — promote to CONTEXT before treating as mandatory
|
||||
- Rate limit handling approach — promote to CONTEXT before treating as mandatory
|
||||
|
||||
## Deferred Ideas
|
||||
|
||||
|
|
|
|||
|
|
@ -145,15 +145,19 @@ class OptionsChain:
|
|||
return pd.DataFrame([vars(c) for c in self.contracts])
|
||||
|
||||
def filter_by_dte(self, min_dte: int = 0, max_dte: int = 50) -> "OptionsChain":
|
||||
"""Filter contracts by DTE range."""
|
||||
"""Filter contracts by DTE range; skip contracts with bad expiration_date strings."""
|
||||
today = date.today()
|
||||
filtered = []
|
||||
for c in self.contracts:
|
||||
exp = datetime.strptime(c.expiration_date, "%Y-%m-%d").date()
|
||||
try:
|
||||
exp = datetime.strptime(c.expiration_date, "%Y-%m-%d").date()
|
||||
except (ValueError, TypeError):
|
||||
# Log/skip malformed c.expiration_date — do not fail whole chain
|
||||
continue
|
||||
dte = (exp - today).days
|
||||
if min_dte <= dte <= max_dte:
|
||||
filtered.append(c)
|
||||
filtered_exps = list(set(c.expiration_date for c in filtered))
|
||||
filtered_exps = list({c.expiration_date for c in filtered})
|
||||
return OptionsChain(
|
||||
underlying=self.underlying,
|
||||
fetch_timestamp=self.fetch_timestamp,
|
||||
|
|
@ -206,12 +210,20 @@ def make_tradier_request(path: str, params: dict | None = None) -> dict:
|
|||
}
|
||||
response = requests.get(url, headers=headers, params=params or {})
|
||||
|
||||
# Check rate limit via headers
|
||||
remaining = response.headers.get("X-Ratelimit-Available")
|
||||
if remaining is not None and int(remaining) <= 0:
|
||||
raise TradierRateLimitError(
|
||||
f"Tradier rate limit exceeded. Resets at: {response.headers.get('X-Ratelimit-Expiry')}"
|
||||
)
|
||||
# Check rate limit via headers (defensive parse — header may be non-numeric)
|
||||
remaining_raw = response.headers.get("X-Ratelimit-Available")
|
||||
expiry = response.headers.get("X-Ratelimit-Expiry")
|
||||
if remaining_raw is not None:
|
||||
try:
|
||||
remaining = int(remaining_raw)
|
||||
except (ValueError, TypeError):
|
||||
raise TradierRateLimitError(
|
||||
f"Invalid X-Ratelimit-Available={remaining_raw!r}; X-Ratelimit-Expiry={expiry!r}"
|
||||
)
|
||||
if remaining <= 0:
|
||||
raise TradierRateLimitError(
|
||||
f"Tradier rate limit exceeded (quota {remaining}). Resets at: {expiry}"
|
||||
)
|
||||
|
||||
# Also check HTTP 429
|
||||
if response.status_code == 429:
|
||||
|
|
@ -536,34 +548,36 @@ def make_tradier_request_with_retry(
|
|||
### Test Framework
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Framework | unittest (stdlib) -- existing test uses unittest. No pytest installed. |
|
||||
| Config file | None -- see Wave 0 |
|
||||
| Quick run command | `uv run python -m pytest tests/ -x --timeout=10` |
|
||||
| Framework | **pytest** (>=8) — Phase 1 adds `tests/unit/data/test_tradier.py` and `pytest` dev dependency; existing `tests/test_ticker_symbol_handling.py` may remain unittest-style until migrated. |
|
||||
| Config file | **None in Wave 0** — no `pytest.ini`/`pyproject` test section required for initial Tradier tests; add later if markers/fixtures grow. |
|
||||
| Quick run command | `uv run python -m pytest tests/unit/data/test_tradier.py -x --timeout=10` |
|
||||
| Full suite command | `uv run python -m pytest tests/ --timeout=30` |
|
||||
|
||||
### Phase Requirements to Test Map
|
||||
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||
|--------|----------|-----------|-------------------|-------------|
|
||||
| DATA-01 | Retrieve full options chain (strikes, exps, bid/ask, vol, OI) | unit (mock API) | `uv run python -m pytest tests/test_tradier.py::TestGetOptionsChain -x` | No -- Wave 0 |
|
||||
| DATA-02 | Retrieve expirations and strikes for any ticker | unit (mock API) | `uv run python -m pytest tests/test_tradier.py::TestGetExpirations -x` | No -- Wave 0 |
|
||||
| DATA-03 | 1st-order Greeks displayed per contract with timestamp | unit (mock API) | `uv run python -m pytest tests/test_tradier.py::TestGreeksPresent -x` | No -- Wave 0 |
|
||||
| DATA-04 | IV per contract (bid_iv, mid_iv, ask_iv, smv_vol) | unit (mock API) | `uv run python -m pytest tests/test_tradier.py::TestIVPresent -x` | No -- Wave 0 |
|
||||
| DATA-05 | Filter options chain by DTE range | unit | `uv run python -m pytest tests/test_tradier.py::TestDTEFilter -x` | No -- Wave 0 |
|
||||
| DATA-08 | Tradier registered in vendor routing layer | unit | `uv run python -m pytest tests/test_tradier.py::TestVendorRegistration -x` | No -- Wave 0 |
|
||||
| DATA-01 | Retrieve full options chain (strikes, exps, bid/ask, vol, OI) | unit (mock / vcr) | `uv run python -m pytest tests/unit/data/test_tradier.py::TestGetOptionsChain -x` | No -- Wave 0 |
|
||||
| DATA-02 | Retrieve expirations and strikes for any ticker | unit (mock / vcr) | `uv run python -m pytest tests/unit/data/test_tradier.py::TestGetExpirations -x` | No -- Wave 0 |
|
||||
| DATA-03 | 1st-order Greeks displayed per contract with timestamp | unit (mock / vcr) | `uv run python -m pytest tests/unit/data/test_tradier.py::TestGreeksPresent -x` | No -- Wave 0 |
|
||||
| DATA-04 | IV per contract (bid_iv, mid_iv, ask_iv, smv_vol) | unit (mock / vcr) | `uv run python -m pytest tests/unit/data/test_tradier.py::TestIVPresent -x` | No -- Wave 0 |
|
||||
| DATA-05 | Filter options chain by DTE range | unit | `uv run python -m pytest tests/unit/data/test_tradier.py::TestDTEFilter -x` | No -- Wave 0 |
|
||||
| DATA-06 | (Phase 2) 2nd-order Greeks — not validated in Phase 1 | — | — | N/A |
|
||||
| DATA-07 | (Phase 10) Tastytrade streaming — not validated in Phase 1 | — | — | N/A |
|
||||
| DATA-08 | Tradier registered in vendor routing layer | unit | `uv run python -m pytest tests/unit/data/test_tradier.py::TestVendorRegistration -x` | No -- Wave 0 |
|
||||
|
||||
### Sampling Rate
|
||||
- **Per task commit:** `uv run python -m pytest tests/test_tradier.py -x --timeout=10`
|
||||
- **Per task commit:** `uv run python -m pytest tests/unit/data/test_tradier.py -x --timeout=10`
|
||||
- **Per wave merge:** `uv run python -m pytest tests/ --timeout=30`
|
||||
- **Phase gate:** Full suite green before `/gsd:verify-work`
|
||||
|
||||
### Wave 0 Gaps
|
||||
- [ ] `tests/test_tradier.py` -- covers DATA-01 through DATA-05, DATA-08
|
||||
- [ ] `tests/conftest.py` -- shared fixtures (mock Tradier API responses)
|
||||
- [ ] Install pytest: `uv add --dev pytest>=8.0`
|
||||
- [ ] `tests/unit/data/test_tradier.py` -- covers DATA-01 through DATA-05, DATA-08 (Tier 2 data layer; prefer **vcrpy** cassettes under `tests/fixtures/cassettes/tradier/` for realistic HTTP replay)
|
||||
- [ ] `tests/conftest.py` (repo root or `tests/unit/data/`) -- shared fixtures + VCR configuration
|
||||
- [ ] Install pytest: `uv add --dev pytest>=8.0` and `uv add --dev vcrpy` (or pytest-vcr) when enabling cassettes
|
||||
|
||||
## Project Constraints (from CLAUDE.md)
|
||||
|
||||
- **Python >=3.10**, consistent with existing codebase
|
||||
- **Python >=3.11**, consistent with `pyproject.toml` and Tastytrade SDK (Phase 10)
|
||||
- **snake_case.py** for all module names, no hyphens
|
||||
- **Factory functions** use `create_` prefix; getter functions use `get_` prefix
|
||||
- **`requests`** for HTTP calls (no new HTTP client dependency)
|
||||
|
|
|
|||
|
|
@ -17,17 +17,19 @@ created: 2026-03-29
|
|||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Framework** | pytest 7.x |
|
||||
| **Config file** | none — Wave 0 installs |
|
||||
| **Quick run command** | `python -m pytest tests/test_tradier.py -x -q` |
|
||||
| **Framework** | pytest >=8 |
|
||||
| **Config file** | **None in Wave 0** — Wave 0 adds tests and dev deps only; no `pytest.ini` / `pyproject` `[tool.pytest]` block is required until markers grow. |
|
||||
| **Quick run command** | `python -m pytest tests/unit/data/test_tradier.py -x -q` |
|
||||
| **Full suite command** | `python -m pytest tests/ -q` |
|
||||
| **Estimated runtime** | ~5 seconds |
|
||||
|
||||
**Tier / replay:** `tests/unit/data/test_tradier.py` is **Tier 2 (data layer)**. Use **vcrpy** (or pytest-vcr) to record/replay Tradier HTTP into `tests/fixtures/cassettes/tradier/` (or `tests/unit/data/cassettes/`).
|
||||
|
||||
---
|
||||
|
||||
## Sampling Rate
|
||||
|
||||
- **After every task commit:** Run `python -m pytest tests/test_tradier.py -x -q`
|
||||
- **After every task commit:** Run `python -m pytest tests/unit/data/test_tradier.py -x -q`
|
||||
- **After every plan wave:** Run `python -m pytest tests/ -q`
|
||||
- **Before `/gsd:verify-work`:** Full suite must be green
|
||||
- **Max feedback latency:** 5 seconds
|
||||
|
|
@ -38,12 +40,14 @@ created: 2026-03-29
|
|||
|
||||
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|
||||
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
|
||||
| TBD | TBD | TBD | DATA-01 | unit | `python -m pytest tests/test_tradier.py -k chain -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-02 | unit | `python -m pytest tests/test_tradier.py -k expirations -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-03 | unit | `python -m pytest tests/test_tradier.py -k greeks -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-04 | unit | `python -m pytest tests/test_tradier.py -k iv -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-05 | unit | `python -m pytest tests/test_tradier.py -k dte -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-08 | integration | `python -m pytest tests/test_tradier.py -k vendor -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-01 | unit | `python -m pytest tests/unit/data/test_tradier.py -k chain -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-02 | unit | `python -m pytest tests/unit/data/test_tradier.py -k expirations -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-03 | unit | `python -m pytest tests/unit/data/test_tradier.py -k greeks -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-04 | unit | `python -m pytest tests/unit/data/test_tradier.py -k iv -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-05 | unit | `python -m pytest tests/unit/data/test_tradier.py -k dte -q` | ❌ W0 | ⬜ pending |
|
||||
| TBD | TBD | TBD | DATA-08 | integration | `python -m pytest tests/unit/data/test_tradier.py -k vendor -q` | ❌ W0 | ⬜ pending |
|
||||
|
||||
**DATA-06 / DATA-07:** Intentionally omitted here — validated in **Phase 2** (DATA-06) and **Phase 10** (DATA-07); see `.planning/REQUIREMENTS.md` traceability.
|
||||
|
||||
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||
|
||||
|
|
@ -51,11 +55,11 @@ created: 2026-03-29
|
|||
|
||||
## Wave 0 Requirements
|
||||
|
||||
- [ ] `tests/test_tradier.py` — stubs for DATA-01 through DATA-05 and DATA-08
|
||||
- [ ] `tests/conftest.py` — shared fixtures (mock Tradier responses)
|
||||
- [ ] pytest install — if not already present
|
||||
- [ ] `tests/unit/data/test_tradier.py` — stubs/tests for DATA-01 through DATA-05 and DATA-08
|
||||
- [ ] `tests/conftest.py` — shared fixtures; **vcrpy** cassette config for Tradier HTTP replay
|
||||
- [ ] pytest + vcrpy dev install — `uv add --dev pytest>=8.0 vcrpy` (or equivalent)
|
||||
|
||||
*If none: "Existing infrastructure covers all phase requirements."*
|
||||
*Aligned with the **Config file** row: Wave 0 does not add a pytest config file unless needed later.*
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -69,7 +73,7 @@ created: 2026-03-29
|
|||
|
||||
## Validation Sign-Off
|
||||
|
||||
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
|
||||
- [ ] Every task has an **Automated Command** entry in the map above **or** an explicit Wave 0 dependency (no bare `<automated>` placeholders)
|
||||
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
|
||||
- [ ] Wave 0 covers all MISSING references
|
||||
- [ ] No watch-mode flags
|
||||
|
|
|
|||
|
|
@ -64,14 +64,23 @@ def create_volatility_analyst(llm_client, tools):
|
|||
system_prompt = VOLATILITY_ANALYST_PROMPT
|
||||
|
||||
def volatility_analyst(state):
|
||||
if "options_chain" not in state or "ticker" not in state:
|
||||
return {"volatility_analysis_error": "missing options_chain or ticker in state"}
|
||||
if "compute_iv_rank" not in tools:
|
||||
return {"volatility_analysis_error": "compute_iv_rank tool not registered"}
|
||||
chain_data = state["options_chain"]
|
||||
# Compute IV metrics using tools
|
||||
iv_rank = tools["compute_iv_rank"](chain_data, state["ticker"])
|
||||
# LLM interprets the metrics
|
||||
response = llm_client.invoke([
|
||||
SystemMessage(content=system_prompt),
|
||||
HumanMessage(content=format_iv_analysis(iv_rank, chain_data))
|
||||
])
|
||||
ticker = state["ticker"]
|
||||
try:
|
||||
iv_rank = tools["compute_iv_rank"](chain_data, ticker)
|
||||
except Exception as e:
|
||||
return {"volatility_analysis_error": f"compute_iv_rank failed: {e!s}"}
|
||||
try:
|
||||
response = llm_client.invoke([
|
||||
SystemMessage(content=system_prompt),
|
||||
HumanMessage(content=format_iv_analysis(iv_rank, chain_data))
|
||||
])
|
||||
except Exception as e:
|
||||
return {"volatility_analysis_error": f"llm invoke failed: {e!s}"}
|
||||
return {"volatility_analysis": response.content}
|
||||
|
||||
return volatility_analyst
|
||||
|
|
@ -92,6 +101,7 @@ def get_options_chain(ticker, expiration, config):
|
|||
return tradier.get_chain(ticker, expiration)
|
||||
elif vendor == "tastytrade":
|
||||
return tastytrade.get_chain(ticker, expiration)
|
||||
raise ValueError(f"Unsupported options_vendor={vendor!r}; expected 'tradier' or 'tastytrade'")
|
||||
```
|
||||
|
||||
### Pattern 3: Computation Modules as Pure Functions
|
||||
|
|
@ -102,9 +112,11 @@ def get_options_chain(ticker, expiration, config):
|
|||
```python
|
||||
# tradingagents/options/gex.py
|
||||
def compute_gex(chain_df: pd.DataFrame, spot: float) -> pd.DataFrame:
|
||||
"""Pure function: chain DataFrame in, GEX DataFrame out."""
|
||||
chain_df["call_gex"] = chain_df["gamma"] * chain_df["open_interest"] * 100 * spot**2 * 0.01
|
||||
chain_df["put_gex"] = -chain_df["gamma"] * chain_df["open_interest"] * 100 * spot**2 * 0.01
|
||||
"""Pure function: chain DataFrame in, GEX DataFrame out.
|
||||
Standard notional-scaled GEX: gamma * OI * 100 * spot**2 (per-share gamma → contract multiplier 100).
|
||||
"""
|
||||
chain_df["call_gex"] = chain_df["gamma"] * chain_df["open_interest"] * 100 * spot**2
|
||||
chain_df["put_gex"] = -chain_df["gamma"] * chain_df["open_interest"] * 100 * spot**2
|
||||
# ... aggregate, find walls, flip zone
|
||||
return gex_df
|
||||
```
|
||||
|
|
@ -153,9 +165,17 @@ def compute_gex(chain_df: pd.DataFrame, spot: float) -> pd.DataFrame:
|
|||
| API rate limits | Tradier: ~2 req/ticker (chain + expirations), well within 120 req/min | 20 requests, still fine | 100+ requests, need queuing/throttling |
|
||||
| Chain data size | ~200 strikes per expiry, 5-8 expiries = 1000-1600 rows | 10x = 10-16K rows, fine in memory | 50x = manageable but cache aggressively |
|
||||
| GEX computation | Sub-second numpy vectorization | Still sub-second | Still sub-second; numpy handles millions of rows |
|
||||
| LLM calls per analysis | ~6 agents x 1 call each = 6 LLM calls | 60 LLM calls, significant latency | Need batching or parallel execution |
|
||||
| LLM calls per analysis | **~6 + Ndebate** — Volatility, Greeks, GEX, Flow (4 analysis) + Strategy selection + Portfolio manager + **Options debate × rounds (N)** | Scales with debate rounds; 60+ calls multi-ticker | Batch where safe; parallelize independent agents; cap `max_debate_rounds` |
|
||||
| Tastytrade WebSocket | Single subscription, minimal overhead | 10 subscriptions, fine | May hit subscription limits |
|
||||
|
||||
## Operational Concerns
|
||||
|
||||
- **Errors / retries:** Vendor routers (`get_options_chain`, `route_to_vendor`) should map HTTP/rate-limit failures to typed errors, retry with backoff where safe, and return actionable messages to agents (see REL-01/REL-02 in REQUIREMENTS.md).
|
||||
- **Testing:** Prefer pure-function unit tests for `tradingagents/options/gex.py`, `greeks.py`, vol math; mock LLMs and external HTTP; integration tests for `options_team` / LangGraph wiring.
|
||||
- **Observability:** LangGraph flows (`tradingagents/graph/options_team.py` when added) should emit structured step logs (node name, duration, tool calls) — align with OBS-01.
|
||||
- **Cost:** Batch or cache LLM calls; avoid redundant chain fetches across nodes (session cache).
|
||||
- **Security:** API keys via env only; rotate keys; least-privilege broker API tokens. Deep-dive docs TBD per subsystem.
|
||||
|
||||
## Sources
|
||||
|
||||
- Existing codebase patterns in `tradingagents/agents/`, `tradingagents/graph/`, `tradingagents/dataflows/`
|
||||
|
|
|
|||
|
|
@ -33,14 +33,20 @@ Mistakes that cause rewrites or major issues.
|
|||
|
||||
### Pitfall 4: Python Version Incompatibility with tastytrade SDK
|
||||
|
||||
**What goes wrong:** The tastytrade community SDK (tastyware/tastytrade v12.3.1) requires Python >=3.11. The project declares `requires-python = ">=3.10"`. If a user installs on Python 3.10, the tastytrade dependency will fail.
|
||||
**Why it happens:** The project's minimum Python version is lower than the tastytrade SDK's requirement.
|
||||
**Consequences:** Installation failure for Python 3.10 users; confusing error messages.
|
||||
**Prevention:** Either bump `requires-python` to `>=3.11` or make tastytrade an optional dependency with graceful degradation (Tradier-only mode). Recommended: bump to >=3.11 since numpy/scipy latest versions also dropped 3.10.
|
||||
**Detection:** CI testing on Python 3.10 would catch this immediately.
|
||||
**What goes wrong:** The tastytrade community SDK (tastyware/tastytrade v12.3.1) requires Python >=3.11. If `requires-python` drifts below that, installs break for streaming-dependent workflows.
|
||||
**Why it happens:** SDK minimum Python is stricter than some legacy project baselines.
|
||||
**Consequences:** Installation failure on older interpreters; confusing error messages.
|
||||
**Prevention:** Keep `requires-python = ">=3.11"` in `pyproject.toml` (current baseline). Optional: extras group for Tradier-only installs if you ever split optional deps.
|
||||
**Detection:** CI on Python 3.11+ matrix; `pip install` smoke test with tastytrade extra.
|
||||
|
||||
## Moderate Pitfalls
|
||||
|
||||
### Overfitted Strategy Backtests
|
||||
|
||||
**What goes wrong:** Options or equity strategies show unrealistically strong historical performance (curve-fit to noise), so live results collapse.
|
||||
**Red flags:** Sharpe above ~2 on daily bars without robustness checks; win rate above ~60% for directional systems; small parameter changes destroy performance.
|
||||
**Prevention:** Walk-forward validation, holdout / out-of-sample periods, parameter sensitivity sweeps, stability checks, and alerting when metrics exceed sanity thresholds before promoting a ruleset.
|
||||
|
||||
### Pitfall 1: LLM Hallucinating Option Contract Symbols
|
||||
|
||||
**What goes wrong:** When the LLM agent recommends specific contracts, it may hallucinate valid-looking but non-existent option symbols (wrong expiration dates, non-standard strikes).
|
||||
|
|
@ -95,7 +101,7 @@ Use consistent 252-trading-day lookback. Document the definition in code comment
|
|||
| SVI calibration | Convergence failure on illiquid names | Fallback to interpolation, minimum liquidity filters |
|
||||
| GEX implementation | Sign convention error | Comprehensive unit tests against known outputs |
|
||||
| Flow detection | False positives on unusual activity (earnings, ex-div dates) | Check corporate calendar before flagging activity as "unusual" |
|
||||
| Strategy recommendation | LLM recommending strategies inappropriate for IV environment | TastyTrade rules engine as guardrail (e.g., do not sell premium when IVR < 20) |
|
||||
| Strategy recommendation | LLM recommending strategies inappropriate for IV environment | **Tastytrade** rules engine as **soft guardrail** by default: surface **non-blocking warnings** when IVR is below a configured threshold (default e.g. 20); **hard block** only if product policy enables `strict_iv_rules`. Thresholds must be **CONFIG-01**-style tunables per user/strategy. |
|
||||
| Multi-leg construction | Recommending spreads wider than available liquidity | Check bid-ask spreads on recommended legs; warn if total spread cost is >10% of max profit |
|
||||
|
||||
## Sources
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
|
||||
| Technology | Version | Purpose | Why | Confidence |
|
||||
|------------|---------|---------|-----|------------|
|
||||
| **numpy** | >=2.0.0 | Vectorized GEX calculation across all strikes/expirations | GEX formula is straightforward: `GEX_per_strike = Gamma * OI * 100 * Spot^2 * 0.01`, then `Net_GEX = sum(Call_GEX) - sum(Put_GEX)`. Pure numpy vectorization handles the full chain in microseconds. | HIGH |
|
||||
| **numpy** | >=2.0.0 | Vectorized GEX calculation across all strikes/expirations | GEX formula (standard notional form): `GEX_per_strike = Gamma * OI * 100 * Spot^2` (calls positive, puts negative per sign convention), then net across strikes. Pure numpy vectorization handles the full chain in microseconds. | HIGH |
|
||||
| **pandas** | >=2.3.0 | Structuring GEX output (strike-level breakdown, flip zones, walls) | Already a core dependency. GEX output is naturally tabular: per-strike gamma, cumulative GEX, call/put walls, flip zones. | HIGH |
|
||||
|
||||
**No external library needed.** GEX computation is arithmetic on options chain data. The SpotGamma methodology is documented:
|
||||
|
|
@ -85,10 +85,7 @@ The complexity is in interpretation (which the LLM agent handles), not computati
|
|||
|
||||
## Python Version Consideration
|
||||
|
||||
The project declares `requires-python = ">=3.10"` but the development venv runs Python 3.13. The tastytrade SDK requires `>=3.11`. Two options:
|
||||
|
||||
1. **Recommended:** Bump `requires-python` to `>=3.11` when adding the options module. This unlocks the tastytrade SDK and latest numpy/scipy without constraints.
|
||||
2. **Alternative:** Keep `>=3.10` and make tastytrade an optional dependency. Tradier alone covers the core use case.
|
||||
The project declares `requires-python = ">=3.11"` (aligned with the tastytrade SDK and options module). Development venvs should use 3.11+ (e.g. 3.13).
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
|
|
@ -145,5 +142,5 @@ TASTYTRADE_PASSWORD=your_tastytrade_pass
|
|||
- [SpotGamma GEX methodology](https://spotgamma.com/gamma-exposure-gex/) -- MEDIUM confidence, proprietary methodology with published formulas
|
||||
- [SVI vs SABR comparison (2025 thesis)](https://repositori.upf.edu/items/eceeb187-f169-483e-bf67-416fd9e00d70) -- MEDIUM confidence, academic source
|
||||
- [SVI fitting with Python](https://tradingtechai.medium.com/python-volatility-surface-modeling-data-fetching-iv-calculation-svi-fitting-and-visualization-80be58328ac6) -- LOW confidence, blog post but methodology is standard
|
||||
- [NumPy 2.4.x release](https://numpy.org/news/) -- HIGH confidence, official
|
||||
- [SciPy 1.17.x release](https://docs.scipy.org/doc/scipy/release.html) -- HIGH confidence, official
|
||||
- [NumPy 2.0+ release notes](https://numpy.org/news/) -- HIGH confidence, official (minimum stack versions in tables refer to **>=2.0.0** / **>=1.14.0** unless a stricter pin is added later)
|
||||
- [SciPy 1.14+ release notes](https://docs.scipy.org/doc/scipy/release.html) -- HIGH confidence, official
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ The principal risks are: (1) stale Greeks from Tradier's hourly ORATS refresh ca
|
|||
|
||||
### Recommended Stack
|
||||
|
||||
The core data stack is two vendors: Tradier for REST-based chains, Greeks, and IV (direct `requests` calls, no SDK needed), and tastytrade community SDK for optional real-time streaming via DXLink WebSocket. Tradier's free sandbox makes development straightforward; production requires a brokerage account. The official tastytrade SDK was archived in March 2026, making the `tastyware/tastytrade` community SDK (v12.3.1, 95%+ test coverage, full Pydantic typing) the only viable maintained option. It requires Python >=3.11, so the project's `requires-python` should be bumped from `>=3.10` to `>=3.11`.
|
||||
The core data stack is two vendors: Tradier for REST-based chains, Greeks, and IV (direct `requests` calls, no SDK needed), and tastytrade community SDK for optional real-time streaming via DXLink WebSocket. Tradier's free sandbox makes development straightforward; production requires a brokerage account. The official tastytrade SDK was archived in March 2026, making the `tastyware/tastytrade` community SDK (v12.3.1, 95%+ test coverage, full Pydantic typing) the only viable maintained option. It requires Python >=3.11, aligned with the project's `requires-python = ">=3.11"` in `pyproject.toml`.
|
||||
|
||||
All quantitative computation uses libraries already present or easily added: `blackscholes` for 2nd/3rd-order Greeks, `scipy.optimize` for IV solving (Brent's method) and SVI calibration, `scipy.interpolate` for vol surface construction, `numpy` for vectorized GEX computation, and `pandas` for structured output. QuantLib and py_vollib are both explicitly not recommended -- the former is a 200MB over-engineered dependency, the latter is abandoned since 2017.
|
||||
|
||||
|
|
|
|||
19
CLAUDE.md
19
CLAUDE.md
|
|
@ -12,7 +12,7 @@ An options trading analysis module for TradingAgents — a multi-agent AI system
|
|||
- **Data providers**: Tradier (REST, 120 req/min, Greeks hourly) and Tastyworks (REST + WebSocket streaming) as primary options data sources
|
||||
- **No 2nd-order Greeks from API**: Charm, Vanna, Volga must be calculated from 1st-order Greeks + Black-Scholes
|
||||
- **Architecture**: Must follow existing patterns — agent factory functions, vendor routing, LangGraph StateGraph
|
||||
- **Python**: >=3.10, consistent with existing codebase
|
||||
- **Python**: >=3.11, consistent with `pyproject.toml` and options-module / tastytrade SDK baseline
|
||||
- **LLM provider agnostic**: Options agents must work with any supported LLM provider via the client factory
|
||||
<!-- GSD:project-end -->
|
||||
|
||||
|
|
@ -20,10 +20,9 @@ An options trading analysis module for TradingAgents — a multi-agent AI system
|
|||
## Technology Stack
|
||||
|
||||
## Languages
|
||||
- Python >=3.10 - Entire codebase (agents, dataflows, CLI, graph orchestration)
|
||||
- None detected
|
||||
- Python >=3.11 - Entire codebase (agents, dataflows, CLI, graph orchestration)
|
||||
## Runtime
|
||||
- Python 3.10+ (compatible up to 3.13 per `uv.lock` resolution markers)
|
||||
- Python 3.11+ (compatible up to 3.13+ per `uv.lock` resolution markers)
|
||||
- uv (primary - `uv.lock` present at project root)
|
||||
- pip/setuptools (build backend, `pyproject.toml` uses `setuptools>=61.0`)
|
||||
- Lockfile: `uv.lock` present
|
||||
|
|
@ -70,7 +69,7 @@ An options trading analysis module for TradingAgents — a multi-agent AI system
|
|||
- Maps to `cli.main:app` (Typer application)
|
||||
- Fetches announcements from `https://api.tauric.ai/v1/announcements` (`cli/config.py`)
|
||||
## Platform Requirements
|
||||
- Python 3.10+
|
||||
- Python 3.11+
|
||||
- uv package manager (recommended) or pip
|
||||
- At least one LLM provider API key (OpenAI, Anthropic, Google, xAI, or OpenRouter)
|
||||
- Redis server (if redis-based features are used)
|
||||
|
|
@ -139,6 +138,9 @@ An options trading analysis module for TradingAgents — a multi-agent AI system
|
|||
- Analyst nodes return `{"messages": [result], "<type>_report": report}`
|
||||
- Debate nodes return `{"investment_debate_state": {...}}` or `{"risk_debate_state": {...}}`
|
||||
## LLM Prompt Construction
|
||||
|
||||
**Patterns:** Analysts often use LangChain `ChatPromptTemplate.from_messages()` with `MessagesPlaceholder` and `.partial()` for tool-calling flows (`tradingagents/agents/analysts/`). Researchers, managers, and the trader frequently use plain f-strings or string prompts with `llm.invoke(...)`. Prefer **ChatPromptTemplate + bind_tools** when tools are required; otherwise f-string prompts are acceptable for simpler nodes. Keep system vs human roles explicit and keep prompts close to the node factory that uses them.
|
||||
|
||||
## Configuration Pattern
|
||||
- `tradingagents/dataflows/config.py` holds a module-level `_config` dict
|
||||
- `set_config()` and `get_config()` provide access
|
||||
|
|
@ -228,6 +230,13 @@ An options trading analysis module for TradingAgents — a multi-agent AI system
|
|||
- LLM provider validation: `create_llm_client()` raises `ValueError` for unsupported providers (`tradingagents/llm_clients/factory.py`)
|
||||
- No structured error handling within agent nodes; LLM failures propagate as exceptions
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
- **Logging:** Mostly `print()` and Rich `console.print()`; no centralized log levels yet — see `.planning/codebase/INTEGRATIONS.md` for recommended structured logging path.
|
||||
- **Security:** Secrets only via environment / `.env` (gitignored); never commit API keys.
|
||||
- **Observability:** `StatsCallbackHandler` tracks LLM/tool/token counts in CLI runs; no external APM.
|
||||
- **Caching:** File cache under `tradingagents/dataflows/data_cache/`; Redis dependency declared but unused in code today.
|
||||
- **Reliability:** Vendor fallback in `route_to_vendor()` for rate limits; LLM calls generally lack retries (see `.planning/codebase/CONCERNS.md`).
|
||||
|
||||
<!-- GSD:architecture-end -->
|
||||
|
||||
<!-- GSD:workflow-start source:GSD defaults -->
|
||||
|
|
|
|||
Loading…
Reference in New Issue