change quarter date for hedge funds

This commit is contained in:
MuslemRahimi 2024-10-25 13:42:02 +02:00
parent 5985c54c77
commit b406a53f68
4 changed files with 126 additions and 110 deletions

View File

@ -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"):

View File

@ -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"):

View File

@ -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

View File

@ -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")
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")