<jupyter_start><jupyter_text>Trabajando con opciones Una opción puede negociarse en el mercado secundario por lo que es importante determinar su valor $V_t$ para cada tiempo $t\in [0, T]$. La ganancia que obtiene quién adquiere la opción se llama función de pago o "payoff" y claramente depende del valor del subyacente. Hay una gran variedad de opciones en el mercado y éstas se clasiflcan según su función de pago y la forma en que pueden ejercerse.  Las opciones que tienen como función de pago a$$ P(S(t),t)=max\{S(T)-K,0\} \rightarrow \text{En el caso de Call}$$ $$ P(S(t),t)=max\{K-S(T),0\} \rightarrow \text{En el caso de Put}$$ se llaman opciones **Vainilla**, con $h:[0,\infty) \to [0,\infty)$.La opción se llama **europea** si puede ejercerse sólo en la fecha de vencimiento.Se dice que una opción es **americana** si puede ejercerse en cualquier momento antes o en la fecha de vencimiento.Una opción compleja popular son las llamadas **opciones asiáticas** cuyos pagos dependen de todas las trayectorias del precio de los activos subyacentes. Las opciones cuyos pagos dependen de las trayectorias de los precios de los activos subyacentes se denominan opciones dependientes de la ruta.Principalmente, se puede resumir que las dos razones con más peso de importancia para utilizar opciones son el **aseguramiento** y la **especulación**. Opciones Plan Vainilla: opción de compra y opción de venta europeaUna opción vainilla o estándar es una opción normal de compra o venta que no tiene características especiales o inusuales. Puede ser para tamaños y vencimientos estandarizados, y negociarse en un intercambio.En comparación con otras estructuras de opciones, las opciones de vanilla no son sofisticadas o complicadas.  1. ¿Cómo descargar datos de opciones?<jupyter_code>#importar los paquetes que se van a usar
import pandas as pd
import pandas_datareader.data as web
import numpy as np
import datetime
import matplotlib.pyplot as plt
import scipy.stats as st
import seaborn as sns
%matplotlib inline
#algunas opciones para Pandas
pd.set_option('display.notebook_repr_html', True)
pd.set_option('display.max_columns', 6)
pd.set_option('display.max_rows', 10)
pd.set_option('display.width', 78)
pd.set_option('precision', 3)<jupyter_output><empty_output><jupyter_text>Usando el paquete `pandas_datareader` también podemos descargar datos de opciones. Por ejemplo, descarguemos los datos de las opciones cuyo activo subyacente son las acciones de Apple<jupyter_code>aapl = web.YahooOptions('AAPL')
aapl_opt = aapl.get_all_data().reset_index()
aapl_opt.set_index('Expiry')
# aapl
# closes = web.YahooDailyReader(ticker, start_date, end_date).read().sort_index('major_axis')
# aapl_opt.loc[0, 'JSON']
aapl_opt.loc[200, 'JSON']<jupyter_output><empty_output><jupyter_text>Una vez tenemos la información, podemos consultar de qué tipo son las opciones<jupyter_code>aapl_opt.loc[:, 'Type']<jupyter_output><empty_output><jupyter_text>o en que fecha expiran<jupyter_code>aapl_opt.loc[:, 'Expiry']<jupyter_output><empty_output><jupyter_text>Por otra parte, podríamos querer consultar todas las opciones de compra (call) que expiran en cierta fecha (2019-04-18)<jupyter_code>call06 = aapl_opt.loc[(aapl_opt.Expiry=='2019-04-18') & (aapl_opt.Type=='call')]
call06<jupyter_output><empty_output><jupyter_text>2. ¿Qué es la volatilidad implícita? **Volatilidad:** desviación estándar de los rendimientos.- ¿Cómo se calcula?- ¿Para qué calcular la volatilidad? - **Para valuar derivados**, por ejemplo **opciones**.- Método de valuación de riesgo neutral (se supone que el precio del activo $S_t$ no se ve afectado por el riesgo de mercado).Recorderis de cuantitativas:1. Ecuación de Black-Scholes$$ dS(t) = \mu S(t) + \sigma S(t)dW_t$$2. Solución de la ecuaciónEl valor de una opción Europea de vainilla $V_t$ puede obtenerse por:$$V_t = F(t,S_t)$$ donde![imagen.png](attachment:imagen.png)3. Opción de compra europea, suponiendo que los precios del activo son lognormales4. Opción de venta europea, suponiendo que los precios del activo son lognormales Entonces, ¿qué es la **volatilidad implícita**?La volatilidad es una medida de la incertidumbre sobre el comportamiento futuro de un activo, que se mide habitualmente como la desviación típica de la rentabilidad de dicho activo.<jupyter_code>ax = call06.set_index('Strike').loc[:, 'IV'].plot(figsize=(8,6))
ax.axvline(call06.Underlying_Price.iloc[0], color='g');
# call06.Underlying_Price
put06 = aapl_opt.loc[(aapl_opt.Expiry=='2019-04-18') & (aapl_opt.Type=='put')]
put06
ax = put06.set_index('Strike').loc[:, 'IV'].plot(figsize=(8,6))
ax.axvline(put06.Underlying_Price.iloc[0], color='g')<jupyter_output><empty_output><jupyter_text>Con lo que hemos aprendido, deberíamos ser capaces de crear una función que nos devuelva un `DataFrame` de `pandas` con los precios de cierre ajustados de ciertas compañías en ciertas fechas:- Escribir la función a continuación<jupyter_code># Función para descargar precios de cierre ajustados de varios activos a la vez:
def get_historical_closes(tickers, start_date=None, end_date=None, freq=None):
    # Fecha inicio por defecto (start_date='2010-01-01') y fecha fin por defecto (end_date=today)
    # Frecuencia de muestreo por defecto (freq='d')
    # Importamos paquetes necesarios
    import pandas as pd
    pd.core.common.is_list_like = pd.api.types.is_list_like
    import pandas_datareader.data as web  
    # Creamos DataFrame vacío de precios, con el índice de las fechas
    closes = pd.DataFrame(columns = tickers, index=web.YahooDailyReader(symbols=tickers[0], start=start_date, end=end_date, interval=freq).read().index)
    # Agregamos cada uno de los precios con YahooDailyReader
    for ticker in tickers:
        df = web.YahooDailyReader(symbols=ticker, start=start_date, end=end_date, interval=freq).read()
        closes[ticker]=df['Adj Close']
    closes.index_name = 'Date'
    closes = closes.sort_index()
    return closes<jupyter_output><empty_output><jupyter_text>- Obtener como ejemplo los precios de cierre de Apple del año pasado hasta la fecha. Graficar...<jupyter_code>ticker = ['AAPL']
