Alpha Factor en Python: Modelado para Estrategias de Trading Avanzadas
El Alpha Trading es la forma de modelar Alpha Factors en escenarios de exposición a la rentabilidad. Estos modelos están ganando popularidad entre inversores de todos los niveles gracias a la mayor accesibilidad tecnológica.
¿Qué es un Alpha Factor?
Es un concepto relativamente nuevo donde las explicaciones tradicionales no reflejan la realidad dinámica de esta modalidad de inversión. El mundo de los criptoactivos ha atraído a investigadores brillantes que han generado nuevos modelos de backtesting con criterios distintos a los clásicos. Estos procesos de obtención de rentabilidad operan en un paradigma que los inversores tradicionales o analistas técnicos encontrarían difícil de comprender.
 y evaluar la continuidad de la robustez del modelo.
El Alpha Factor mantiene una fuerte asociación con el método científico, realizando modelos de inferencia con rigor científico aceptable.
 y factor alpha (rentabilidad adicional al mercado). Definimos alpha como la rentabilidad proveniente de la exposición que no corresponde en tiempo con el comportamiento del activo subyacente.
Una estrategia 100% beta sería comprar y olvidar, mientras que una estrategia 100% Alpha sería oportunista, operando solo bajo ciertas condiciones específicas.
Un Alpha Factor es un conjunto de reglas y procedimientos que, partiendo de una formulación matemática, genera exposiciones a retornos utilizando metodologías alternativas para la extracción de ventajas.
Alpha Encodings
Los alpha encoders estandarizan y matematizan las señales dentro de la filosofía de sistemas de trading. Por ejemplo, podríamos generar señales de un sistema trend following básico con un cruce de medias:
$$sign(minus(ma_{30}(close),ma_{200}(close)))$$
Esto genera una respuesta binaria basada en la resta entre dos medias móviles (30,200). Sin embargo, la entropía de estas señales es demasiado alta, similar a usar entradas y salidas aleatorias. Para mejorar, podemos utilizar más fórmulas análogas:
$$plus( \ sign(minus(ma_{20}(close),ma_{50}(close))), \ sign(minus(ma_{50}(close),ma_{100}(close))), \ sign(minus(ma_{100}(close),ma_{200}(close))) \ )$$
Esto proporciona robustez mediante la validación de señales anteriores, creando un "comité" que exige mayores umbrales para realizar cambios de estado.
Las posibilidades para codificar alpha son infinitas. Los grandes bancos suelen utilizar estos encodings en estrategias de momentum y arbitrajes long/short. Incluso podríamos testear el efecto de anuncios post-beneficios de forma sencilla:
$$div(epsDifference,std_{12}(epsDifference))$$
Universos de Activos
. Considerado como el indicador más descriptivo del mercado de capitales de los EE.UU. El índice se compone por 500 compañías.
Para calcular el peso de cada activo se usa la fórmula:
$$p_a = MktCap / GlobalMktCap$$
Esto permite replicar exactamente la misma ponderación del índice sin ambigüedades.
Selección de Universos en Python
Vamos a crear universos de activos candidatos. Comenzaremos con una selección 100% aleatoria para evitar sesgos:
import pandas as pd
def random_tickers(x):
tables=pd.read_html("https://en.wikipedia.org/wiki/List_of_S%26P_500_companies")
tickers = tables[0]['Symbol'].tolist()
return random.choices(tickers,k=x)
También podemos refinar el proceso utilizando ETFs establecidos como referencia:
import pandas as pd
urls = ['https://www.blackrock.com/es/profesionales/productos/253743/ishares-sp-500-b-ucits-etf-acc-fund/1497267045693.ajax?fileType=csv&fileName=CSPX_holdings&dataType=fund',
'https://www.blackrock.com/es/profesionales/productos/253741/ishares-nasdaq-100-ucits-etf/1497267045693.ajax?fileType=csv&fileName=CSNDX_holdings&dataType=fund',
'https://www.blackrock.com/es/profesionales/productos/251382/ishares-msci-world-minimum-volatility-ucits-etf/1497267045693.ajax?fileType=csv&fileName=MVOL_holdings&dataType=fund',
'https://www.blackrock.com/es/profesionales/productos/280507/ishares-sp-500-health-care-sector-ucits-etf/1497267045693.ajax?fileType=csv&fileName=IUHC_holdings&dataType=fund']
allz = pd.DataFrame()
for x in range(len(urls)):
tempz = pd.read_csv(urls[x],skiprows=2)
allz = pd.concat([tempz,allz])
allz = allz[allz['Asset Class'] == 'Equity']
print('Cargados',len(allz['Ticker'].unique()),'tickers en un total de ',len(allz['Sector'].unique()),'Sectores')
allz = allz.set_index('Ticker')
allz['Market Value'] = allz['Market Value'].str.replace('.', '').str.replace(',', '.')
allz['Market Value'] = allz['Market Value'].astype(float)
Para transformar los candidatos a formato lista:
tickers_candidatos = allz.index.tolist()
O para acceder a tickers de un sector específico:
allz[allz['Sector'] == 'Cuidado de la Salud'].index.tolist()
Es importante destacar que no estamos seleccionando acciones donde nuestro sistema funciona mejor (lo que introduciría overfit), sino creando modelos que impacten contra todos los activos y seleccionen los más oportunos.
Introducción a los Alpha Factors en Python: Implementación Práctica
Vamos a explorar diferentes lógicas para investigar Alpha Factors. Este artículo presenta herramientas básicas para transformar una fórmula matemática en un modelo funcional.
Plan de Ataque
- Cargar las librerías y herramientas necesarias
- Programar las funciones auxiliares
- Programar el motor principal de optimización
- Integrar todas las piezas a la cadena
Herramientas Utilizadas
Utilizaremos librerías estándar de Python y yfinance para agilizar la descarga de datos.
import pandas as pd
import numpy as np
import scipy as sc
import matplotlib.pyplot as plt
import yfinance as yf
import itertools
from IPython.display import clear_output
plt.style.use("quantarmy")
Programación de Funciones Auxiliares
Creamos funciones para tareas repetitivas:
def calc_skew(data,y):
skew = data.rolling(y).skew()
return skew
def calc_rets(data,y):
rets = data.pct_change(y)
return rets
def calc_delta(d1,d2):
delta = d1 - d1.shift(d2)
return delta
Programación del Primer Alpha con Enfoque Multiactivo
Probaremos el alpha en diferentes activos para estimar su rendimiento:
def backtest_alpha_01(data,ret_x=10,days_delta=10,rolling_skew=10,p=1.0):
for d in ['SPY','QQQ','IWM','TLT','GLD']:
data = yf.download(d,progress=False)[['Adj Close']].round(2)
df = data[['Adj Close']].round(2)
df['c_ret'] = df['Adj Close'].pct_change(ret_x)
df['skew'] = df['c_ret'].rolling(rolling_skew).skew()
df['abs'] = np.abs(df['skew'])
df['alpha'] = calc_delta(df['abs'],days_delta)
df['signal'] = np.where(df['alpha'] > p,1,0)
df['pct'] = df['Adj Close'].pct_change()
df['ret'] = df['pct'] * df['signal'].shift(1)
df['ret'].cumsum().plot()
plt.title('Basic Backtest over assets main assets')
plt.legend(labels=['SPY','QQQ','IWM','TLT','GLD'])
plt.show()
return
 son arbitrarios. Los resultados variarán según los parámetros utilizados para calcular el estimador y sus puntos de corte.*
Programación del Motor del Optimizador
Creamos una función que recorra todo el espectro de parámetros posibles:
def explore_alpha_01(df,ret_x=10,ret_y=16,days_delta_x=10,days_delta_y=16,rolling_skew_x=10,rolling_skew_y=16,p_x=1,p_y=2.1):
eqs = pd.DataFrame()
res = []
i = 1
retz = range(ret_x,ret_y,1)
delt