From b406a53f68d3a7b25993df66ed8f069c265b8655 Mon Sep 17 00:00:00 2001 From: MuslemRahimi Date: Fri, 25 Oct 2024 13:42:02 +0200 Subject: [PATCH] change quarter date for hedge funds --- app/create_institute_db.py | 3 +- app/create_stock_db.py | 2 +- app/main.py | 4 +- app/support.py | 227 ++++++++++++++++++++----------------- 4 files changed, 126 insertions(+), 110 deletions(-) diff --git a/app/create_institute_db.py b/app/create_institute_db.py index de113b9..dc3452c 100755 --- a/app/create_institute_db.py +++ b/app/create_institute_db.py @@ -62,7 +62,8 @@ crypto_con.close() load_dotenv() api_key = os.getenv('FMP_API_KEY') -quarter_date = '2024-09-30' +quarter_date = '2024-06-30' + if os.path.exists("backup_db/institute.db"): diff --git a/app/create_stock_db.py b/app/create_stock_db.py index b294879..f16c710 100755 --- a/app/create_stock_db.py +++ b/app/create_stock_db.py @@ -27,7 +27,7 @@ warnings.filterwarnings("ignore", category=RuntimeWarning, message="invalid valu start_date = datetime(2015, 1, 1).strftime("%Y-%m-%d") end_date = datetime.today().strftime("%Y-%m-%d") -quarter_date = '2024-09-30' +quarter_date = '2024-06-30' if os.path.exists("backup_db/stocks.db"): diff --git a/app/main.py b/app/main.py index 4c21bf4..c80b34a 100755 --- a/app/main.py +++ b/app/main.py @@ -3937,7 +3937,7 @@ async def get_next_earnings(data:TickerData, api_key: str = Security(get_api_key res = {} redis_client.set(cache_key, orjson.dumps(res)) - redis_client.expire(cache_key,3600*3600) + redis_client.expire(cache_key,5*60) return res @@ -3955,7 +3955,7 @@ async def get_surprise_earnings(data:TickerData, api_key: str = Security(get_api res = {} redis_client.set(cache_key, orjson.dumps(res)) - redis_client.expire(cache_key,15*60) + redis_client.expire(cache_key,5*60) return res diff --git a/app/support.py b/app/support.py index 16560b3..1d762a9 100644 --- a/app/support.py +++ b/app/support.py @@ -2,6 +2,7 @@ import pandas as pd import numpy as np import ujson import matplotlib.pyplot as plt +from matplotlib.colors import LinearSegmentedColormap from scipy.stats import norm from datetime import datetime, date, timedelta from benzinga import financial_data @@ -43,7 +44,7 @@ query_template = """ """ -ticker = 'SPY' +ticker = 'NVDA' end_date = date.today() start_date = end_date - timedelta(1) end_date_str = end_date.strftime('%Y-%m-%d') @@ -57,9 +58,6 @@ volatility = calculate_volatility(df_price) print(start_date, end_date) print('volatility', volatility) -stock_con.close() -etf_con.close() - def get_data(ticker): res_list = [] page = 0 @@ -89,32 +87,34 @@ print(len(ticker_data)) def calculate_option_greeks(S, K, T, r, sigma, option_type='CALL'): """ - Calculate option Greeks using Black-Scholes formula + Calculate option Greeks using Black-Scholes formula with improved accuracy S: Current stock price K: Strike price T: Time to expiration (in years) r: Risk-free rate sigma: Volatility """ - if T <= 0: + if T <= 0 or sigma <= 0 or S <= 0: return 0, 0 - d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T)) - #d2 = d1 - sigma * np.sqrt(T) - - if option_type == 'CALL': - delta = norm.cdf(d1) - #gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T)) - else: # PUT - delta = norm.cdf(d1) - 1 #-norm.cdf(-d1) - #gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T)) - gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T)) if S > 0 and sigma > 0 and np.sqrt(T) > 0 else 0 + try: + d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T)) + d2 = d1 - sigma * np.sqrt(T) - return delta, gamma + if option_type == 'CALL': + delta = norm.cdf(d1) + gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T)) + else: # PUT + delta = -norm.cdf(-d1) + gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T)) + + return delta, gamma + except: + return 0, 0 -def process_options_data(df): +def process_options_data_by_expiry(df): """ - Process options data and calculate DEX and GEX + Process options data with separate calculations for each expiration date """ # Convert data types df['strike_price'] = pd.to_numeric(df['strike_price']) @@ -123,14 +123,13 @@ def process_options_data(df): df['underlying_price'] = pd.to_numeric(df['underlying_price']) df['date_expiration'] = pd.to_datetime(df['date_expiration']) df['date'] = pd.to_datetime(df['date']) + df['price'] = pd.to_numeric(df['price']) # Calculate time to expiration in years df['T'] = (df['date_expiration'] - df['date']).dt.days / 365.0 - # Parameters for calculations - risk_free_rate = 0.05 # Current approximate risk-free rate - # Calculate historical volatility (or you could use implied volatility if available) - sigma = volatility #df['underlying_price'].pct_change().std() * np.sqrt(252) if len(df) > 30 else 0.3 + # Use current risk-free rate + risk_free_rate = 0.0525 # Calculate Greeks for each option greeks = df.apply(lambda row: calculate_option_greeks( @@ -138,114 +137,130 @@ def process_options_data(df): row['strike_price'], row['T'], risk_free_rate, - sigma, + volatility, row['put_call'] ), axis=1) df['delta'], df['gamma'] = zip(*greeks) - # Calculate DEX (Delta Exposure) and GEX (Gamma Exposure) - # Convert volume to float if it's not already - df['volume'] = df['volume'].astype(int) - df['open_interest'] = df['open_interest'].astype(float) - - # Get price per contract - df['price'] = pd.to_numeric(df['price']) - - # Calculate position values - contract_multiplier = 100 # Standard option contract multiplier - df['position_value'] = df['price'] * df['open_interest'] * contract_multiplier - # Calculate exposures - df['gex'] = df['gamma'] * df['volume'] * contract_multiplier #df['gamma'] * df['open_interest'] * df['volume'] * df['underlying_price'] - df['dex'] = df['delta'] * df['volume'] * contract_multiplier * df['underlying_price'] * 0.01 #df['delta'] * df['volume'] * df['underlying_price'] *0.01 + contract_multiplier = 100 + df['delta_exposure'] = (df['delta'] * df['volume'] * contract_multiplier * + df['underlying_price']) + + # Separate calls and puts + df['option_type'] = np.where(df['put_call'] == 'CALL', 'call', 'put') return df -def plot_option_exposure(df, current_price): +def plot_delta_exposure_by_expiry(df, current_price): """ - Create visualization of DEX and GEX profiles with focused y-axis + Create a visualization similar to the screenshot with delta exposure by expiration date """ plt.style.use('dark_background') - fig, ax = plt.subplots(figsize=(12, 8)) + fig, ax = plt.subplots(figsize=(15, 12)) - # Aggregate exposures by strike price - dex_by_strike = df.groupby('strike_price')['dex'].sum().reset_index() - gex_by_strike = df.groupby('strike_price')['gex'].sum().reset_index() + # Create custom colormap for calls and puts + call_colors = ['#90EE90', '#32CD32', '#228B22'] # Light to dark green + put_colors = ['#FFB6C1', '#DC143C', '#8B0000'] # Light to dark red - # Filter out strikes with no significant exposure - significant_exposure = abs(dex_by_strike['dex']) > abs(dex_by_strike['dex']).max() * 0.01 - min_strike = dex_by_strike[significant_exposure]['strike_price'].min() - max_strike = dex_by_strike[significant_exposure]['strike_price'].max() + # Get unique strike prices and expiration dates + strike_prices = sorted(df['strike_price'].unique()) + expiry_dates = sorted(df['date_expiration'].unique()) - # Add some padding to the range - strike_padding = (max_strike - min_strike) * 0.1 - y_min = max(min_strike - strike_padding, dex_by_strike['strike_price'].min()) - y_max = min(max_strike + strike_padding, dex_by_strike['strike_price'].max()) + # Create y-axis ticks for strike prices + y_ticks = np.arange(len(strike_prices)) - # Plot DEX bars - positive_dex = dex_by_strike[dex_by_strike['dex'] > 0] - negative_dex = dex_by_strike[dex_by_strike['dex'] <= 0] + # Calculate total exposure for each strike and type + total_exposure = pd.DataFrame() - ax.barh(positive_dex['strike_price'], positive_dex['dex'], - color='green', alpha=0.7, label='Positive DEX') - ax.barh(negative_dex['strike_price'], negative_dex['dex'], - color='#964B00', alpha=0.7, label='Negative DEX') + for expiry in expiry_dates: + expiry_data = df[df['date_expiration'] == expiry] + + # Process calls + calls = expiry_data[expiry_data['put_call'] == 'CALL'] + call_exposure = calls.groupby('strike_price')['delta_exposure'].sum() + + # Process puts + puts = expiry_data[expiry_data['put_call'] == 'PUT'] + put_exposure = puts.groupby('strike_price')['delta_exposure'].sum() + + # Plot calls (positive x-axis) + if not call_exposure.empty: + ax.barh(y_ticks, call_exposure.reindex(strike_prices).fillna(0), + alpha=0.7, left=total_exposure.get('calls', 0), + color=call_colors[expiry_dates.tolist().index(expiry) % len(call_colors)], + height=0.8) + + # Plot puts (negative x-axis) + if not put_exposure.empty: + ax.barh(y_ticks, put_exposure.reindex(strike_prices).fillna(0), + alpha=0.7, left=total_exposure.get('puts', 0), + color=put_colors[expiry_dates.tolist().index(expiry) % len(put_colors)], + height=0.8) + + # Update total exposure + total_exposure['calls'] = total_exposure.get('calls', 0) + call_exposure.reindex(strike_prices).fillna(0) + total_exposure['puts'] = total_exposure.get('puts', 0) + put_exposure.reindex(strike_prices).fillna(0) - # Plot GEX profile - ax.plot(gex_by_strike['gex'], gex_by_strike['strike_price'], - color='yellow', label='GEX Profile', linewidth=2) + # Add strike price labels + ax.set_yticks(y_ticks) + ax.set_yticklabels([f'${price:.2f}' for price in strike_prices]) - # Calculate and plot support/resistance levels - significant_gex = gex_by_strike[abs(gex_by_strike['gex']) > abs(gex_by_strike['gex']).mean()] - resistance_level = significant_gex[significant_gex['strike_price'] > current_price]['strike_price'].min() - support_level = significant_gex[significant_gex['strike_price'] < current_price]['strike_price'].max() + # Add current price line + current_price_idx = np.searchsorted(strike_prices, current_price) + ax.axhline(y=current_price_idx, color='red', linestyle='--', alpha=0.5, + label=f'Current Price: ${current_price:.2f}') - # Add reference lines - if pd.notna(resistance_level): - ax.axhline(y=resistance_level, color='red', linestyle='--', alpha=0.5, - label=f'Call Resistance: {resistance_level:.1f}') - if pd.notna(support_level): - ax.axhline(y=support_level, color='green', linestyle='--', alpha=0.5, - label=f'Put Support: {support_level:.1f}') - ax.axhline(y=current_price, color='white', linestyle='--', alpha=0.5, - label=f'Spot Price: {current_price:.1f}') + # Format x-axis + ax.xaxis.set_major_formatter(plt.FuncFormatter( + lambda x, p: f'${abs(x/1e6):.1f}M' if abs(x) >= 1e6 else f'${abs(x/1e3):.1f}K')) - # Calculate and plot HVL - hvl = dex_by_strike['strike_price'][abs(dex_by_strike['dex']).idxmin()] - ax.axhline(y=hvl, color='gray', linestyle='--', alpha=0.5, - label=f'HVL: {hvl:.1f}') + # Add labels and title + ax.set_title(f'Delta Hedging Exposure by Strike and Expiration\n{df["ticker"].iloc[0]}', + pad=20) + ax.set_xlabel('Delta Exposure ($)') + ax.set_ylabel('Strike Price ($)') - # Set y-axis limits to focus on relevant region - ax.set_ylim(y_min, y_max) - - # Customize the plot - ax.set_title(f'Net DEX All Expirations for {df["ticker"].iloc[0]}\nTimestamp: {df["date"].iloc[0]}', pad=20) - ax.set_xlabel('Exposure (Contract-Adjusted)') - ax.set_ylabel('Strike Price') + # Add grid ax.grid(True, alpha=0.2) - ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left') - # Format axis - ax.xaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/1e6:.1f}B' if abs(x) >= 1e6 else f'{x/1e3:.1f}M')) + # Add legend for expiration dates + legend_elements = [] + for i, expiry in enumerate(expiry_dates): + days_to_expiry = (expiry - df['date'].iloc[0]).days + legend_elements.append(plt.Rectangle((0,0), 1, 1, + fc=call_colors[i % len(call_colors)], + alpha=0.7, + label=f'Exp: {expiry.strftime("%Y-%m-%d")} ({days_to_expiry}d)')) + + ax.legend(handles=legend_elements, loc='center left', bbox_to_anchor=(1, 0.5)) plt.tight_layout() return fig + +# Main execution +if __name__ == "__main__": + # Assuming we have the data loaded in ticker_data + df = pd.DataFrame(ticker_data) -# Use the functions with your data -df = pd.DataFrame(ticker_data) -processed_df = process_options_data(df) -current_price = float(df['underlying_price'].iloc[0]) -fig = plot_option_exposure(processed_df, current_price) -plt.show() - -# Print analysis -print("\nKey Levels Analysis:") -dex_by_strike = processed_df.groupby('strike_price')['dex'].sum() -gex_by_strike = processed_df.groupby('strike_price')['gex'].sum() - -print(f"Largest DEX Positive: Strike ${dex_by_strike.idxmax():.2f} (${dex_by_strike.max()/1e6:.2f}M)") -print(f"Largest DEX Negative: Strike ${dex_by_strike.idxmin():.2f} (${dex_by_strike.min()/1e6:.2f}M)") -print(f"Largest GEX: Strike ${gex_by_strike.idxmax():.2f} (${gex_by_strike.max()/1e6:.2f}M)") -print(f"Net DEX: ${dex_by_strike.sum()/1e6:.2f}M") -print(f"Net GEX: ${gex_by_strike.sum()/1e6:.2f}M") \ No newline at end of file + if not df.empty: + # Process the data + processed_df = process_options_data_by_expiry(df) + current_price = float(df['underlying_price'].iloc[0]) + + # Create and show the plot + fig = plot_delta_exposure_by_expiry(processed_df, current_price) + plt.show() + + # Print summary statistics + print("\nDelta Exposure Summary:") + total_call_exposure = processed_df[processed_df['put_call'] == 'CALL']['delta_exposure'].sum() + total_put_exposure = processed_df[processed_df['put_call'] == 'PUT']['delta_exposure'].sum() + net_exposure = total_call_exposure + total_put_exposure + + print(f"Total Call Delta Exposure: ${total_call_exposure/1e6:.2f}M") + print(f"Total Put Delta Exposure: ${total_put_exposure/1e6:.2f}M") + print(f"Net Delta Exposure: ${net_exposure/1e6:.2f}M") + else: + print("No data available for analysis") \ No newline at end of file