diff --git a/app/cron_dashboard.py b/app/cron_dashboard.py index 712dce7..cb2123d 100644 --- a/app/cron_dashboard.py +++ b/app/cron_dashboard.py @@ -223,8 +223,8 @@ async def get_recent_earnings(session): pass res_list = remove_duplicates(res_list) - #res_list.sort(key=lambda x: x['marketCap'], reverse=True) - res_list.sort(key=lambda x: (-parse_time(x['time']).timestamp(), -x['marketCap'])) + res_list.sort(key=lambda x: x['marketCap'], reverse=True) + #res_list.sort(key=lambda x: (-parse_time(x['time']).timestamp(), -x['marketCap'])) res_list = [{k: v for k, v in d.items() if k != 'marketCap'} for d in res_list] return res_list[0:10] diff --git a/app/support.py b/app/support.py new file mode 100644 index 0000000..6c0d80a --- /dev/null +++ b/app/support.py @@ -0,0 +1,228 @@ +import pandas as pd +import numpy as np +import ujson +import matplotlib.pyplot as plt +from scipy.stats import norm +from datetime import datetime, date, timedelta +from benzinga import financial_data +import os +from dotenv import load_dotenv +import seaborn as sns +import sqlite3 + +def calculate_volatility(prices_df): + prices_df = prices_df.sort_values(by='date') + prices_df['return'] = prices_df['close'].pct_change() + returns = prices_df['return'].dropna() + return returns.std() * np.sqrt(252) + +# Load API key from environment +load_dotenv() +api_key = os.getenv('BENZINGA_API_KEY') +fin = financial_data.Benzinga(api_key) +stock_con = sqlite3.connect('stocks.db') + + +query_template = """ + SELECT date, close,change_percent + FROM "{ticker}" + WHERE date BETWEEN ? AND ? +""" + + +ticker = 'NVDA' +end_date = date.today() +start_date = end_date - timedelta(1) +end_date_str = end_date.strftime('%Y-%m-%d') +start_date_str = start_date.strftime('%Y-%m-%d') + +query = query_template.format(ticker=ticker) +df_price = pd.read_sql_query(query, stock_con, params=('2024-01-01', end_date_str)).round(2) +df_price = df_price.rename(columns={"change_percent": "changesPercentage"}) + +volatility = calculate_volatility(df_price) +print(start_date, end_date) +print('volatility', volatility) + + +def get_data(ticker): + res_list = [] + page = 0 + while True: + try: + data = fin.options_activity(date_from=start_date_str, date_to=end_date_str, company_tickers=ticker, page=page, pagesize=1000) + data = ujson.loads(fin.output(data))['option_activity'] + if not data: + break # Stop when no more data is returned + filtered_data = [{key: value for key, value in item.items() if key not in ['description_extended', 'updated']} for item in data] + res_list += filtered_data + except Exception as e: + print(e) + break + page += 1 + return res_list + + +ticker_data = get_data(ticker) +ticker_data = [item for item in ticker_data if datetime.strptime(item['date_expiration'], '%Y-%m-%d') >= datetime.now()] +print(len(ticker_data)) + +def calculate_option_greeks(S, K, T, r, sigma, option_type='CALL'): + """ + Calculate option Greeks using Black-Scholes formula + S: Current stock price + K: Strike price + T: Time to expiration (in years) + r: Risk-free rate + sigma: Volatility + """ + if T <= 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) + gamma = norm.pdf(d1) / (S * sigma * np.sqrt(T)) + + return delta, gamma + +def process_options_data(df): + """ + Process options data and calculate DEX and GEX + """ + # Convert data types + df['strike_price'] = pd.to_numeric(df['strike_price']) + df['volume'] = pd.to_numeric(df['volume']) + 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']) + + # 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 + + # Calculate Greeks for each option + greeks = df.apply(lambda row: calculate_option_greeks( + row['underlying_price'], + row['strike_price'], + row['T'], + risk_free_rate, + sigma, + 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(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['volume'] * contract_multiplier + + # Calculate exposures + df['dex'] = df['delta'] * df['volume'] * contract_multiplier + df['gex'] = df['gamma'] * df['volume'] * contract_multiplier * df['underlying_price'] * 0.01 + + return df + +def plot_option_exposure(df, current_price): + """ + Create visualization of DEX and GEX profiles with focused y-axis + """ + plt.style.use('dark_background') + fig, ax = plt.subplots(figsize=(12, 8)) + + # 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() + + # 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() + + # 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()) + + # Plot DEX bars + positive_dex = dex_by_strike[dex_by_strike['dex'] > 0] + negative_dex = dex_by_strike[dex_by_strike['dex'] <= 0] + + 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') + + # Plot GEX profile + ax.plot(gex_by_strike['gex'], gex_by_strike['strike_price'], + color='yellow', label='GEX Profile', linewidth=2) + + # 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 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}') + + # 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}') + + # 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') + 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')) + + plt.tight_layout() + return fig + +# 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