feat(tools): run_python, web search/fetch, market data, options, portfolio

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DvdRZkyCy9NJpHYDdPqmS
This commit is contained in:
Aryan SinghandClaude Opus 4.8 committed 2026-07-01 16:29:29 -05:00
1 parent f41cf75d8a
commit 63085113ed
6 files changed
+552

No files matched your search

+104
View File
@@ -0,0 +1,104 @@
"""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}"
)