El auge del comercio automatizado: máquinas que operan con el S&P 500
Publicado: 2022-03-11Hoy en día, más del 60 por ciento de las actividades comerciales con diferentes activos (como acciones, futuros de índices, productos básicos) ya no son realizadas por comerciantes de "seres humanos", sino que dependen del comercio automatizado. Existen programas especializados basados en algoritmos particulares que compran y venden automáticamente activos en diferentes mercados, destinados a lograr un rendimiento positivo a largo plazo.
En este artículo, le mostraré cómo predecir, con buena precisión, cómo debe colocarse la próxima operación para obtener una ganancia positiva. Para este ejemplo, como activo subyacente para negociar, seleccioné el índice S&P 500, el promedio ponderado de las 500 empresas estadounidenses con mayor capitalización. Una estrategia muy simple de implementar es comprar el índice S&P 500 cuando Wall Street Exchange comience a cotizar, a las 9:30 a. m., y venderlo en la sesión de cierre a las 4:00 p. m., hora del este. Si el precio de cierre del índice es más alto que el precio de apertura, hay una ganancia positiva, mientras que se lograría una ganancia negativa si el precio de cierre es más bajo que el precio de apertura. Entonces, la pregunta es: ¿cómo sabemos si la sesión de negociación terminará con un precio de cierre más alto que el precio de apertura? El aprendizaje automático es una herramienta poderosa para lograr una tarea tan compleja, y puede ser una herramienta útil para ayudarnos con la decisión comercial.
El aprendizaje automático es la nueva frontera de muchas aplicaciones útiles de la vida real. El comercio financiero es uno de estos, y se usa muy a menudo en este sector. Un concepto importante sobre Machine Learning es que no necesitamos escribir código para cada tipo de regla posible, como el reconocimiento de patrones. Esto se debe a que cada modelo asociado con el aprendizaje automático aprende de los datos en sí y luego se puede usar para predecir nuevos datos no vistos.
Descargo de responsabilidad: el propósito de este artículo es mostrar cómo entrenar métodos de aprendizaje automático y, en los ejemplos de código proporcionados, no se explican todas las funciones. Este artículo no pretende permitir que uno copie y pegue todo el código y ejecute las mismas pruebas proporcionadas, ya que faltan algunos detalles que estaban fuera del alcance del artículo. Además, se requiere un conocimiento básico de Python. La intención principal del artículo es mostrar un ejemplo de cómo el aprendizaje automático puede ser efectivo para predecir compras y ventas en el sector financiero. Sin embargo, comerciar con dinero real significa tener muchas otras habilidades, como la gestión del dinero y la gestión de riesgos. Este artículo es solo una pequeña parte del "panorama general".
Creación de su primer programa de comercio automatizado de datos financieros
Entonces, ¿quieres crear tu primer programa para analizar datos financieros y predecir la operación correcta? Déjame enseñarte como. Usaré Python para el código de Machine Learning y usaremos datos históricos del servicio Yahoo Finance. Como se mencionó anteriormente, los datos históricos son necesarios para entrenar el modelo antes de hacer nuestras predicciones.
Para comenzar, necesitamos instalar:
- Python, y en particular sugiero usar el cuaderno IPython.
- Paquete Yahoo Finance Python (el nombre exacto es
yahoo-finance
) a través del comando de terminal:pip install yahoo-finance
. - Una versión de prueba gratuita del paquete Machine Learning llamado GraphLab. No dude en consultar la documentación útil de esa biblioteca.
Tenga en cuenta que solo una parte de GraphLab es de código abierto, el SFrame, por lo que para usar toda la biblioteca necesitamos una licencia. Hay una licencia gratuita de 30 días y una licencia no comercial para estudiantes o aquellos que participan en competencias de Kaggle. Desde mi punto de vista, GraphLab Create es una biblioteca muy intuitiva y fácil de usar para analizar datos y entrenar modelos de Machine Learning.
Excavando en el código de Python
Profundicemos con un poco de código de Python para ver cómo descargar datos financieros de Internet. Sugiero usar el cuaderno IPython para probar el siguiente código, porque IPython tiene muchas ventajas en comparación con un IDE tradicional, especialmente cuando necesitamos combinar el código fuente, el código de ejecución, los datos de la tabla y los gráficos en el mismo documento. Para obtener una breve explicación sobre el uso de IPython Notebook, consulte el artículo Introducción a IPython Notebook.
Entonces, creemos un nuevo cuaderno IPython y escribamos un código para descargar los precios históricos del índice S&P 500. Tenga en cuenta que si prefiere usar otras herramientas, puede comenzar con un nuevo proyecto de Python en su IDE preferido.
import graphlab as gl from __future__ import division from datetime import datetime from yahoo_finance import Share # download historical prices of S&P 500 index today = datetime.strftime(datetime.today(), "%Y-%m-%d") stock = Share('^GSPC') # ^GSPC is the Yahoo finance symbol to refer S&P 500 index # we gather historical quotes from 2001-01-01 up to today hist_quotes = stock.get_historical('2001-01-01', today) # here is how a row looks like hist_quotes[0] {'Adj_Close': '2091.580078', 'Close': '2091.580078', 'Date': '2016-04-22', 'High': '2094.320068', 'Low': '2081.199951', 'Open': '2091.48999', 'Symbol': '%5eGSPC', 'Volume': '3790580000'}
Aquí, hist_quotes
es una lista de diccionarios, y cada objeto de diccionario es un día de negociación con valores Open
, High
, Low
, Close
, Adj_close
, Volume
, Symbol
y Date
. Durante cada día de negociación, el precio generalmente cambia desde el precio de apertura de Open
hasta el precio de cierre de Close
, y alcanza un valor máximo y mínimo High
y Low
. Necesitamos leerlo y crear listas de cada uno de los datos más relevantes. Además, los datos deben ordenarse por los valores más recientes al principio, por lo que debemos invertirlo:
l_date = [] l_open = [] l_high = [] l_low = [] l_close = [] l_volume = [] # reverse the list hist_quotes.reverse() for quotes in hist_quotes: l_date.append(quotes['Date']) l_open.append(float(quotes['Open'])) l_high.append(float(quotes['High'])) l_low.append(float(quotes['Low'])) l_close.append(float(quotes['Close'])) l_volume.append(int(quotes['Volume']))
Podemos empaquetar todas las cotizaciones descargadas en un objeto SFrame
, que es un marco de datos basado en columnas altamente escalable, y está comprimido. Una de las ventajas es que también puede ser más grande que la cantidad de RAM porque está respaldado por disco. Puede consultar la documentación para obtener más información sobre SFrame.
Entonces, almacenemos y luego verifiquemos los datos históricos:
qq = gl.SFrame({'datetime' : l_date, 'open' : l_open, 'high' : l_high, 'low' : l_low, 'close' : l_close, 'volume' : l_volume}) # datetime is a string, so convert into datetime object qq['datetime'] = qq['datetime'].apply(lambda x:datetime.strptime(x, '%Y-%m-%d')) # just to check if data is sorted in ascending mode qq.head(3)
cerrar | fecha y hora | elevado | bajo | abierto | volumen |
1283.27 | 2001-01-02 00:00:00 | 1320.28 | 1276.05 | 1320.28 | 1129400000 |
1347.56 | 2001-01-03 00:00:00 | 1347.76 | 1274.62 | 1283.27 | 1880700000 |
1333.34 | 2001-01-04 00:00:00 | 1350.24 | 1329.14 | 1347.56 | 2131000000 |
Ahora podemos guardar datos en el disco con el método save
de SFrame
, de la siguiente manera:
qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)
Veamos cómo se ve el S&P 500
Para ver cómo se verán los datos S&P 500 cargados, podemos usar el siguiente código:
import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])
La salida del código es el siguiente gráfico:
Entrenamiento de algunos modelos de aprendizaje automático
Agregar resultado
Como dije en la parte introductoria de este artículo, el objetivo de cada modelo es predecir si el precio de cierre será más alto que el precio de apertura. Por lo tanto, en ese caso, podemos lograr un rendimiento positivo al comprar el activo subyacente. Por lo tanto, debemos agregar una columna de outcome
en nuestros datos que será la variable target
o predicted
. Cada fila de esta nueva columna será:
-
+1
por un día alcista con un precio deClosing
superior al precio deOpening
. -
-1
por un día Abajo con un precio deClosing
más bajo que el precio deOpening
.
# add the outcome variable, 1 if the trading session was positive (close>open), 0 otherwise qq['outcome'] = qq.apply(lambda x: 1 if x['close'] > x['open'] else -1) # we also need to add three new columns 'ho' 'lo' and 'gain' # they will be useful to backtest the model, later qq['ho'] = qq['high'] - qq['open'] # distance between Highest and Opening price qq['lo'] = qq['low'] - qq['open'] # distance between Lowest and Opening price qq['gain'] = qq['close'] - qq['open']
Dado que necesitamos evaluar algunos días antes del último día de negociación, debemos retrasar los datos uno o más días. Para ese tipo de operación de retraso , necesitamos otro objeto del paquete GraphLab llamado TimeSeries
. TimeSeries
tiene un shift
de método que retrasa los datos en un cierto número de filas.
ts = gl.TimeSeries(qq, index='datetime') # add the outcome variable, 1 if the bar was positive (close>open), 0 otherwise ts['outcome'] = ts.apply(lambda x: 1 if x['close'] > x['open'] else -1) # GENERATE SOME LAGGED TIMESERIES ts_1 = ts.shift(1) # by 1 day ts_2 = ts.shift(2) # by 2 days # ...etc.... # it's an arbitrary decision how many days of lag are needed to create a good forecaster, so # everyone can experiment by his own decision
Adición de predictores
Los predictores son un conjunto de características variables que deben elegirse para entrenar el modelo y predecir nuestro resultado . Por lo tanto, la elección del factor de pronóstico es un componente crucial, si no el más importante, del pronosticador.
Solo por nombrar algunos ejemplos, un factor a considerar puede ser si el cierre de hoy es más alto que el cierre de ayer, y eso podría extenderse con el cierre de dos días anteriores, etc. Una opción similar se puede traducir con el siguiente código:
ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close']
Como se muestra arriba, agregué dos nuevas columnas de características, feat1
y feat2
en nuestro conjunto de datos ( ts
) que contienen 1
si la comparación es verdadera y 0
en caso contrario.
Este artículo pretende dar un ejemplo de Machine Learning aplicado al sector Financiero. Prefiero centrarme en cómo se pueden usar los modelos de aprendizaje automático con datos financieros, y no entraremos en detalles sobre cómo elegir los factores correctos para entrenar los modelos. Es demasiado exhaustivo explicar por qué se utilizan unos factores respecto a otros, debido a un aumento considerable de la complejidad. Mi trabajo de investigación es estudiar muchas hipótesis de elegir factores para crear un buen predictor. Entonces, para empezar, le sugiero que experimente con muchas combinaciones diferentes de factores, para ver si pueden aumentar la precisión del modelo.
# add_features is a helper function, which is out of the scope of this article, # and it returns a tuple with: # ts: a timeseries object with, in addition to the already included columns, also lagged columns # as well as some features added to train the model, as shown above with feat1 and feat2 examples # l_features: a list with all features used to train Classifier models # l_lr_features: a list all features used to train Linear Regression models ts, l_features, l_lr_features = add_features(ts) # add the gain column, for trading operations with LONG only positions. # The gain is the difference between Closing price - Opening price ts['gain'] = ts['close'] - ts['open'] ratio = 0.8 # 80% of training set and 20% of testing set training = ts.to_sframe()[0:round(len(ts)*ratio)] testing = ts.to_sframe()[round(len(ts)*ratio):]
Entrenamiento de un modelo de árbol de decisión
GraphLab Create tiene una interfaz muy limpia para implementar modelos de Machine Learning. Cada modelo tiene un método de create
que se usa para ajustar el modelo con un conjunto de datos de entrenamiento. Los parámetros típicos son:
-
training
: es un conjunto de entrenamiento que contiene columnas de características y una columna de destino. -
target
: es el nombre de la columna que contiene la variable de destino. -
validation_set
: es un conjunto de datos para monitorear el rendimiento de generalización del modelo. En nuestro caso, no tenemosvalidation_set
. -
features
: es una lista de nombres de columnas de características utilizadas para entrenar el modelo. -
verbose
: si estrue
, imprime la información de progreso durante el entrenamiento.
Mientras que otros parámetros son propios del propio modelo, como por ejemplo:
-
max_depth
- es la profundidad máxima de un árbol.
Con el siguiente código construimos nuestro árbol de decisión:
max_tree_depth = 6 decision_tree = gl.decision_tree_classifier.create(training, validation_set=None, target='outcome', features=l_features, max_depth=max_tree_depth, verbose=False)
Medición del rendimiento del modelo ajustado
La precisión es una métrica importante para evaluar la bondad del pronosticador. Es el número de predicciones correctas dividido por el número total de puntos de datos. Dado que el modelo está equipado con datos de entrenamiento, la precisión evaluada con el conjunto de entrenamiento es mejor que la obtenida con un conjunto de prueba.
La precisión es la fracción de predicciones positivas que son positivas. Necesitamos precisión para ser un número más cercano a 1
, para lograr una tasa de ganancias "perfecta". Nuestro decision_tree
, como otro clasificador del paquete GraphLab Create, tiene su método de evaluate
para obtener muchas métricas importantes del modelo ajustado.
Recordar cuantifica la capacidad de un clasificador para predecir ejemplos positivos. El recuerdo se puede interpretar como la probabilidad de que el clasificador identifique correctamente un ejemplo positivo seleccionado al azar. Necesitamos que la precisión sea un número más cercano a 1
para lograr una tasa de ganancias "perfecta".
El siguiente código mostrará la precisión del modelo ajustado tanto con el conjunto de entrenamiento como con el conjunto de prueba:
decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy'] (0.6077348066298343, 0.577373211963589)
Como se muestra arriba, la precisión del modelo con el conjunto de prueba es de alrededor del 57 por ciento, lo que de alguna manera es mejor que lanzar una moneda al aire (50 por ciento).
Predicción de datos
GraphLab Create tiene la misma interfaz para predecir datos de diferentes modelos ajustados. Usaremos el método de predict
, que necesita un conjunto de pruebas para predecir la variable objetivo, en nuestro caso, el outcome
. Ahora, podemos predecir los datos del conjunto de prueba:
predictions = decision_tree.predict(testing) # and we add the predictions column in testing set testing['predictions'] = predictions # let's see the first 10 predictions, compared to real values (outcome column) testing[['datetime', 'outcome', 'predictions']].head(10)
fecha y hora | Salir | predicciones |
2013-04-05 00:00:00 | -1 | -1 |
2013-04-08 00:00:00 | 1 | 1 |
2013-04-09 00:00:00 | 1 | 1 |
2013-04-10 00:00:00 | 1 | -1 |
2013-04-11 00:00:00 | 1 | -1 |
2013-04-12 00:00:00 | -1 | -1 |
2013-04-15 00:00:00 | -1 | 1 |
2013-04-16 00:00:00 | 1 | 1 |
2013-04-17 00:00:00 | -1 | -1 |
2013-04-18 00:00:00 | -1 | 1 |
Los falsos positivos son casos en los que el modelo predice un resultado positivo, mientras que el resultado real del conjunto de pruebas es negativo. Viceversa, los falsos negativos son casos en los que el modelo predice un resultado negativo en el que el resultado real del conjunto de pruebas es positivo.
Nuestra estrategia comercial espera un resultado pronosticado positivamente para comprar S&P 500 al precio de Opening
y venderlo al precio de Closing
, por lo que nuestra esperanza es tener la tasa de falsos positivos más baja para evitar pérdidas. En otras palabras, esperamos que nuestro modelo tenga la tasa de precisión más alta.
Como podemos ver, hay dos falsos negativos (en 2013-04-10 y 2013-04-11) y dos falsos positivos (en 2013-04-15 y 2013-04-18) dentro de los primeros diez valores predichos de la conjunto de prueba
Con un cálculo simple, considerando este pequeño conjunto de diez predicciones:
- precisión = 6/10 = 0,6 o 60 %
- precisión =3/5 = 0,6 o 60%
- recuerdo = 3/5 = 0,6 o 60%
Tenga en cuenta que, por lo general, los números anteriores son diferentes entre sí, pero en este caso son los mismos.
Prueba retrospectiva del modelo
Ahora simulamos cómo operaría el modelo usando sus valores predichos. Si el resultado pronosticado es igual a +1
, significa que esperamos un día Up . Con un día al alza, compramos el índice al comienzo de la sesión y vendemos el índice al final de la sesión durante el mismo día. Por el contrario, si el resultado pronosticado es igual a -1
, esperamos un día bajista , por lo que no operaremos durante ese día.
Las ganancias y pérdidas ( pnl
) de una operación diaria completa, también denominada round turn , en este ejemplo vienen dadas por:
-
pnl = Close - Open
(para cada día de negociación)
Con el código que se muestra a continuación, llamo a la función auxiliar plot_equity_chart
para crear un gráfico con la curva de ganancias acumuladas (curva de equidad). Sin profundizar demasiado, simplemente obtiene una serie de valores de pérdidas y ganancias y calcula la serie de sumas acumuladas para graficar.
pnl = testing[testing['predictions'] == 1]['gain'] # the gain column contains (Close - Open) values # I have written a simple helper function to plot the result of all the trades applied to the # testing set and represent the total return expressed by the index basis points # (not expressed in dollars $) plot_equity_chart(pnl,'Decision tree model')
Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511
Aquí, Sharpe es el índice anual de Sharpe, un indicador importante de la bondad del modelo comercial.
Teniendo en cuenta las operaciones expresadas día a día, mientras que la mean
es la media de la lista de pérdidas y ganancias, y sd
es la desviación estándar. Para simplificar la fórmula que se muestra arriba, he considerado un rendimiento libre de riesgo igual a 0.
Algunos conceptos básicos sobre el comercio
Operar con el índice requiere comprar un activo, que se deriva directamente del índice. Muchos corredores replican el índice S&P 500 con un producto derivado llamado CFD (Contrato por diferencia), que es un acuerdo entre dos partes para intercambiar la diferencia entre el precio de apertura y el precio de cierre de un contrato.
Ejemplo : compre 1 CFD S&P 500 en la Open
(el valor es 2000), véndalo al Close
del día (el valor es 2020). La diferencia, por lo tanto la ganancia, es de 20 puntos. Si cada punto tiene un valor de $25:
- La ganancia bruta es
20 points x $25 = $500
con 1 contrato de CFD.
Digamos que el corredor mantiene un deslizamiento de 0,6 puntos para sus propios ingresos:

