Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016DvdRZkyCy9NJpHYDdPqmS
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
"""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}"
|
||
)
|