Files
trading-agent/tools/options.py
T

105 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Options data tools (Phase 6a — single-leg long calls/puts).
get_option_chain lists near-the-money contracts near a target DTE with live premiums; the
agent copies a contract_symbol into its pick. get_option_quote prices one contract.
"""
from __future__ import annotations
from datetime import date, timedelta
from functools import lru_cache
from langchain_core.tools import tool
from config.settings import get_settings
from tools.market_data import latest_price
@lru_cache
def _opt_data():
from alpaca.data.historical.option import OptionHistoricalDataClient
key, secret = get_settings().alpaca_data_creds()
return OptionHistoricalDataClient(key, secret)
@lru_cache
def _opt_trading():
from alpaca.trading.client import TradingClient
s = get_settings()
key, secret = s.alpaca_data_creds()
return TradingClient(key, secret, paper=s.alpaca_paper)
def option_premium(contract_symbol: str) -> float | None:
"""Mid premium for an option contract (per share; ×100 = contract cost). None if unavailable."""
from alpaca.data.requests import OptionLatestQuoteRequest
try:
q = _opt_data().get_option_latest_quote(
OptionLatestQuoteRequest(symbol_or_symbols=contract_symbol)
)
c = q[contract_symbol]
bid, ask = float(c.bid_price or 0), float(c.ask_price or 0)
if bid and ask:
return round((bid + ask) / 2, 2)
return round(ask or bid, 2) or None
except Exception:
return None
@tool
def get_option_chain(underlying: str, right: str = "call", target_dte: int = 35) -> str:
"""List near-the-money option contracts for an underlying stock.
right = 'call' (bullish) or 'put' (bearish). target_dte = desired days to expiration
(~30-45 is typical). Returns contract symbols + strike + expiry + mid premium. Copy a
contract_symbol into your pick to trade it.
"""
from alpaca.trading.enums import AssetStatus, ContractType
from alpaca.trading.requests import GetOptionContractsRequest
underlying = underlying.strip().upper()
px = latest_price(underlying)
if not px:
return f"ERROR: no price for {underlying}"
ct = ContractType.CALL if right.lower().startswith("c") else ContractType.PUT
today = date.today()
try:
req = GetOptionContractsRequest(
underlying_symbols=[underlying],
type=ct,
status=AssetStatus.ACTIVE,
expiration_date_gte=today + timedelta(days=max(target_dte - 15, 1)),
expiration_date_lte=today + timedelta(days=target_dte + 20),
strike_price_gte=str(round(px * 0.85, 2)),
strike_price_lte=str(round(px * 1.15, 2)),
limit=60,
)
contracts = _opt_trading().get_option_contracts(req).option_contracts
except Exception as e:
return f"ERROR: chain fetch failed: {e}"
if not contracts:
return f"No {right} contracts for {underlying} near {target_dte} DTE."
# nearest expiry first, then nearest strike to spot
contracts.sort(key=lambda c: (c.expiration_date, abs(float(c.strike_price) - px)))
out = [f"{underlying} ~${px:.2f}: {right}s ~{target_dte}DTE (copy a contract_symbol):"]
for c in contracts[:10]:
prem = option_premium(c.symbol)
prem_s = f"${prem:.2f}" if prem else "n/a"
out.append(
f" {c.symbol} strike ${float(c.strike_price):.1f} exp {c.expiration_date} mid {prem_s}"
)
return "\n".join(out)
@tool
def get_option_quote(contract_symbol: str) -> str:
"""Latest mid premium for an option contract symbol (OCC format, from get_option_chain)."""
prem = option_premium(contract_symbol.strip().upper())
return f"{contract_symbol}: mid ${prem:.2f} (x100 = ${prem * 100:.0f}/contract)" if prem else (
f"ERROR: no quote for {contract_symbol}"
)