Aprendizaje automático sobre rendimientos
El aprendizaje supervisado ajusta una función de las variables X al objetivo y. En finanzas el objetivo suele ser un rendimiento futuro y las variables son cosas conocidas de antemano: rendimientos pasados, volatilidad, volumen. scikit-learn ofrece una interfaz uniforme: todo modelo tiene fit(X, y) y predict(X). Una diferencia clave con los problemas de libro de texto es la relación señal/ruido: los rendimientos son casi puro ruido, así que incluso un buen modelo explica una fracción mínima, y una configuración descuidada encuentra patrones que no existen.
import numpy as np
returns = np.array([0.01, -0.02, 0.015, 0.005, -0.01, 0.02])
n_lags = 2
# Row i holds the two returns before the target; y[i] is the return that follows them
windows = np.lib.stride_tricks.sliding_window_view(returns, n_lags)
X, y = windows[:-1], returns[n_lags:]
print(X)
print(y) # the targets are one step ahead of the featuresVariables a partir de retardos
Las variables más simples de un rendimiento son los rendimientos anteriores. Construye una matriz de retardos: la fila i contiene returns[i : i + n_lags], y el objetivo es returns[i + n_lags], el valor siguiente. Una vista de ventana deslizante la da sin bucle. Todo lo de una fila debe conocerse en el momento de predecir, que es la regla de anticipación aplicada a un modelo: una variable desplazada una posición en el sentido equivocado dejaría al modelo ver la respuesta.
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
# Pipeline: scale each feature, then a ridge regression (a linear model with a penalty on large coefficients)
pipe = Pipeline([("scale", StandardScaler()), ("model", Ridge(alpha=10.0))])
print([name for name, _ in pipe.steps])
rng = np.random.default_rng(1)
returns = np.zeros(400)
noise = rng.normal(0, 0.01, size=400)
for t in range(1, 400):
returns[t] = 0.3 * returns[t - 1] + noise[t] # a series with a little memory
windows = np.lib.stride_tricks.sliding_window_view(returns, 3)
X, y = windows[:-1], returns[3:]
pipe.fit(X[:300], y[:300]) # fit on the first 300 rows
print(np.round(pipe.named_steps["model"].coef_, 4)) # the last lag weighs mostPipelines y fuga de información
Un Pipeline agrupa el escalador y el modelo de modo que fit aprende las estadísticas de escalado (media y desviación de cada variable) solo con las filas de entrenamiento y predict las aplica sin cambios a filas nuevas. Escalar todo el conjunto antes de dividirlo filtraría la media y dispersión del periodo de prueba al entrenamiento: el mismo sesgo de anticipación, en un lugar nuevo. En una serie temporal la división también es cronológica: entrenar con las filas tempranas, probar con las posteriores, nunca una mezcla aleatoria.
import numpy as np
from sklearn.linear_model import Ridge
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
def build_pipeline(alpha):
return Pipeline([("scale", StandardScaler()), ("model", Ridge(alpha=alpha))])
rng = np.random.default_rng(1)
returns = np.zeros(600)
noise = rng.normal(0, 0.01, size=600)
for t in range(1, 600):
returns[t] = 0.3 * returns[t - 1] + noise[t]
windows = np.lib.stride_tricks.sliding_window_view(returns, 3)
X, y = windows[:-1], returns[3:]
model = build_pipeline(10.0).fit(X[:400], y[:400]) # chronological split: early rows to train
pred = model.predict(X[400:]) # later rows to test
print(round(float(np.mean(np.sign(y[400:]) == np.sign(pred))), 3)) # direction accuracy on unseen dataJuzgar el resultado con honestidad
Para una señal de trading, la dirección suele ser lo que importa, así que una puntuación simple es la precisión direccional: la proporción de días en que el rendimiento predicho y el real tienen el mismo signo. El 50 % es cara o cruz. Los predictores reales de rendimientos alcanzan el 51 al 53 %, y un 60 % en datos de prueba es más probablemente una fuga que un hallazgo. Compara con las referencias triviales (predecir siempre subida, predecir cero) antes de creer nada.
import numpy as np
y_true = np.array([0.01, -0.02, 0.015, -0.005, 0.02, -0.01])
y_pred = np.array([0.004, 0.001, 0.002, -0.001, 0.003, 0.002]) # the model is right on 4 of 6 signs
accuracy = float(np.mean(np.sign(y_true) == np.sign(y_pred)))
always_up = float(np.mean(np.sign(y_true) == 1)) # the trivial baseline: always predict a rise
print(round(accuracy, 3), round(always_up, 3)) # compare the model with the baselinealpha es un mando que ajustar, pero en un periodo de validación, nunca en el de prueba. Los siguientes temas construyen las variables y la validación walk-forward que lo hacen honesto.- Una matriz de retardos (
sliding_window_view) da variables a partir de los rendimientos previos a cada objetivo; todo lo de una fila debe conocerse al predecir. - Un
Pipeline([("scale", StandardScaler()), ("model", Ridge(alpha=alpha))])aprende el escalado solo con las filas de entrenamiento; divida cronológicamente, nunca al azar. - Puntúe la dirección (proporción de signos iguales) frente a referencias triviales; 51 al 53 % es realista, 60 % sugiere fuga; los modelos lineales regularizados convienen a datos ruidosos.
A continuación: la ingeniería de variables para datos financieros, construir mejores entradas que los rendimientos retardados en bruto.
Lab: qué se califica
- Convertir una serie en variables con
lag_matrix25 pts - Encadenar los pasos con
build_pipeline25 pts - Entrenar con el pasado y predecir el resto con
fit_predict25 pts - Puntuar las direcciones con
direction_accuracy25 pts