SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
9.2 KB · 252 lines python
Raw Blame History
1#!/usr/bin/env python32# =============================================================================3#  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4# -----------------------------------------------------------------------------5#  File:      server/services/optionsPricingService.py6#7#  Author:    Simon-Pierre Boucher8#  Contact:   contact@spboucher.ai9#  Website:   https://www.spboucher.ai10#  Demo:      https://www.vquant.ai11#  License:   MIT (see LICENSE)12#13#  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14# =============================================================================1516"""17Black-Scholes Options Pricing Model18Calculates theoretical option prices and Greeks for European options19Supports direct API integration for fetching real market data20"""2122import sys23import json24import math25import os26from scipy import stats2728# Import our FMP client29sys.path.insert(0, os.path.dirname(__file__))30from fmpClient import get_historical_prices, get_stock_quote, calculate_historical_volatility3132def black_scholes_call(S, K, T, r, sigma):33    """Calculate Black-Scholes call option price"""34    d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))35    d2 = d1 - sigma * math.sqrt(T)3637    call_price = S * stats.norm.cdf(d1) - K * math.exp(-r * T) * stats.norm.cdf(d2)38    return call_price3940def black_scholes_put(S, K, T, r, sigma):41    """Calculate Black-Scholes put option price"""42    d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))43    d2 = d1 - sigma * math.sqrt(T)4445    put_price = K * math.exp(-r * T) * stats.norm.cdf(-d2) - S * stats.norm.cdf(-d1)46    return put_price4748def calculate_greeks(S, K, T, r, sigma, option_type):49    """Calculate option Greeks: Delta, Gamma, Theta, Vega, Rho"""50    d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))51    d2 = d1 - sigma * math.sqrt(T)5253    # Delta54    if option_type == "call":55        delta = stats.norm.cdf(d1)56    else:  # put57        delta = stats.norm.cdf(d1) - 15859    # Gamma (same for call and put)60    gamma = stats.norm.pdf(d1) / (S * sigma * math.sqrt(T))6162    # Vega (same for call and put)63    vega = S * stats.norm.pdf(d1) * math.sqrt(T) / 100  # divided by 100 for 1% change6465    # Theta66    if option_type == "call":67        theta = (-(S * stats.norm.pdf(d1) * sigma) / (2 * math.sqrt(T))68                 - r * K * math.exp(-r * T) * stats.norm.cdf(d2)) / 36569    else:  # put70        theta = (-(S * stats.norm.pdf(d1) * sigma) / (2 * math.sqrt(T))71                 + r * K * math.exp(-r * T) * stats.norm.cdf(-d2)) / 3657273    # Rho74    if option_type == "call":75        rho = K * T * math.exp(-r * T) * stats.norm.cdf(d2) / 100  # divided by 100 for 1% change76    else:  # put77        rho = -K * T * math.exp(-r * T) * stats.norm.cdf(-d2) / 1007879    return {80        "delta": round(delta, 4),81        "gamma": round(gamma, 4),82        "theta": round(theta, 4),83        "vega": round(vega, 4),84        "rho": round(rho, 4)85    }8687def calculate_implied_volatility(option_price, S, K, T, r, option_type, max_iterations=100, tolerance=0.0001):88    """Calculate implied volatility using Newton-Raphson method"""89    sigma = 0.3  # Initial guess9091    for i in range(max_iterations):92        if option_type == "call":93            price = black_scholes_call(S, K, T, r, sigma)94        else:95            price = black_scholes_put(S, K, T, r, sigma)9697        # Vega for Newton-Raphson98        d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * math.sqrt(T))99        vega = S * stats.norm.pdf(d1) * math.sqrt(T)100101        if abs(vega) < 1e-10:102            break103104        diff = option_price - price105        if abs(diff) < tolerance:106            return round(sigma, 4)107108        sigma = sigma + diff / vega109110        # Ensure sigma stays positive111        if sigma <= 0:112            sigma = 0.01113114    return round(sigma, 4)115116def main():117    try:118        # Read input from stdin119        input_data = json.loads(sys.stdin.read())120121        # Check if symbol is provided (new API mode)122        symbol = input_data.get('symbol')123124        if symbol:125            # DIRECT API MODE - Fetch data from FMP126            print(f"Fetching market data for {symbol} from FMP API...", file=sys.stderr)127128            # Get current stock quote for current price129            quote = get_stock_quote(symbol)130            S = quote['price']131            print(f"Current stock price: ${S:.2f}", file=sys.stderr)132133            # Get historical prices to calculate volatility134            hist_data = get_historical_prices(symbol)135            closing_prices = [h['close'] for h in reversed(hist_data['historical'])]136137            # Calculate historical volatility (annualized)138            sigma_provided = input_data.get('volatility')139            if sigma_provided:140                sigma = sigma_provided141                print(f"Using provided volatility: {sigma*100:.2f}%", file=sys.stderr)142            else:143                # Use 30 days of history for volatility calculation144                recent_prices = closing_prices[-30:] if len(closing_prices) >= 30 else closing_prices145                sigma = calculate_historical_volatility(recent_prices, annualize=True)146                print(f"Calculated 30-day historical volatility: {sigma*100:.2f}%", file=sys.stderr)147148            # Extract other required parameters149            K = input_data.get('strike_price')150            T = input_data.get('time_to_maturity')151            r = input_data.get('risk_free_rate', 0.05)152            option_type = input_data.get('option_type', 'call').lower()153154            # Validate required inputs155            if K is None or T is None:156                raise ValueError("strike_price and time_to_maturity are required")157        else:158            # MANUAL MODE - Use provided parameters159            S = input_data.get('stock_price')160            K = input_data.get('strike_price')161            T = input_data.get('time_to_maturity')162            r = input_data.get('risk_free_rate', 0.05)163            sigma = input_data.get('volatility')164            option_type = input_data.get('option_type', 'call').lower()165166            # Validate inputs167            if S is None or K is None or T is None:168                raise ValueError("Either provide 'symbol' OR all of: stock_price, strike_price, time_to_maturity")169170            # If volatility not provided, use default171            if sigma is None:172                sigma = 0.3  # 30% default volatility173                print("Using default volatility: 30%", file=sys.stderr)174175        # Common validation for all modes176        if S <= 0 or K <= 0 or T <= 0:177            raise ValueError("stock_price, strike_price, and time_to_maturity must be positive")178179        if option_type not in ['call', 'put']:180            raise ValueError("option_type must be 'call' or 'put'")181182        if sigma <= 0:183            raise ValueError("volatility must be positive")184185        # Calculate option prices186        call_price = black_scholes_call(S, K, T, r, sigma)187        put_price = black_scholes_put(S, K, T, r, sigma)188189        # Calculate Greeks for both call and put190        call_greeks = calculate_greeks(S, K, T, r, sigma, "call")191        put_greeks = calculate_greeks(S, K, T, r, sigma, "put")192193        # Calculate intrinsic and time value194        call_intrinsic = max(0, S - K)195        put_intrinsic = max(0, K - S)196        call_time_value = call_price - call_intrinsic197        put_time_value = put_price - put_intrinsic198199        # Calculate moneyness200        if S > K:201            moneyness = "ITM" if option_type == "call" else "OTM"202        elif S < K:203            moneyness = "OTM" if option_type == "call" else "ITM"204        else:205            moneyness = "ATM"206207        # Prepare result208        result = {209            "success": True,210            "parameters": {211                "stock_price": round(S, 2),212                "strike_price": round(K, 2),213                "time_to_maturity": round(T, 4),214                "risk_free_rate": round(r, 4),215                "volatility": round(sigma, 4),216                "option_type": option_type217            },218            "call_option": {219                "price": round(call_price, 4),220                "intrinsic_value": round(call_intrinsic, 4),221                "time_value": round(call_time_value, 4),222                "greeks": call_greeks,223                "moneyness": "ITM" if S > K else ("ATM" if S == K else "OTM")224            },225            "put_option": {226                "price": round(put_price, 4),227                "intrinsic_value": round(put_intrinsic, 4),228                "time_value": round(put_time_value, 4),229                "greeks": put_greeks,230                "moneyness": "ITM" if S < K else ("ATM" if S == K else "OTM")231            },232            "parity_check": {233                "call_minus_put": round(call_price - put_price, 4),234                "stock_minus_pv_strike": round(S - K * math.exp(-r * T), 4),235                "parity_holds": bool(abs((call_price - put_price) - (S - K * math.exp(-r * T))) < 0.01)236            }237        }238239        # Output result as JSON240        print(json.dumps(result))241242    except Exception as e:243        error_result = {244            "success": False,245            "error": str(e)246        }247        print(json.dumps(error_result))248        sys.exit(1)249250if __name__ == "__main__":251    main()252