start_date = '2017-01-01'

closes_aapl = get_historical_closes(ticker, start_date,freq='d')
closes_aapl.plot(figsize=(8,5));
plt.legend(ticker);<jupyter_output><empty_output><jupyter_text>- Escribir una función que pasándole el histórico de precios devuelva los rendimientos logarítmicos:<jupyter_code>def calc_daily_ret(closes):
    return np.log(closes/closes.shift(1)).iloc[1:]<jupyter_output><empty_output><jupyter_text>- Graficar...<jupyter_code>ret_aapl = calc_daily_ret(closes_aapl)
ret_aapl.plot(figsize=(8,6));<jupyter_output><empty_output><jupyter_text>También, descargar datos de opciones de Apple:<jupyter_code>aapl = web.YahooOptions('AAPL')
aapl_opt = aapl.get_all_data().reset_index()
aapl_opt.set_index('Expiry').sort_index()
aapl_opt.loc[(aapl_opt.Type=='call') & (aapl_opt.Strike==200) & (aapl_opt.Expiry=='2019-02-15')]
opcion_valuar = aapl_opt.loc[487]
opcion_valuar['JSON']<jupyter_output><empty_output><jupyter_text>Simulación de precios usando rendimiento simple y logarítmico  * Comenzaremos por suponer que los rendimientos son un p.e. estacionario que distribuyen $\mathcal{N}(\mu,\sigma)$.<jupyter_code># Descargamos los precios de apple
ticker = ['AAPL']
start_date = '2017-01-01'

closes_aapl = get_historical_closes(ticker, start_date,freq='d')
closes_aapl<jupyter_output><empty_output><jupyter_text>- **Rendimiento Simple**<jupyter_code># Obtenemos el rendimiento simple
Ri = closes_aapl.pct_change(1).iloc[1:]
# Obtenemos su media y desviación estándar de los rendimientos
mu_R = Ri.mean()[0]
sigma_R = Ri.std()[0]
Ri
ndays = 109
nscen = 10
dates = pd.date_range('2018-10-29', periods = ndays)
dates
dt = 1; # Rendimiento diario 
Z = np.random.randn(ndays,nscen)  # Z ~ N(0,1)
# Simulación normal de los rendimientos
Ri_dt = pd.DataFrame(mu_R*dt+Z*sigma_R*np.sqrt(dt),index=dates)
Ri_dt.cumprod()
# Simulación del precio
S_0 = closes_aapl.iloc[-1,0]
S_T = S_0*(1+Ri_dt).cumprod()