- La ganancia neta es
(20 - 0.6) points x $25 = $485
.
Otro aspecto importante a considerar es evitar pérdidas significativas dentro de una operación. Pueden ocurrir siempre que el resultado predicho sea +1
pero el valor del resultado real sea -1
, por lo que es un falso positivo . En ese caso, la sesión final resulta ser un día bajista con un precio de cierre más bajo que el de apertura, y obtenemos una pérdida.
Se debe colocar una orden de stop loss para protegernos contra una pérdida máxima que toleraríamos dentro de una operación, y dicha orden se activa cada vez que el precio del activo cae por debajo de un valor fijo que hemos establecido anteriormente.
Si observamos la serie temporal descargada de Yahoo Finance al comienzo de este artículo, todos los días tienen un precio Low
, que es el precio más bajo alcanzado durante ese día. Si establecemos un nivel de stop de -3
puntos lejos del precio de Opening
, y Low - Open = -5
, se activará la orden de stop y la posición abierta se cerrará con una pérdida de -3
puntos en lugar de -5
. Este es un método simple para reducir el riesgo. El siguiente código representa mi función auxiliar para simular una operación con un nivel de parada:
# This is a helper function to trade 1 bar (for example 1 day) with a Buy order at opening session # and a Sell order at closing session. To protect against adverse movements of the price, a STOP order # will limit the loss to the stop level (stop parameter must be a negative number) # each bar must contains the following attributes: # Open, High, Low, Close prices as well as gain = Close - Open and lo = Low - Open def trade_with_stop(bar, slippage = 0, stop=None): """ Given a bar, with a gain obtained by the closing price - opening price it applies a stop limit order to limit a negative loss If stop is equal to None, then it returns bar['gain'] """ bar['gain'] = bar['gain'] - slippage if stop<>None: real_stop = stop - slippage if bar['lo']<=stop: return real_stop # stop == None return bar['gain']
Costos comerciales
Los costos de transacción son gastos incurridos al comprar o vender valores. Los costos de transacción incluyen las comisiones y los diferenciales de los corredores (la diferencia entre el precio que pagó el corredor por un valor y el precio que paga el comprador), y deben tenerse en cuenta si queremos realizar una prueba retrospectiva de nuestra estrategia, de manera similar a un escenario real. El deslizamiento en el comercio de acciones a menudo ocurre cuando hay un cambio en el diferencial. En este ejemplo y para las próximas simulaciones en curso, los costos comerciales se fijan como:
- Deslizamiento = 0,6 puntos
- Comisión = 1$ por cada operación (un turno redondo costará 2$)
Solo para escribir algunos números, si nuestra ganancia bruta fuera de 10 puntos, 1 punto = $25, entonces $250 incluyendo los costos de negociación, nuestra ganancia neta sería (10 - 0.6)*$25 - 2 = $233
.
El siguiente código muestra una simulación de la estrategia comercial anterior con un stop loss de -3 puntos. La curva azul es la curva de rendimientos acumulados. Los únicos costes contabilizados son el deslizamiento (0,6 puntos) y el resultado se expresa en puntos básicos (la misma unidad base de los valores S&P 500 descargados de Yahoo Finance).
SLIPPAGE = 0.6 STOP = -3 trades = testing[testing['predictions'] == 1][('datetime', 'gain', 'ho', 'lo', 'open', 'close')] trades['pnl'] = trades.apply(lambda x: trade_with_stop(x, slippage=SLIPPAGE, stop=STOP)) plot_equity_chart(trades['pnl'],'Decision tree model') print("Slippage is %s, STOP level at %s" % (SLIPPAGE, STOP))
Mean of PnL is 2.162171 Sharpe is 3.502897 Round turns 511 Slippage is 0.6 STOP level at -3
El siguiente código se usa para hacer predicciones de una manera ligeramente diferente. Preste atención al método de predict
que se llama con un parámetro adicional output_type = “probability”
. Este parámetro se usa para devolver probabilidades de valores predichos en lugar de su predicción de clase ( +1
para un resultado predicho positivamente, -1
para un resultado predicho negativamente). Una probabilidad mayor o igual a 0.5
se asocia con un valor previsto de +1
y un valor de probabilidad inferior a 0.5
se relaciona con un valor previsto de -1
. Cuanto mayor sea esa probabilidad, más posibilidades tenemos de predecir un Up Day real.
predictions_prob = decision_tree.predict(testing, output_type = 'probability') # predictions_prob will contain probabilities instead of the predicted class (-1 or +1)
Ahora realizamos una prueba retrospectiva del modelo con una función auxiliar llamada backtest_ml_model
que calcula la serie de rendimientos acumulativos, incluidos el deslizamiento y las comisiones, y traza sus valores. Por brevedad, sin explicar a fondo la función backtest_ml_model
, el detalle importante a resaltar es que en lugar de filtrar aquellos días con un outcome = 1
como hicimos en el ejemplo anterior, ahora filtramos aquellos predictions_prob
iguales o mayores que un threshold = 0.5
, de la siguiente manera:
trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]
Recuerde que la ganancia neta de cada día de negociación es: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION
.
Otra métrica importante utilizada para evaluar la bondad de una estrategia comercial es la reducción máxima . En general, mide la caída individual más grande desde el pico hasta el fondo, en el valor de una cartera invertida. En nuestro caso, es la caída más significativa desde el pico hasta el fondo de la curva de renta variable (solo tenemos un activo en nuestra cartera, S&P 500). Entonces, dado un SArray
de ganancias y pérdidas pnl
, calculamos la reducción como:
drawdown = pnl - pnl.cumulative_max() max_drawdown = min(drawdown)
Dentro de la función auxiliar se calcula backtest_summary
:
- Disposición máxima (en dólares) como se muestra arriba.
- Precisión, con método de
Graphlab.evaluation
. - Precisión, con método de
Graphlab.evaluation
. - Recordemos, con el método de
Graphlab.evaluation
.
En conjunto, el siguiente ejemplo muestra la curva de capital que representa los rendimientos acumulados de la estrategia del modelo, con todos los valores expresados en dólares.
model = decision_tree predictions_prob = model.predict(testing, output_type="probability") THRESHOLD = 0.5 bt_1_1 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree') backtest_summary(bt_1_1)
Mean of PnL is 54.054286 Sharpe is 3.502897 Round turns 511 Name: DecisionTree Accuracy: 0.577373211964 Precision: 0.587084148728 Recall: 0.724637681159 Max Drawdown: -1769.00025
Para aumentar la precisión de los valores pronosticados, en lugar de una probabilidad estándar de 0.5
(50 por ciento), elegimos un valor de umbral más alto, para estar más seguros de que el modelo predice un día de actividad.
THRESHOLD = 0.55 # it's the minimum threshold to predict an Up day so hopefully a good day to trade bt_1_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, MULT=25, SLIPPAGE=0.6, COMMISSION=1, plot_title='DecisionTree') backtest_summary(bt_1_2)
Mean of PnL is 118.244689 Sharpe is 6.523478 Round turns 234 Name: DecisionTree Accuracy: 0.560468140442 Precision: 0.662393162393 Recall: 0.374396135266 Max Drawdown: -1769.00025
Como podemos ver en el gráfico anterior, la curva de acciones es mucho mejor que antes (Sharpe es 6,5 en lugar de 3,5), incluso con menos vueltas redondas.
A partir de este momento, consideraremos todos los próximos modelos con un umbral superior a un valor estándar.
Entrenamiento de un Clasificador Logístico
Podemos aplicar nuestra investigación, como hicimos anteriormente con el árbol de decisiones, en un modelo de clasificador logístico. GraphLab Create tiene la misma interfaz que el objeto Logistic Classifier, y llamaremos al método create
para construir nuestro modelo con la misma lista de parámetros. Además, preferimos predecir el vector de probabilidad en lugar del vector de clase pronosticado (compuesto por +1
para un resultado positivo y -1
para un resultado negativo), por lo que tendríamos un umbral superior a 0.5
para lograr una mayor precisión en nuestro pronóstico
model = gl.logistic_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.6 bt_2_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_2_2)
Mean of PnL is 112.704215 Sharpe is 6.447859 Round turns 426 Name: LogisticClassifier Accuracy: 0.638491547464 Precision: 0.659624413146 Recall: 0.678743961353 Max Drawdown: -1769.00025
En este caso, hay un resumen muy similar a Decision Tree. Después de todo, ambos modelos son clasificadores, solo predicen una clase de resultados binarios ( +1
, -1
).
Entrenamiento de un modelo de regresión lineal
La principal diferencia de este modelo es que trata con valores continuos en lugar de clases binarias, como se mencionó anteriormente. No tenemos que entrenar el modelo con una variable objetivo igual a +1
para los días arriba y -1
para los días abajo , nuestro objetivo debe ser una variable continua. Dado que queremos predecir una ganancia positiva, o en otras palabras, un precio de cierre más alto que el precio de apertura , ahora el objetivo debe ser la columna de ganancia de nuestro conjunto de entrenamiento. Además, la lista de características debe estar compuesta por valores continuos, como los anteriores Open
, Close
, etc.
Para abreviar, no entraré en los detalles de cómo seleccionar las funciones correctas, ya que esto está más allá del alcance de este artículo, que se inclina más a mostrar cómo debemos aplicar diferentes modelos de Machine Learning sobre un conjunto de datos. La lista de parámetros pasados al método de creación son:
-
training
: es un conjunto de entrenamiento que contiene columnas de características y una columna de destino. -
target
: es el nombre de la columna que contiene la variable de destino. -
validation_set
: es un conjunto de datos para monitorear el rendimiento de generalización del modelo. En nuestro caso, no tenemosvalidation_set
. -
features
: es una lista de nombres de columnas de características utilizadas para entrenar el modelo, para este modelo usaremos otro conjunto con respecto a los modelos Clasificadores. -
verbose
: si estrue
, imprimirá información de progreso durante el entrenamiento. -
max_iterations
: es el número máximo de pases permitidos a través de los datos. Más pases sobre los datos pueden dar como resultado un modelo entrenado con mayor precisión.
model = gl.linear_regression.create(training, target='gain', features = l_lr_features, validation_set=None, verbose=False, max_iterations=100) predictions = model.predict(testing) # a linear regression model, predict continuous values, so we need to make an estimation of their # probabilities of success and normalize all values in order to have a vector of probabilities predictions_max, predictions_min = max(predictions), min(predictions) predictions_prob = (predictions - predictions_min)/(predictions_max - predictions_min)
Hasta ahora, tenemos predicciones que son SArray
de ganancias previstas, mientras que forecasts_prob es SArray
con valores predictions
predictions_prob
. Para tener una buena precisión y un cierto número de vueltas, comparable con los modelos anteriores, he elegido un valor de umbral de 0.4
. Para un pronóstico de predictions_prob
inferior a 0.4
, la función auxiliar backtest_linear_model
no abrirá una operación porque se espera un día de caída. De lo contrario, se abrirá una operación.
THRESHOLD = 0.4 bt_3_2 = backtest_linear_model(testing, predictions_prob, target='gain', threshold=THRESHOLD, STOP = -3, plot_title=model.name()) backtest_summary(bt_3_2)
Mean of PnL is 138.868280 Sharpe is 7.650187 Round turns 319 Name: LinearRegression Accuracy: 0.631989596879 Precision: 0.705329153605 Recall: 0.54347826087 Max Drawdown: -1769.00025
Entrenamiento de un árbol potenciado
Como antes entrenamos un árbol de decisión, ahora vamos a entrenar un clasificador de árbol potenciado con los mismos parámetros que se usan para otros modelos de clasificador. Además, establecemos el número de max_iterations = 12
para aumentar el número máximo de iteraciones para impulsar. Cada iteración da como resultado la creación de un árbol adicional. También establecemos un valor de umbral superior a 0.5
para aumentar la precisión.
model = gl.boosted_trees_classifier.create(training, target='outcome', features=l_features, validation_set=None, max_iterations=12, verbose=False) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.7 bt_4_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_4_2)
Mean of PnL is 112.002338 Sharpe is 6.341981 Round turns 214 Name: BoostedTreesClassifier Accuracy: 0.563068920676 Precision: 0.682242990654 Recall: 0.352657004831 Max Drawdown: -1769.00025
Entrenamiento de un bosque aleatorio
Este es nuestro último modelo entrenado, un Random Forest Classifier, compuesto por un conjunto de árboles de decisión. El número máximo de árboles para usar en el modelo se establece en num_trees = 10
, para evitar demasiada complejidad y sobreajuste.
model = gl.random_forest_classifier.create(training, target='outcome', features=l_features, validation_set=None, verbose=False, num_trees = 10) predictions_prob = model.predict(testing, 'probability') THRESHOLD = 0.6 bt_5_2 = backtest_ml_model(testing, predictions_prob, target='outcome', threshold=THRESHOLD, STOP=-3, plot_title=model.name()) backtest_summary(bt_5_2)
Mean of PnL is 114.786962 sharpe is 6.384243 Round turns 311 Name: RandomForestClassifier Accuracy: 0.598179453836 Precision: 0.668810289389 Recall: 0.502415458937 Max Drawdown: -1769.00025
Collecting All the Models Together
Now we can join all the strategies together and see the overall result. It's interesting to see the summary of all Machine Learning models, sorted by their precision.
nombre | accuracy | precision | round turns | sharpe |
LinearRegression | 0.63 | 0.71 | 319 | 7.65 |
BoostedTreesClassifier | 0.56 | 0,68 | 214 | 6.34 |
RandomForestClassifier | 0,60 | 0,67 | 311 | 6.38 |
DecisionTree | 0.56 | 0,66 | 234 | 6.52 |
LogisticClassifier | 0,64 | 0,66 | 426 | 6.45 |
If we collect all the profit and loss for each one of the previous models in the array pnl
, the following chart depicts the equity curve obtained by the sum of each profit and loss, day by day.
Mean of PnL is 119.446463 Sharpe is 6.685744 Round turns 1504 First trading day 2013-04-09 Last trading day 2016-04-22 Total return 179647
Just to give some numbers, with about 3 years of trading, all models have a total gain of about 180,000 dollars. The maximum exposition is 5 CFD contracts in the market, but to reduce the risk they all are closed at the end of each day, so overnight positions are not allowed.
Statistics of the Aggregation of All Models Together
Since each model can open a trade, but we added 5 concurrent models together, during the same day there could be from 1 contract up to 5 CFD contracts. If all models agree to open trades during the same day, there is a high chance to have an Up day predicted. Moreover, we can group by the number of models that open a trade at the same time during the opening session of the day. Then we evaluate precision as a function of the number of concurrent models.
As we can see by the chart depicted above, the precision gets better as the number of models do agree to open a trade. The more models agree, the more precision we get. For instance, with 5 models triggered the same day, the chance to predict an Up day is greater than 85%.
Conclusión
Even in the financial world, Machine Learning is welcomed as a powerful instrument to learn from data and give us great forecasting tools. Each model shows different values of accuracy and precision, but in general, all models can be aggregated to achieve a better result than each one of them taken singularly. GraphLab Create is a great library, easy to use, scalable and able to manage Big Data very quickly. It implements different scientific and forecasting models, and there is a free license for students and Kaggle competitions.
Additional disclosure: This article has been prepared solely for information purposes, and is not an offer to buy or sell or a solicitation of an offer to buy or sell any security or instrument or to participate in any particular trading strategy. Examples presented on these sites are for educational purposes only. Past results are not necessarily indicative of future results.