574 lines
25 KiB
Markdown
574 lines
25 KiB
Markdown
---
|
|
phase: 01-tradier-data-layer
|
|
plan: 02
|
|
type: execute
|
|
wave: 2
|
|
depends_on:
|
|
- 01-01
|
|
files_modified:
|
|
- tradingagents/dataflows/interface.py
|
|
- tradingagents/default_config.py
|
|
- tradingagents/agents/utils/options_tools.py
|
|
- .env.example
|
|
- tests/unit/data/test_tradier.py
|
|
- tests/conftest.py
|
|
autonomous: true
|
|
requirements:
|
|
- DATA-08
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Tradier is registered as a vendor in VENDOR_LIST"
|
|
- "options_chain category exists in TOOLS_CATEGORIES with get_options_chain and get_options_expirations tools"
|
|
- "VENDOR_METHODS maps get_options_chain and get_options_expirations to Tradier implementations"
|
|
- "DEFAULT_CONFIG data_vendors includes options_chain: tradier"
|
|
- "route_to_vendor catches TradierRateLimitError for vendor fallback"
|
|
- "@tool decorated functions exist for options chain retrieval"
|
|
- "All unit tests pass with mocked Tradier API responses"
|
|
artifacts:
|
|
- path: "tradingagents/dataflows/interface.py"
|
|
provides: "Vendor routing with Tradier and options_chain category"
|
|
contains: "options_chain"
|
|
- path: "tradingagents/default_config.py"
|
|
provides: "Default config with options_chain vendor"
|
|
contains: "options_chain"
|
|
- path: "tradingagents/agents/utils/options_tools.py"
|
|
provides: "LangChain @tool functions for options data"
|
|
exports: ["get_options_chain", "get_options_expirations"]
|
|
- path: "tests/unit/data/test_tradier.py"
|
|
provides: "Unit tests for all DATA requirements"
|
|
min_lines: 100
|
|
key_links:
|
|
- from: "tradingagents/dataflows/interface.py"
|
|
to: "tradingagents/dataflows/tradier.py"
|
|
via: "import get_options_chain, get_options_expirations"
|
|
pattern: "from .tradier import"
|
|
- from: "tradingagents/agents/utils/options_tools.py"
|
|
to: "tradingagents/dataflows/interface.py"
|
|
via: "route_to_vendor call"
|
|
pattern: "route_to_vendor"
|
|
- from: "tradingagents/dataflows/interface.py"
|
|
to: "tradingagents/dataflows/tradier_common.py"
|
|
via: "import TradierRateLimitError for fallback catch"
|
|
pattern: "TradierRateLimitError"
|
|
---
|
|
|
|
<objective>
|
|
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/unit/data/test_tradier.py.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/01-tradier-data-layer/01-CONTEXT.md
|
|
@.planning/phases/01-tradier-data-layer/01-RESEARCH.md
|
|
@.planning/phases/01-tradier-data-layer/01-01-SUMMARY.md
|
|
|
|
<interfaces>
|
|
<!-- From Plan 01 outputs -->
|
|
|
|
From tradingagents/dataflows/tradier.py (created in Plan 01):
|
|
```python
|
|
@dataclass
|
|
class OptionsContract:
|
|
symbol: str; underlying: str; option_type: str; strike: float
|
|
expiration_date: str; bid: float; ask: float; last: float
|
|
volume: int; open_interest: int
|
|
delta: float | None; gamma: float | None; theta: float | None
|
|
vega: float | None; rho: float | None; phi: float | None
|
|
bid_iv: float | None; mid_iv: float | None; ask_iv: float | None
|
|
smv_vol: float | None; greeks_updated_at: str | None
|
|
|
|
@dataclass
|
|
class OptionsChain:
|
|
underlying: str; fetch_timestamp: str; expirations: list[str]
|
|
contracts: list[OptionsContract]
|
|
def to_dataframe(self) -> pd.DataFrame: ...
|
|
def filter_by_dte(self, min_dte: int = 0, max_dte: int = 50) -> "OptionsChain": ...
|
|
|
|
def get_options_expirations(symbol: str, min_dte: int = 0, max_dte: int = 50) -> list[str]: ...
|
|
def get_options_chain(symbol: str, min_dte: int = 0, max_dte: int = 50) -> str: ...
|
|
def get_options_chain_structured(symbol: str, min_dte: int = 0, max_dte: int = 50) -> OptionsChain: ...
|
|
def clear_options_cache(): ...
|
|
```
|
|
|
|
From tradingagents/dataflows/tradier_common.py (created in Plan 01):
|
|
```python
|
|
class TradierRateLimitError(Exception): pass
|
|
def get_api_key() -> str: ...
|
|
def get_base_url() -> str: ...
|
|
def make_tradier_request(path: str, params: dict | None = None) -> dict: ...
|
|
def make_tradier_request_with_retry(path: str, params: dict | None = None, max_retries: int = 3) -> dict: ...
|
|
```
|
|
|
|
From tradingagents/dataflows/interface.py (existing):
|
|
```python
|
|
TOOLS_CATEGORIES = { "core_stock_apis": ..., "technical_indicators": ..., "fundamental_data": ..., "news_data": ... }
|
|
VENDOR_LIST = ["yfinance", "alpha_vantage"]
|
|
VENDOR_METHODS = { "get_stock_data": {...}, ... }
|
|
def route_to_vendor(method: str, *args, **kwargs): ...
|
|
# Currently catches only AlphaVantageRateLimitError in fallback loop
|
|
```
|
|
|
|
From tradingagents/default_config.py (existing):
|
|
```python
|
|
DEFAULT_CONFIG = {
|
|
"data_vendors": {
|
|
"core_stock_apis": "yfinance",
|
|
"technical_indicators": "yfinance",
|
|
"fundamental_data": "yfinance",
|
|
"news_data": "yfinance",
|
|
},
|
|
"tool_vendors": {},
|
|
}
|
|
```
|
|
|
|
From tradingagents/agents/utils/core_stock_tools.py (pattern reference):
|
|
```python
|
|
from langchain_core.tools import tool
|
|
from typing import Annotated
|
|
from tradingagents.dataflows.interface import route_to_vendor
|
|
|
|
@tool
|
|
def get_stock_data(symbol: Annotated[str, "ticker symbol"], ...) -> str:
|
|
return route_to_vendor("get_stock_data", symbol, start_date, end_date)
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Register Tradier in vendor routing and update default config</name>
|
|
<files>tradingagents/dataflows/interface.py, tradingagents/default_config.py, .env.example, tradingagents/agents/utils/options_tools.py</files>
|
|
<read_first>
|
|
- tradingagents/dataflows/interface.py
|
|
- tradingagents/default_config.py
|
|
- tradingagents/agents/utils/core_stock_tools.py
|
|
- tradingagents/dataflows/tradier.py
|
|
- tradingagents/dataflows/tradier_common.py
|
|
- .env.example
|
|
</read_first>
|
|
<action>
|
|
**A. Update `tradingagents/dataflows/interface.py`:**
|
|
|
|
1. Add imports at top of file (after existing vendor imports):
|
|
```python
|
|
from .tradier import (
|
|
get_options_chain as get_tradier_options_chain,
|
|
get_options_expirations as get_tradier_options_expirations,
|
|
)
|
|
from .tradier_common import TradierRateLimitError
|
|
```
|
|
|
|
2. Add `"options_chain"` category to `TOOLS_CATEGORIES` dict:
|
|
```python
|
|
"options_chain": {
|
|
"description": "Options chain data with Greeks and IV",
|
|
"tools": [
|
|
"get_options_chain",
|
|
"get_options_expirations",
|
|
]
|
|
}
|
|
```
|
|
|
|
3. Add `"tradier"` to `VENDOR_LIST`:
|
|
```python
|
|
VENDOR_LIST = ["yfinance", "alpha_vantage", "tradier"]
|
|
```
|
|
|
|
4. Add options methods to `VENDOR_METHODS` dict:
|
|
```python
|
|
"get_options_chain": {
|
|
"tradier": get_tradier_options_chain,
|
|
},
|
|
"get_options_expirations": {
|
|
"tradier": get_tradier_options_expirations,
|
|
},
|
|
```
|
|
|
|
5. Update `route_to_vendor()` fallback exception catch to also catch `TradierRateLimitError`. Change line:
|
|
```python
|
|
except AlphaVantageRateLimitError:
|
|
```
|
|
to:
|
|
```python
|
|
except (AlphaVantageRateLimitError, TradierRateLimitError):
|
|
```
|
|
|
|
**B. Update `tradingagents/default_config.py`:**
|
|
|
|
Add to the `"data_vendors"` dict inside `DEFAULT_CONFIG`:
|
|
```python
|
|
"options_chain": "tradier", # Options: tradier
|
|
```
|
|
|
|
**C. Update `.env.example`:**
|
|
|
|
Add after the existing API keys section:
|
|
```
|
|
# Options Data Providers
|
|
TRADIER_API_KEY=
|
|
TRADIER_SANDBOX=false
|
|
```
|
|
|
|
**D. Create `tradingagents/agents/utils/options_tools.py`:**
|
|
|
|
Following the `core_stock_tools.py` pattern exactly:
|
|
|
|
```python
|
|
from langchain_core.tools import tool
|
|
from typing import Annotated
|
|
from tradingagents.dataflows.interface import route_to_vendor
|
|
|
|
|
|
@tool
|
|
def get_options_chain(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
min_dte: Annotated[int, "minimum days to expiration"] = 0,
|
|
max_dte: Annotated[int, "maximum days to expiration"] = 50,
|
|
) -> str:
|
|
"""
|
|
Retrieve options chain data with Greeks and IV for a given ticker symbol.
|
|
Returns strikes, expirations, bid/ask, volume, OI, 1st-order Greeks
|
|
(Delta, Gamma, Theta, Vega, Rho), and implied volatility (bid_iv,
|
|
mid_iv, ask_iv, smv_vol) filtered by DTE range.
|
|
|
|
Args:
|
|
symbol (str): Ticker symbol of the company, e.g. AAPL, TSLA
|
|
min_dte (int): Minimum days to expiration (default 0)
|
|
max_dte (int): Maximum days to expiration (default 50)
|
|
Returns:
|
|
str: A formatted dataframe containing options chain data with Greeks and IV.
|
|
"""
|
|
return route_to_vendor("get_options_chain", symbol, min_dte, max_dte)
|
|
|
|
|
|
@tool
|
|
def get_options_expirations(
|
|
symbol: Annotated[str, "ticker symbol of the company"],
|
|
min_dte: Annotated[int, "minimum days to expiration"] = 0,
|
|
max_dte: Annotated[int, "maximum days to expiration"] = 50,
|
|
) -> str:
|
|
"""
|
|
Retrieve available options expiration dates for a given ticker symbol,
|
|
filtered by DTE range.
|
|
|
|
Args:
|
|
symbol (str): Ticker symbol of the company, e.g. AAPL, TSLA
|
|
min_dte (int): Minimum days to expiration (default 0)
|
|
max_dte (int): Maximum days to expiration (default 50)
|
|
Returns:
|
|
str: Comma-separated list of expiration dates (YYYY-MM-DD format).
|
|
"""
|
|
result = route_to_vendor("get_options_expirations", symbol, min_dte, max_dte)
|
|
if isinstance(result, list):
|
|
return ", ".join(result)
|
|
return str(result)
|
|
```
|
|
|
|
Default DTE range 0-50 per D-04. Docstrings are LLM-readable per project conventions.
|
|
</action>
|
|
<verify>
|
|
<automated>uv run python -c "
|
|
from tradingagents.dataflows.interface import TOOLS_CATEGORIES, VENDOR_LIST, VENDOR_METHODS
|
|
assert 'options_chain' in TOOLS_CATEGORIES, 'options_chain not in TOOLS_CATEGORIES'
|
|
assert 'tradier' in VENDOR_LIST, 'tradier not in VENDOR_LIST'
|
|
assert 'get_options_chain' in VENDOR_METHODS, 'get_options_chain not in VENDOR_METHODS'
|
|
assert 'get_options_expirations' in VENDOR_METHODS, 'get_options_expirations not in VENDOR_METHODS'
|
|
from tradingagents.default_config import DEFAULT_CONFIG
|
|
assert DEFAULT_CONFIG['data_vendors'].get('options_chain') == 'tradier', 'options_chain not in default config'
|
|
from tradingagents.agents.utils.options_tools import get_options_chain, get_options_expirations
|
|
print('ALL CHECKS PASSED')
|
|
"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- tradingagents/dataflows/interface.py contains `"options_chain"` in TOOLS_CATEGORIES
|
|
- tradingagents/dataflows/interface.py contains `"tradier"` in VENDOR_LIST
|
|
- tradingagents/dataflows/interface.py contains `"get_options_chain"` in VENDOR_METHODS
|
|
- tradingagents/dataflows/interface.py contains `"get_options_expirations"` in VENDOR_METHODS
|
|
- tradingagents/dataflows/interface.py contains `TradierRateLimitError` in the except clause of route_to_vendor
|
|
- tradingagents/dataflows/interface.py contains `from .tradier import`
|
|
- tradingagents/dataflows/interface.py contains `from .tradier_common import TradierRateLimitError`
|
|
- tradingagents/default_config.py contains `"options_chain": "tradier"`
|
|
- .env.example contains `TRADIER_API_KEY=`
|
|
- .env.example contains `TRADIER_SANDBOX=false`
|
|
- tradingagents/agents/utils/options_tools.py exists
|
|
- tradingagents/agents/utils/options_tools.py contains `@tool` (at least twice)
|
|
- tradingagents/agents/utils/options_tools.py contains `route_to_vendor("get_options_chain"`
|
|
- tradingagents/agents/utils/options_tools.py contains `route_to_vendor("get_options_expirations"`
|
|
- tradingagents/agents/utils/options_tools.py contains `min_dte` and `max_dte` parameters with default 0 and 50 (D-04)
|
|
- `uv run python -c "from tradingagents.agents.utils.options_tools import get_options_chain"` exits 0
|
|
</acceptance_criteria>
|
|
<done>Tradier registered in vendor routing (VENDOR_LIST, TOOLS_CATEGORIES, VENDOR_METHODS). DEFAULT_CONFIG has options_chain: tradier. route_to_vendor catches TradierRateLimitError. Two @tool functions created following core_stock_tools.py pattern. .env.example updated with TRADIER_API_KEY and TRADIER_SANDBOX.</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Create comprehensive unit tests for all Phase 1 requirements</name>
|
|
<files>tests/unit/data/test_tradier.py, tests/conftest.py</files>
|
|
<read_first>
|
|
- tradingagents/dataflows/tradier.py
|
|
- tradingagents/dataflows/tradier_common.py
|
|
- tradingagents/dataflows/interface.py
|
|
- tradingagents/agents/utils/options_tools.py
|
|
- tests/test_ticker_symbol_handling.py
|
|
</read_first>
|
|
<behavior>
|
|
- TestGetExpirations (DATA-02): mock Tradier /expirations response with 5 dates, verify DTE filter returns only dates within range. Test single-date string normalization (Pitfall 5).
|
|
- TestGetOptionsChain (DATA-01): mock Tradier /chains response with 3 contracts, verify OptionsChain has correct underlying, expirations, and contract count. Test single-contract dict normalization (Pitfall 2).
|
|
- TestGreeksPresent (DATA-03): mock response with greeks object, verify OptionsContract has delta, gamma, theta, vega, rho values and greeks_updated_at timestamp.
|
|
- TestGreeksAbsent (Pitfall 1): mock response with greeks: null, verify OptionsContract has None for all Greeks fields without crashing.
|
|
- TestIVPresent (DATA-04): mock response with greeks object, verify OptionsContract has bid_iv, mid_iv, ask_iv, smv_vol values.
|
|
- 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: 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>
|
|
**A. Install pytest if not present:**
|
|
```bash
|
|
uv add --dev pytest>=8.0
|
|
```
|
|
|
|
**B. Create `tests/conftest.py`** with shared Tradier API mock fixtures:
|
|
|
|
```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": [_iso_days_out(d) for d in (7, 14, 21, 28, 45)]
|
|
}
|
|
}
|
|
|
|
MOCK_SINGLE_EXPIRATION_RESPONSE = {
|
|
"expirations": {
|
|
"date": _iso_days_out(21)
|
|
}
|
|
}
|
|
|
|
MOCK_CHAIN_RESPONSE = {
|
|
"options": {
|
|
"option": [
|
|
{
|
|
"symbol": "AAPL260417C00170000",
|
|
"underlying": "AAPL",
|
|
"option_type": "call",
|
|
"strike": 170.0,
|
|
"expiration_date": _iso_days_out(20),
|
|
"bid": 5.10,
|
|
"ask": 5.30,
|
|
"last": 5.20,
|
|
"volume": 1234,
|
|
"open_interest": 5678,
|
|
"greeks": {
|
|
"delta": 0.55,
|
|
"gamma": 0.04,
|
|
"theta": -0.08,
|
|
"vega": 0.25,
|
|
"rho": 0.03,
|
|
"phi": -0.02,
|
|
"bid_iv": 0.28,
|
|
"mid_iv": 0.29,
|
|
"ask_iv": 0.30,
|
|
"smv_vol": 0.285,
|
|
"updated_at": "2026-04-01 12:00:00"
|
|
}
|
|
},
|
|
{
|
|
"symbol": "AAPL260417P00170000",
|
|
"underlying": "AAPL",
|
|
"option_type": "put",
|
|
"strike": 170.0,
|
|
"expiration_date": _iso_days_out(20),
|
|
"bid": 3.40,
|
|
"ask": 3.60,
|
|
"last": 3.50,
|
|
"volume": 890,
|
|
"open_interest": 2345,
|
|
"greeks": {
|
|
"delta": -0.45,
|
|
"gamma": 0.04,
|
|
"theta": -0.07,
|
|
"vega": 0.25,
|
|
"rho": -0.02,
|
|
"phi": 0.02,
|
|
"bid_iv": 0.27,
|
|
"mid_iv": 0.28,
|
|
"ask_iv": 0.29,
|
|
"smv_vol": 0.280,
|
|
"updated_at": "2026-04-01 12:00:00"
|
|
}
|
|
},
|
|
{
|
|
"symbol": "AAPL260417C00175000",
|
|
"underlying": "AAPL",
|
|
"option_type": "call",
|
|
"strike": 175.0,
|
|
"expiration_date": _iso_days_out(20),
|
|
"bid": 2.80,
|
|
"ask": 3.00,
|
|
"last": 2.90,
|
|
"volume": 567,
|
|
"open_interest": 1234,
|
|
"greeks": {
|
|
"delta": 0.40,
|
|
"gamma": 0.05,
|
|
"theta": -0.09,
|
|
"vega": 0.24,
|
|
"rho": 0.02,
|
|
"phi": -0.01,
|
|
"bid_iv": 0.30,
|
|
"mid_iv": 0.31,
|
|
"ask_iv": 0.32,
|
|
"smv_vol": 0.305,
|
|
"updated_at": "2026-04-01 12:00:00"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
MOCK_CHAIN_NO_GREEKS_RESPONSE = {
|
|
"options": {
|
|
"option": [
|
|
{
|
|
"symbol": "AAPL260417C00170000",
|
|
"underlying": "AAPL",
|
|
"option_type": "call",
|
|
"strike": 170.0,
|
|
"expiration_date": _iso_days_out(20),
|
|
"bid": 5.10,
|
|
"ask": 5.30,
|
|
"last": 5.20,
|
|
"volume": 1234,
|
|
"open_interest": 5678,
|
|
"greeks": None
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
MOCK_SINGLE_CONTRACT_RESPONSE = {
|
|
"options": {
|
|
"option": {
|
|
"symbol": "AAPL260417C00170000",
|
|
"underlying": "AAPL",
|
|
"option_type": "call",
|
|
"strike": 170.0,
|
|
"expiration_date": _iso_days_out(20),
|
|
"bid": 5.10,
|
|
"ask": 5.30,
|
|
"last": 5.20,
|
|
"volume": 1234,
|
|
"open_interest": 5678,
|
|
"greeks": {
|
|
"delta": 0.55, "gamma": 0.04, "theta": -0.08,
|
|
"vega": 0.25, "rho": 0.03, "phi": -0.02,
|
|
"bid_iv": 0.28, "mid_iv": 0.29, "ask_iv": 0.30,
|
|
"smv_vol": 0.285, "updated_at": "2026-04-01 12:00:00"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**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.
|
|
|
|
2. `TestGetOptionsChain` (DATA-01): patch `make_tradier_request_with_retry` to return mock expirations then mock chain. Call `get_options_chain_structured("AAPL")`. Assert `.underlying == "AAPL"`. Assert `len(.contracts) == 3`. Assert `.contracts[0].bid == 5.10`. Assert `.contracts[0].volume == 1234`. Assert `.contracts[0].open_interest == 5678`. Test single-contract normalization with `MOCK_SINGLE_CONTRACT_RESPONSE`.
|
|
|
|
3. `TestGreeksPresent` (DATA-03): use contract from mock chain response. Assert `contract.delta == 0.55`. Assert `contract.gamma == 0.04`. Assert `contract.theta == -0.08`. Assert `contract.vega == 0.25`. Assert `contract.rho == 0.03`. Assert `contract.greeks_updated_at == "2026-04-01 12:00:00"`.
|
|
|
|
4. `TestGreeksAbsent` (Pitfall 1): use `MOCK_CHAIN_NO_GREEKS_RESPONSE`. Assert `contract.delta is None`. Assert `contract.gamma is None`. Assert no exception raised.
|
|
|
|
5. `TestIVPresent` (DATA-04): use contract from mock chain. Assert `contract.bid_iv == 0.28`. Assert `contract.mid_iv == 0.29`. Assert `contract.ask_iv == 0.30`. Assert `contract.smv_vol == 0.285`.
|
|
|
|
6. `TestDTEFilter` (DATA-05): create OptionsChain with contracts at various known expiration_dates. Call `chain.filter_by_dte(10, 30)`. Assert only contracts within 10-30 DTE remain. Assert returned OptionsChain.expirations matches filtered contracts.
|
|
|
|
7. `TestVendorRegistration` (DATA-08): import TOOLS_CATEGORIES, VENDOR_LIST, VENDOR_METHODS from interface. Assert `"tradier" in VENDOR_LIST`. Assert `"options_chain" in TOOLS_CATEGORIES`. Assert `"get_options_chain" in VENDOR_METHODS`. Assert `"tradier" in VENDOR_METHODS["get_options_chain"]`.
|
|
|
|
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`: 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"`.
|
|
|
|
**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/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/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>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `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>
|
|
|
|
<success_criteria>
|
|
- Tradier fully registered in vendor routing (VENDOR_LIST, TOOLS_CATEGORIES, VENDOR_METHODS, DEFAULT_CONFIG)
|
|
- route_to_vendor catches both AlphaVantageRateLimitError and TradierRateLimitError
|
|
- Two @tool functions exist in options_tools.py following core_stock_tools.py pattern
|
|
- .env.example documents TRADIER_API_KEY and TRADIER_SANDBOX
|
|
- All unit tests pass covering DATA-01 through DATA-05 and DATA-08
|
|
- No real API calls in tests (all mocked)
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/01-tradier-data-layer/01-02-SUMMARY.md`
|
|
</output>
|