Spaces:
Runtime error
Runtime error
| 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, | |
| ) | |