import pandas as pd
from scipy import optimize
import numpy as np
from scipy.stats import norm
import plotly.graph_objects as go
import time

# Función de Black-Scholes para opciones Call
def black_scholes_call(S, K, T, r, sigma):
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    call_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    return call_price

# Función de Black-Scholes para opciones Put
def black_scholes_put(S, K, T, r, sigma):
    d1 = (np.log(S / K) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    put_price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
    return put_price

# Función para calcular la volatilidad implícita
def implied_volatility(option_price, S, K, T, r, option_type='call'):
    try:
        objective = lambda sigma: (black_scholes_call(S, K, T, r, sigma) - option_price) if option_type == 'call' else (black_scholes_put(S, K, T, r, sigma) - option_price)
        implied_vol = optimize.newton(objective, 0.2, tol=1e-6)
        return implied_vol
    except RuntimeError:
        return np.nan  # Retorna NaN si no se puede encontrar la volatilidad implícita

# Leer el archivo original
file_path = 'optionChain_2024-06-26.csv'
data = pd.read_csv(file_path)

# Función para encontrar el strike más cercano al precio subyacente
def find_atm_row(group):
    group['difference'] = (group['strike'] - group['underlying_price']).abs()
    atm_row = group.loc[group['difference'].idxmin()]
    return atm_row

# Agrupar por timestamp y aplicar la función para obtener los strikes ATM
atm_data = data.groupby('timestamp').apply(find_atm_row).reset_index(drop=True)

# Eliminar la columna de diferencia que se usó para calcular el strike más cercano
atm_data = atm_data.drop(columns=['difference'])

# Suponemos una tasa de interés libre de riesgo de 0.054
risk_free_rate = 0.054

# Tiempo hasta la expiración (1 día en este caso)
time_to_expiration = 1 / 365

# Calcular la volatilidad implícita para cada fila ATM y el movimiento esperado
iv_list = []
for index, row in atm_data.iterrows():

    S = row['underlying_price']
    K = row['strike']
    option_price_call = (row['bid_call'] + row['ask_call']) / 2
    option_price_put = (row['bid_put'] + row['ask_put']) / 2
    
    iv_call = implied_volatility(option_price_call, S, K, time_to_expiration, risk_free_rate, option_type='call')
    iv_put = implied_volatility(option_price_put, S, K, time_to_expiration, risk_free_rate, option_type='put')
    
    iv_avg = (iv_call + iv_put) / 2
    
    expected_move = S * iv_avg * np.sqrt(time_to_expiration)
    
    iv_list.append({'timestamp': row['timestamp'], 'strike': K, 'underlying_price': S, 'iv_call': iv_call, 'iv_put': iv_put, 'iv_avg': iv_avg, 'expected_move': expected_move})

iv_data = pd.DataFrame(iv_list)

# Convertir la columna 'timestamp' a tipo datetime sin especificar un formato
iv_data['timestamp'] = pd.to_datetime(iv_data['timestamp'], errors='coerce')

# Ordenar los datos por timestamp
iv_data = iv_data.sort_values('timestamp')

# Guardar los datos en un nuevo archivo CSV
output_path = 'atm_optionChain_with_iv_and_expected_move_2024-06-28.csv'
iv_data.to_csv(output_path, index=False)

# Graficar el movimiento esperado y el precio subyacente (SPX) usando Plotly
fig = go.Figure()

# Graficar el precio subyacente (SPX) en el eje primario
fig.add_trace(go.Scatter(x=iv_data['timestamp'], y=iv_data['underlying_price'], mode='lines', name='SPX'))

# Graficar el movimiento esperado en el eje secundario
fig.add_trace(go.Scatter(x=iv_data['timestamp'], y=iv_data['expected_move'], mode='lines', name='Expected Move', yaxis='y2'))

# Configurar los ejes
fig.update_layout(
    title='Movimiento Esperado y SPX',
    xaxis_title='Timestamp',
    yaxis=dict(
        title='SPX',
        titlefont=dict(color='black'),
        tickfont=dict(color='black')
    ),
    yaxis2=dict(
        title='Expected Move',
        titlefont=dict(color='blue'),
        tickfont=dict(color='blue'),
        overlaying='y',
        side='right'
    ),
    legend=dict(x=0.01, y=0.99),
    xaxis=dict(tickformat='%H:%M', tickangle=45)
)

# Mostrar el gráfico
fig.show()

print(f'El archivo procesado se ha guardado en {output_path}')