# Se muestran los precios simulados con los precios descargados
pd.concat([closes_aapl,S_T]).plot(figsize=(8,6));
plt.title('Simulación de precios usando rendimiento simple');<jupyter_output><empty_output><jupyter_text>- **Rendimiento logarítmico**<jupyter_code>ri = calc_daily_ret(closes_aapl)
# Usando la media y desviación estándar de los rendimientos logarítmicos
mu_r = ri.mean()[0]
sigma_r = ri.std()[0]

# # Usando la equivalencia teórica
# mu_r2 = mu_R - (sigma_R**2)/2

sim_ret_ri = pd.DataFrame(mu_r*dt+Z*sigma_r*np.sqrt(dt), index=dates)
# sim_ret_ri
# Simulación del precio
S_0 = closes_aapl.iloc[-1,0]
S_T2 = S_0*np.exp(sim_ret_ri.cumsum())

# Se muestran los precios simulados con los precios descargados
pd.concat([closes_aapl,S_T2]).plot(figsize=(8,6));
plt.title('Simulación de precios usando rendimiento logarítmico');
S_T-S_T2
print('Las std usando rendimientos logarítmicos y simples son iguales')
sigma_R,sigma_r<jupyter_output>Las std usando rendimientos logarítmicos y simples son iguales<jupyter_text>2. Valuación usando simulación: modelo normal para los rendimientos - Hallar media y desviación estándar muestral de los rendimientos logarítmicos<jupyter_code>mu = ret_aapl.mean()[0]
sigma = ret_aapl.std()[0]
mu, sigma<jupyter_output><empty_output><jupyter_text>No se toma la media sino la tasa libre de riesgo> Referencia: https://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yield<jupyter_code># Tasa de bonos de 1 yr de fecha 10/01/18 -> 2.60%
r = 0.0260/360 # Tasa diaria<jupyter_output><empty_output><jupyter_text>- Simularemos el tiempo de contrato (días=109) desde 29/10/18 hasta 15/02/19, 10 escenarios:> Calculador de fechas: https://es.calcuworld.com/calendarios/calculadora-de-tiempo-entre-dos-fechas/        - Generar fechas<jupyter_code>ndays = 109
nscen = 10
dates = pd.date_range('2018-10-29', periods = ndays)
dates<jupyter_output><empty_output><jupyter_text>- Generamos 10 escenarios de rendimientos simulados y guardamos en un dataframe<jupyter_code>sim_ret = pd.DataFrame(sigma*np.random.randn(ndays,nscen)+r, index=dates)
sim_ret.cumsum()
# Las columnas son los escenarios y las filas son las días de contrato<jupyter_output><empty_output><jupyter_text>- Con los rendimientos simulados, calcular los escenarios de precios respectivos:<jupyter_code>S0 = closes_aapl.iloc[-1,0]  # Condición inicial del precio a simular
sim_closes = S0*np.exp(sim_ret.cumsum())
sim_closes<jupyter_output><empty_output><jupyter_text>- Graficar:<jupyter_code>#sim_closes.plot(figsize=(8,6));
# Se muestran los precios simulados con los precios descargados
pd.concat([closes_aapl,sim_closes]).plot(figsize=(8,6));
opcion_valuar['JSON']
K=200  # strike price
ndays = 109 
nscen = 100000
dates = pd.date_range('2018-10-29', periods = ndays)
S0 = closes_aapl.iloc[-1,0]  # Condición inicial del precio a simular

sim_ret = pd.DataFrame(sigma*np.random.randn(ndays,nscen)+r,index=dates)
sim_closes = S0*np.exp(sim_ret.cumsum())
#strike = pd.DataFrame({'Strike':K*np.ones(ndays)}, index=dates)
#simul = pd.concat([closes_aapl.T,strike.T,sim_closes.T]).T
#simul.plot(figsize=(8,6),legend=False);
strike = pd.DataFrame(K*np.ones([ndays,nscen]), index=dates)
call = pd.DataFrame({'Prima':np.exp(-r*ndays) \
              *np.fmax(sim_closes-strike,np.zeros([ndays,nscen])).T.mean()}, index=dates)
call.plot();<jupyter_output><empty_output><jupyter_text>La valuación de la opción es:<jupyter_code>call.iloc[-1]<jupyter_output><empty_output><jupyter_text>Intervalo de confianza del 99%<jupyter_code>confianza = 0.99
sigma_est = sim_closes.iloc[-1].sem()
mean_est = call.iloc[-1].Prima
i1 = st.t.interval(confianza,nscen-1, loc=mean_est, scale=sigma_est)
i2 = st.norm.interval(confianza, loc=mean_est, scale=sigma_est)
print(i1)
print(i1)<jupyter_output>(20.819952289855564, 21.306430346677388)
(20.819952289855564, 21.306430346677388)<jupyter_text>Análisis de la distribución de los rendimientos<jupyter_code>ren = calc_daily_ret(closes_aapl) # rendimientos 
y,x,des = plt.hist(ren['AAPL'],bins=50,density=True,label='Histograma rendimientos')

