TradingAgents/tradingagents/dataflows/tradier_api.py

187 lines
7.6 KiB
Python

"""
Tradier API - Options Activity Detection
Detects unusual options activity indicating smart money positioning
"""
import os
from typing import Annotated, List
import requests
from tradingagents.config import config
from tradingagents.dataflows.market_data_utils import format_markdown_table
def get_unusual_options_activity(
tickers: Annotated[List[str], "List of ticker symbols to analyze"] = None,
date: Annotated[str, "Analysis date in yyyy-mm-dd format"] = None,
min_volume_multiple: Annotated[float, "Minimum options volume multiple"] = 2.0,
top_n: Annotated[int, "Number of top results to return"] = 20,
) -> str:
"""
Detect unusual options activity for given tickers (confirmation signal).
This function is designed as a CONFIRMATION tool - it analyzes options activity
for candidates found by other discovery methods (unusual volume, analyst changes, etc.)
Unusual options volume is a leading indicator of price moves - institutions
positioning before catalysts.
Args:
tickers: List of ticker symbols to analyze (if None, returns error message)
date: Analysis date in yyyy-mm-dd format
min_volume_multiple: Minimum volume multiple vs 20-day average
top_n: Number of top results to return
Returns:
Formatted markdown report of unusual options activity
"""
try:
api_key = config.validate_key("tradier_api_key", "Tradier")
except ValueError as e:
return f"Error: {str(e)}"
if not tickers or len(tickers) == 0:
return "Error: No tickers provided. This function analyzes options activity for specific tickers found by other discovery methods."
# Tradier API base URLs
# Use sandbox for testing: https://sandbox.tradier.com
# Use production: https://api.tradier.com
base_url = os.getenv("TRADIER_BASE_URL", "https://sandbox.tradier.com")
headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
try:
# Strategy: Analyze options activity for provided tickers
# This confirms smart money positioning for candidates found by other methods
unusual_activity = []
for ticker in tickers:
try:
# Get options chain
options_url = f"{base_url}/v1/markets/options/chains"
params = {
"symbol": ticker,
"expiration": "", # Will get nearest expiration
"greeks": "true",
}
response = requests.get(options_url, headers=headers, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if "options" in data and "option" in data["options"]:
options = data["options"]["option"]
# Aggregate call and put volume
total_call_volume = 0
total_put_volume = 0
total_call_oi = 0
total_put_oi = 0
for option in options[:50]: # Check first 50 options
option_type = option.get("option_type", "")
volume = int(option.get("volume", 0))
open_interest = int(option.get("open_interest", 0))
if option_type == "call":
total_call_volume += volume
total_call_oi += open_interest
elif option_type == "put":
total_put_volume += volume
total_put_oi += open_interest
# Calculate metrics
total_volume = total_call_volume + total_put_volume
if total_volume > 10000: # Significant volume threshold
put_call_ratio = (
total_put_volume / total_call_volume if total_call_volume > 0 else 0
)
# Unusual signals:
# - Very low P/C ratio (<0.7) = Bullish (heavy call buying)
# - Very high P/C ratio (>1.5) = Bearish (heavy put buying)
# - High volume (>50k) = Strong conviction
signal = "neutral"
if put_call_ratio < 0.7:
signal = "bullish_calls"
elif put_call_ratio > 1.5:
signal = "bearish_puts"
elif total_volume > 50000:
signal = "high_volume"
unusual_activity.append(
{
"ticker": ticker,
"total_volume": total_volume,
"call_volume": total_call_volume,
"put_volume": total_put_volume,
"put_call_ratio": put_call_ratio,
"signal": signal,
"call_oi": total_call_oi,
"put_oi": total_put_oi,
}
)
except Exception:
# Skip this ticker if there's an error
continue
# Sort by total volume (highest first)
sorted_activity = sorted(unusual_activity, key=lambda x: x["total_volume"], reverse=True)[
:top_n
]
# Format output
if not sorted_activity:
return "No unusual options activity detected"
report = f"# Unusual Options Activity - {date or 'Latest'}\n\n"
report += (
"**Criteria**: P/C Ratio extremes (<0.7 bullish, >1.5 bearish), High volume (>50k)\n\n"
)
report += f"**Found**: {len(sorted_activity)} stocks with notable options activity\n\n"
report += "## Top Options Activity\n\n"
report += format_markdown_table(
["Ticker", "Total Volume", "Call Vol", "Put Vol", "P/C Ratio", "Signal"],
[
[
a["ticker"],
f"{a['total_volume']:,}",
f"{a['call_volume']:,}",
f"{a['put_volume']:,}",
f"{a['put_call_ratio']:.2f}",
a["signal"],
]
for a in sorted_activity
],
)
report += "\n\n## Signal Definitions\n\n"
report += "- **bullish_calls**: P/C ratio <0.7 - Heavy call buying, bullish positioning\n"
report += "- **bearish_puts**: P/C ratio >1.5 - Heavy put buying, bearish positioning\n"
report += "- **high_volume**: Exceptional volume (>50k) - Strong conviction move\n"
report += "- **neutral**: Balanced activity\n\n"
report += "**Note**: Options activity is a leading indicator. Smart money often positions 1-2 weeks before catalysts.\n"
return report
except requests.exceptions.RequestException as e:
return f"Error fetching options activity from Tradier: {str(e)}"
except Exception as e:
return f"Unexpected error in options activity detection: {str(e)}"
def get_tradier_unusual_options(
tickers: List[str] = None,
date: str = None,
min_volume_multiple: float = 2.0,
top_n: int = 20,
) -> str:
"""Alias for get_unusual_options_activity to match registry naming convention"""
return get_unusual_options_activity(tickers, date, min_volume_multiple, top_n)