OptionStrat AI integrates multiple data sources to provide comprehensive market data for options pricing and analysis. The system implements a hybrid strategy prioritizing data quality and availability.
Alpha Vantage provides premium data with calculated Greeks and historical options.
Alpha Vantage Capabilities
Available Data:
Complete option chains
Pre-calculated Greeks (Delta, Gamma, Theta, Vega)
Historical options data
Implied Volatility surfaces
Better data quality overall
Advantages:
✅ High-quality Greeks
✅ Historical options data
✅ More reliable IV
✅ Professional-grade data
Limitations:
⚠️ Requires API key
⚠️ Rate limits (5 calls/min on free tier)
⚠️ More complex data structures
⚠️ Can be slower
Setup:
# Get free API key from: https://www.alphavantage.co/support/#api-key# Add to .env fileALPHA_VANTAGE_KEY=your_key_here# Install librarypip install alpha-vantage
^IRX (13-week Treasury Bill) provides the risk-free rate for pricing models.From backend/app/data/data_manager.py:127-149:
def get_risk_free_rate(self) -> float: """ Obtiene la Tasa Libre de Riesgo usando el bono a 3 meses (^IRX). Retorna float (ej: 0.052 para 5.2%). """ try: # ^IRX is the 13-week T-Bill yield index t = yf.Ticker("^IRX") # Try fast_info first rate = t.fast_info.last_price if rate is None or str(rate) == 'nan': hist = t.history(period='1d') if not hist.empty: rate = hist['Close'].iloc[-1] else: return 0.05 # Fallback 5% # Value comes as percentage (e.g., 4.5), convert to decimal return float(rate) / 100.0 except Exception as e: logger.warning(f"Could not get risk-free rate, using 5%: {e}") return 0.05
Why ^IRX?
3-month T-Bill is the standard risk-free benchmark
Matches typical option expiration timeframes
Updated continuously during trading hours
Widely accepted in academic and professional settings
class OptionsDataManager: """ Gestor de datos de Opciones (YFinance + Alpha Vantage). Encargado de descargar cadenas de opciones, precios spot y manejar Rate Limits. """ def __init__(self, delay: float = 1.5): self.delay = delay self.av_api_key = os.getenv("ALPHA_VANTAGE_KEY") self.av_client = AVOptions(key=self.av_api_key) if ( self.av_api_key and AVOptions) else None if not self.av_client: logger.warning("Alpha Vantage not configured. Using YFinance only.")
def _safe_request(self, func, *args, **kwargs): """Envoltorio para manejar Rate Limits y pausas.""" time.sleep(self.delay) try: return func(*args, **kwargs) except Exception as e: msg = str(e) if "Too Many Requests" in msg or "429" in msg: logger.critical(f"RATE LIMIT (429) detected. Stopping execution.") raise ConnectionError("RATE_LIMIT_HIT") logger.error(f"Error in yfinance request: {e}") raise e
Rate Limit Handling:
1.5-second delay between requests (default)
429 detection: Immediate stop on rate limit
ConnectionError: Propagates to frontend for user notification
No retry logic: Prevents cascading failures
In production, implement exponential backoff and request queuing.
Since vendor Greeks are often unreliable, OptionStrat AI recalculates all Greeks using its own BSM implementation.From backend/app/data/data_manager.py:361-412:
# Get spot price and risk-free ratespot = self.get_spot_price(ticker)r = self.get_risk_free_rate() # Dynamic from ^IRX# FORCE CALCULATION: yfinance Greeks are often bad/incompleteneed_calc = Trueif need_calc: deltas, gammas, thetas, vegas = [], [], [], [] for idx, row in df.iterrows(): try: T_years = max(row['dte'], 0.5) / 365.0 # Min 0.5 days sigma = row['impliedVolatility'] if sigma <= 0 or T_years <= 0: # Invalid parameters deltas.append(0.0); gammas.append(0.0) thetas.append(0.0); vegas.append(0.0) continue # Calculate Greeks using our BSM implementation greeks = bsm_greeks( S=spot, K=row['strike'], T=T_years, r=r, # Current risk-free rate sigma=sigma, # Market implied vol q=0.0, # Dividend yield (set to 0 for simplicity) kind=row['type'] ) deltas.append(greeks.get('delta', 0.0)) gammas.append(greeks.get('gamma', 0.0)) thetas.append(greeks.get('theta', 0.0)) vegas.append(greeks.get('vega', 0.0)) except Exception: deltas.append(0.0); gammas.append(0.0) thetas.append(0.0); vegas.append(0.0) # Update dataframe df['delta'] = deltas df['gamma'] = gammas df['theta'] = thetas df['vega'] = vegas
Benefits of Recalculation:
Consistency: All Greeks use same r, q, and pricing model
Completeness: No missing Greeks
Current: Uses real-time risk-free rate from ^IRX
Customizable: Easy to adjust dividend yield or model
Historical volatility provides context for evaluating implied volatility levels.From backend/app/data/data_manager.py:68-125:
View Complete HV Implementation
def get_historical_volatility(self, ticker: str, days: int = 30) -> Dict[str, float]: """ Calcula Estadísticas de Volatilidad Histórica (HV) Anualizada. Retorna un diccionario con: - current_hv: Volatilidad de los últimos 'days' - mean_hv: Media de la HV de 30 días en el último año - std_hv: Desviación estándar de la HV - min_hv: Mínima HV en el último año - max_hv: Máxima HV en el último año - percentile: Percentil de la HV actual (0-100) """ try: # Download 1 year of historical data for statistical context t = yf.Ticker(ticker) end_date = datetime.now() start_date = end_date - timedelta(days=365) hist = t.history(start=start_date, end=end_date) if hist.empty or len(hist) < 200: return {} # Calculate log returns hist['LogReturn'] = np.log(hist['Close'] / hist['Close'].shift(1)) # Calculate rolling 30-day HV (annualized) window = 30 # std * sqrt(252) to annualize hist['RollingHV'] = hist['LogReturn'].rolling(window=window).std() * np.sqrt(252) # Clean initial NaNs rolling_series = hist['RollingHV'].dropna() if rolling_series.empty: return {} current_hv = rolling_series.iloc[-1] mean_hv = rolling_series.mean() std_hv = rolling_series.std() min_hv = rolling_series.min() max_hv = rolling_series.max() # Calculate percentile of current HV percentile = (rolling_series < current_hv).mean() * 100 return { "current_hv": float(current_hv), "mean_hv": float(mean_hv), "std_hv": float(std_hv), "min_hv": float(min_hv), "max_hv": float(max_hv), "percentile": float(percentile) } except Exception as e: logger.error(f"Error calculating HV Stats for {ticker}: {e}") return {}
def get_spot_price(self, ticker: str) -> float: """Obtiene el precio actual del activo subyacente.""" try: t = yf.Ticker(ticker) # Try fast_info first (faster) price = t.fast_info.last_price if price is None: # Fallback to history history = t.history(period="1d") if not history.empty: price = history["Close"].iloc[-1] return price except Exception as e: logger.error(f"Could not get spot price for {ticker}: {e}") return 0.0
Fast Info vs History:
fast_info.last_price: Real-time, lightweight API call
history(period="1d"): Fallback when fast_info unavailable