mathidot's picture
build option trading agent modules
8f1601b
Raw
History Blame Contribute Delete
4.84 kB
from __future__ import annotations
from datetime import date, datetime
from typing import Any
import pandas as pd
import yfinance as yf
from .schemas import OptionChain, OptionContract, UnderlyingQuote
def none_if_nan(value: Any) -> Any:
if pd.isna(value):
return None
return value
def to_float(value: Any) -> float | None:
value = none_if_nan(value)
return float(value) if value is not None else None
def to_int(value: Any) -> int | None:
value = none_if_nan(value)
return int(value) if value is not None else None
def get_price_history(
symbol: str,
period: str = "1y",
interval: str = "1d",
start: str | None = None,
end: str | None = None,
) -> pd.DataFrame:
ticker = yf.Ticker(symbol.strip().upper())
return ticker.history(period=period, interval=interval, start=start, end=end)
def get_current_quote(symbol: str) -> UnderlyingQuote:
symbol = symbol.strip().upper()
ticker = yf.Ticker(symbol)
data = ticker.history(period="1d", interval="1m")
if not data.empty:
latest_row = data.iloc[-1]
return UnderlyingQuote(
symbol=symbol,
current_price=float(latest_row["Close"]),
open=float(latest_row["Open"]),
high=float(latest_row["High"]),
low=float(latest_row["Low"]),
volume=int(latest_row["Volume"]),
timestamp=str(data.index[-1]),
data_type="intraday_1m",
)
info = ticker.info
current_price = (
info.get("regularMarketPrice")
or info.get("previousClose")
or info.get("ask")
or info.get("bid")
)
return UnderlyingQuote(
symbol=symbol,
current_price=float(current_price) if current_price else None,
open=to_float(info.get("regularMarketOpen") or info.get("open")),
high=to_float(info.get("regularMarketDayHigh") or info.get("dayHigh")),
low=to_float(info.get("regularMarketDayLow") or info.get("dayLow")),
volume=to_int(info.get("regularMarketVolume") or info.get("volume")),
timestamp=datetime.utcnow().isoformat(timespec="seconds"),
data_type="cached_info",
short_name=info.get("shortName", ""),
)
def list_option_expirations(symbol: str) -> list[str]:
ticker = yf.Ticker(symbol.strip().upper())
return list(ticker.options or [])
def liquidity_warnings(row: pd.Series) -> list[str]:
warnings = []
bid = to_float(row.get("bid"))
ask = to_float(row.get("ask"))
volume = to_int(row.get("volume")) or 0
open_interest = to_int(row.get("openInterest")) or 0
if bid is None or ask is None or bid <= 0 or ask <= 0:
warnings.append("missing_or_zero_bid_ask")
elif ask > 0 and (ask - bid) / ask > 0.25:
warnings.append("wide_bid_ask_spread")
if volume <= 0:
warnings.append("zero_volume")
if open_interest <= 0:
warnings.append("zero_open_interest")
return warnings
def row_to_contract(row: pd.Series, option_type: str, expiration: str) -> OptionContract:
bid = to_float(row.get("bid"))
ask = to_float(row.get("ask"))
mid = (bid + ask) / 2 if bid is not None and ask is not None and bid > 0 and ask > 0 else None
days_to_expiration = max((date.fromisoformat(expiration) - date.today()).days, 0)
return OptionContract(
contract_symbol=str(row.get("contractSymbol", "")),
option_type=option_type,
expiration=expiration,
strike=float(row.get("strike")),
bid=bid,
ask=ask,
mid=mid,
last_price=to_float(row.get("lastPrice")),
volume=to_int(row.get("volume")),
open_interest=to_int(row.get("openInterest")),
implied_volatility=to_float(row.get("impliedVolatility")),
in_the_money=bool(row.get("inTheMoney", False)),
days_to_expiration=days_to_expiration,
liquidity_warnings=liquidity_warnings(row),
)
def get_option_chain(symbol: str, expiration: str | None = None) -> OptionChain:
symbol = symbol.strip().upper()
ticker = yf.Ticker(symbol)
expirations = list(ticker.options or [])
if not expirations:
raise ValueError(f"No option expirations found for {symbol}.")
expiration = expiration or expirations[0]
if expiration not in expirations:
raise ValueError(f"Expiration {expiration} is not available for {symbol}.")
chain = ticker.option_chain(expiration)
quote = get_current_quote(symbol)
calls = [row_to_contract(row, "call", expiration) for _, row in chain.calls.iterrows()]
puts = [row_to_contract(row, "put", expiration) for _, row in chain.puts.iterrows()]
return OptionChain(
symbol=symbol,
expiration=expiration,
underlying_price=quote.current_price,
calls=calls,
puts=puts,
)