mu_fit,sd_fit = st.norm.fit(ren) # Se ajustan los parámetros de una normal
# Valores máximo y mínimo de los rendiemientos a generar
ren_max = max(x);ren_min = min(x)
# Vector de rendimientos generados
ren_gen = np.arange(ren_min,ren_max,0.001)
# Generación de la normal ajustado con los parámetros encontrados
curve_fit = st.norm.pdf(ren_gen,loc=mu_fit,scale=sd_fit)
plt.plot(ren_gen,curve_fit,label='Distribución ajustada')
plt.legend()
plt.show()

st.probplot(ren['AAPL'], dist='norm', plot=plt);<jupyter_output><empty_output><jupyter_text>3. Valuación usando simulación: uso del histograma de rendimientosTodo el análisis anterior se mantiene. Solo cambia la forma de generar los números aleatorios para la simulación montecarlo.Ahora, generemos un histograma de los rendimientos diarios para generar valores aleatorios de los rendimientos simulados. - Primero, cantidad de días y número de escenarios de simulación<jupyter_code>ndays = 109 
nscen = 10<jupyter_output><empty_output><jupyter_text>- Del histograma anterior, ya conocemos las probabilidades de ocurrencia, lo que se llamó como variable `y`<jupyter_code>prob = y/np.sum(y)
values = x[1:]<jupyter_output><empty_output><jupyter_text>- Con esto, generamos los números aleatorios correspondientes a los rendimientos (tantos como días por número de escenarios).<jupyter_code>ret = np.random.choice(values, ndays*nscen, p=prob)
dates = pd.date_range('2018-10-29',periods=ndays)
sim_ret_hist = pd.DataFrame(ret.reshape((ndays,nscen)),index=dates)
sim_ret_hist
sim_closes_hist = (closes_aapl.iloc[-1,0])*np.exp(sim_ret_hist.cumsum())
sim_closes_hist
sim_closes_hist.plot(figsize=(8,6),legend=False);
pd.concat([closes_aapl,sim_closes_hist]).plot(figsize=(8,6),legend=False);
plt.title('Simulación usando el histograma de los rendimientos')
K=200
ndays = 109
nscen = 10000
freq, values = np.histogram(ret_aapl+r-mu, bins=2000)
prob = freq/np.sum(freq)
ret=np.random.choice(values[1:],ndays*nscen,p=prob)
dates=pd.date_range('2018-10-29',periods=ndays)
sim_ret_hist = pd.DataFrame(ret.reshape((ndays,nscen)),index=dates)
sim_closes_hist = (closes_aapl.iloc[-1,0])*np.exp(sim_ret_hist.cumsum())
strike = pd.DataFrame(K*np.ones(ndays*nscen).reshape((ndays,nscen)), index=dates)
call_hist = pd.DataFrame({'Prima':np.exp(-r*ndays)*np.fmax(sim_closes_hist-strike,np.zeros(ndays*nscen).reshape((ndays,nscen))).T.mean()}, index=dates)
call_hist.plot();
call_hist.iloc[-1]
opcion_valuar['JSON']<jupyter_output><empty_output><jupyter_text>Intervalo de confianza del 95%<jupyter_code>confianza = 0.95
sigma_est = sim_closes_hist.iloc[-1].sem()
mean_est = call_hist.iloc[-1].Prima
i1 = st.t.interval(confianza,nscen-1, loc=mean_est, scale=sigma_est)
i2 = st.norm.interval(confianza, loc=mean_est, scale=sigma_est)
print(i1)
print(i1)<jupyter_output>(20.538480482996167, 21.696585119470125)
(20.538480482996167, 21.696585119470125)
{'fraction_non_alphanumeric': 0.1187920805279648, 'fraction_numerical': 0.030464635690953937, 'mean_word_length': 6.7329896907216495, 'pattern_counts': {'":': 0, '<': 106, '<?xml version=': 0, '>': 109, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '27', 'dir': 'jupyter-structured-clean-dedup', 'id': '145329', 'license': 'MIT', 'n_tokens_mistral': 6175, 'n_tokens_neox': 5767, 'n_words': 1305, 'path': 'TEMA-3/Clase18_ValuacionOpciones.ipynb', 'repo_name': 'antoniotiradoz/SPF-2018-II', 'size': '15001'}