A ascensão da negociação automatizada: máquinas negociando o S&P 500

Publicados: 2022-03-11

Hoje em dia, mais de 60 por cento das atividades de negociação com diferentes ativos (como ações, futuros de índices, commodities) não são mais feitas por traders “seres humanos”, mas dependem de negociação automatizada. Existem programas especializados baseados em algoritmos específicos que compram e vendem ativos automaticamente em diferentes mercados, com o objetivo de obter um retorno positivo no longo prazo.

Neste artigo, mostrarei como prever, com boa precisão, como a próxima negociação deve ser colocada para obter um ganho positivo. Para este exemplo, como ativo subjacente para negociação, selecionei o índice S&P 500, a média ponderada de 500 empresas norte-americanas com maior capitalização. Uma estratégia muito simples de implementar é comprar o índice S&P 500 quando a Wall Street Exchange começar a ser negociada, às 9h30, e vendê-lo na sessão de fechamento às 16h, horário do leste. Se o preço de fechamento do índice for maior que o preço de abertura, há um ganho positivo, enquanto um ganho negativo seria obtido se o preço de fechamento for menor que o preço de abertura. Então a pergunta é: como sabemos se o pregão terminará com um preço de fechamento superior ao preço de abertura? O Machine Learning é uma ferramenta poderosa para realizar uma tarefa tão complexa e pode ser uma ferramenta útil para nos apoiar na decisão de negociação.

Machine Learning é a nova fronteira de muitos aplicativos úteis da vida real. A negociação financeira é uma delas, e é usada com muita frequência nesse setor. Um conceito importante sobre Machine Learning é que não precisamos escrever código para todos os tipos de regras possíveis, como reconhecimento de padrões. Isso ocorre porque cada modelo associado ao Machine Learning aprende com os próprios dados e pode ser usado posteriormente para prever novos dados não vistos.

Isenção de responsabilidade: o objetivo deste artigo é mostrar como treinar métodos de aprendizado de máquina e, nos exemplos de código fornecidos, nem todas as funções são explicadas. Este artigo não tem a intenção de deixar copiar e colar todo o código e executar os mesmos testes fornecidos, pois faltam alguns detalhes que estavam fora do escopo do artigo. Além disso, é necessário conhecimento básico de Python. A principal intenção do artigo é mostrar um exemplo de como o aprendizado de máquina pode ser eficaz para prever compras e vendas no setor financeiro. No entanto, negociar com dinheiro real significa ter muitas outras habilidades, como gerenciamento de dinheiro e gerenciamento de risco. Este artigo é apenas uma pequena parte do “grande quadro”.

Construindo seu primeiro programa de negociação automatizada de dados financeiros

Então, você quer criar seu primeiro programa para analisar dados financeiros e prever a negociação certa? Deixa-me mostrar-te como. Usarei Python para código de aprendizado de máquina e usaremos dados históricos do serviço Yahoo Finance. Como mencionado anteriormente, os dados históricos são necessários para treinar o modelo antes de fazer nossas previsões.

Para começar, precisamos instalar:

  • Python e, em particular, sugiro usar o notebook IPython.
  • Pacote Python do Yahoo Finance (o nome exato é yahoo-finance ) por meio do comando do terminal: pip install yahoo-finance .
  • Uma versão de avaliação gratuita do pacote Machine Learning chamado GraphLab. Sinta-se à vontade para verificar a documentação útil dessa biblioteca.

Observe que apenas uma parte do GraphLab é de código aberto, o SFrame, então para usar toda a biblioteca precisamos de uma licença. Há uma licença gratuita de 30 dias e uma licença não comercial para estudantes ou participantes de competições Kaggle. Do meu ponto de vista, o GraphLab Create é uma biblioteca muito intuitiva e fácil de usar para analisar dados e treinar modelos de Machine Learning.

Cavando no código Python

Vamos nos aprofundar em algum código Python para ver como baixar dados financeiros da Internet. Sugiro usar o notebook IPython para testar o código a seguir, pois o IPython tem muitas vantagens em comparação com um IDE tradicional, especialmente quando precisamos combinar código-fonte, código de execução, dados de tabela e gráficos no mesmo documento. Para uma breve explicação sobre como usar o IPython Notebook, consulte o artigo Introdução ao IPython Notebook.

Então, vamos criar um novo notebook IPython e escrever um código para baixar os preços históricos do índice S&P 500. Observe que, se preferir usar outras ferramentas, você pode começar com um novo projeto Python em seu 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'}

Aqui, hist_quotes é uma lista de dicionários, e cada objeto de dicionário é um dia de negociação com valores Open , High , Low , Close , Adj_close , Volume , Symbol e Date . Durante cada dia de negociação, o preço geralmente muda a partir do preço de abertura Open até o preço de fechamento Close , e atingindo um valor máximo e mínimo High e Low . Precisamos lê-lo e criar listas de cada um dos dados mais relevantes. Além disso, os dados devem ser ordenados pelos valores mais recentes primeiro, então precisamos revertê-los:

 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 empacotar todas as cotações baixadas em um objeto SFrame , que é um quadro de dados baseado em coluna altamente escalável, e é compactado. Uma das vantagens é que também pode ser maior do que a quantidade de RAM porque é suportado por disco. Você pode verificar a documentação para saber mais sobre o SFrame.

Então, vamos armazenar e depois verificar os dados 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)
Fechar data hora Alto baixo abrir volume
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

Agora podemos salvar dados em disco com o método SFrame save , da seguinte forma:

 qq.save(“SP500_daily.bin”) # once data is saved, we can use the following instruction to retrieve it qq = gl.SFrame(“SP500_daily.bin/”)

Vamos ver como é o S&P 500

Para ver como serão os dados carregados do S&P 500, podemos usar o seguinte código:

 import matplotlib.pyplot as plt %matplotlib inline # only for those who are using IPython notebook plt.plot(qq['close'])

A saída do código é o seguinte gráfico:

Gráfico S&P 500, geralmente crescendo ao longo do tempo, conforme renderizado pelo código Python acima.

Treinando alguns modelos de aprendizado de máquina

Adicionando resultado

Como afirmei na parte introdutória deste artigo, o objetivo de cada modelo é prever se o preço de fechamento será superior ao preço de abertura. Assim, nesse caso, podemos obter um retorno positivo ao comprar o ativo subjacente. Portanto, precisamos adicionar uma coluna de outcome em nossos dados, que será a variável de target ou predicted . Cada linha desta nova coluna será:

  • +1 para um dia Up com um preço de Closing superior ao preço de Opening .
  • -1 para um dia de baixa com preço de Closing inferior ao preço de Opening .
 # 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']

Como precisamos avaliar alguns dias antes do último dia de negociação, precisamos atrasar os dados em um ou mais dias. Para esse tipo de operação de atraso , precisamos de outro objeto do pacote GraphLab chamado TimeSeries . TimeSeries tem uma shift de método que atrasa os dados em um certo número de linhas.

 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

Adicionando Preditores

Os preditores são um conjunto de variáveis ​​de características que devem ser escolhidas para treinar o modelo e prever nosso resultado . Assim, a escolha do fator de previsão é crucial, se não o mais importante, componente do previsor.

Apenas para citar alguns exemplos, um fator a ser considerado pode ser se o fechamento de hoje for maior que o fechamento de ontem, e isso pode ser estendido com o fechamento de dois dias anteriores, etc. Uma escolha semelhante pode ser traduzida com o seguinte código:

 ts['feat1'] = ts['close'] > ts_1['close'] ts['feat2'] = ts['close'] > ts_2['close']

Como mostrado acima, adicionei duas novas colunas de recursos, feat1 e feat2 em nosso conjunto de dados ( ts ) contendo 1 se a comparação for verdadeira e 0 caso contrário.

Este artigo pretende dar um exemplo de Machine Learning aplicado ao setor financeiro. Prefiro focar em como os modelos de Machine Learning podem ser usados ​​com dados financeiros, e não entraremos em detalhes sobre como escolher os fatores certos para treinar os modelos. É muito exaustivo explicar por que certos fatores são usados ​​em relação a outros, devido a um aumento considerável na complexidade. Meu trabalho de pesquisa é estudar muitas hipóteses de escolha de fatores para criar um bom preditor. Então, para começar, sugiro que você experimente várias combinações diferentes de fatores, para ver se elas podem aumentar a precisão do 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):]

Treinando um modelo de árvore de decisão

O GraphLab Create possui uma interface muito limpa para implementar modelos de Machine Learning. Cada modelo tem um método create usado para ajustar o modelo com um conjunto de dados de treinamento. Os parâmetros típicos são:

  • training - é um conjunto de treinamento contendo colunas de recursos e uma coluna de destino.
  • target - é o nome da coluna que contém a variável de destino.
  • validation_set - é um conjunto de dados para monitorar o desempenho de generalização do modelo. No nosso caso, não temos validation_set .
  • features - é uma lista de nomes de colunas de features usadas para treinar o modelo.
  • verbose - se true , imprime informações de progresso durante o treinamento.

Enquanto outros parâmetros são típicos do próprio modelo, como:

  • max_depth - é a profundidade máxima de uma árvore.

Com o código a seguir, construímos nossa árvore de decisão:

 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)

Medindo o Desempenho do Modelo Ajustado

A precisão é uma métrica importante para avaliar a bondade do previsor. É o número de previsões corretas dividido pelo número total de pontos de dados. Como o modelo é ajustado com dados de treinamento, a precisão avaliada com o conjunto de treinamento é melhor do que a obtida com um conjunto de teste.

Precisão é a fração de previsões positivas que são positivas. Precisamos que a precisão seja um número mais próximo de 1 , para alcançar uma taxa de vitórias “perfeita”. Nosso decision_tree , como outro classificador do pacote GraphLab Create, tem seu método de evaluate para obter muitas métricas importantes do modelo ajustado.

Recall quantifica a capacidade de um classificador de prever exemplos positivos. Recall pode ser interpretado como a probabilidade de que um exemplo positivo selecionado aleatoriamente seja corretamente identificado pelo classificador. Precisamos que a precisão seja um número mais próximo de 1 , para alcançar uma taxa de vitórias “perfeita”.

O código a seguir mostrará a precisão do modelo ajustado tanto com o conjunto de treinamento quanto com o conjunto de teste:

 decision_tree.evaluate(training)['accuracy'], decision_tree.evaluate(testing)['accuracy'] (0.6077348066298343, 0.577373211963589)

Como mostrado acima, a precisão do modelo com o conjunto de teste é de cerca de 57%, o que é de alguma forma melhor do que jogar uma moeda (50%).

Dados de previsão

O GraphLab Create tem a mesma interface para prever dados de diferentes modelos ajustados. Usaremos o método de predict , que precisa de um conjunto de testes para prever a variável alvo, no nosso caso, outcome . Agora, podemos prever dados do conjunto de testes:

 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)
data hora resultado previsões
05-04-2013 00:00:00 -1 -1
08-04-2013 00:00:00 1 1
09-04-2013 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

Os falsos positivos são casos em que o modelo prevê um resultado positivo, enquanto o resultado real do conjunto de testes é negativo. Vice-versa, os falsos negativos são casos em que o modelo prevê um resultado negativo em que o resultado real do conjunto de teste é positivo.

Nossa estratégia de negociação espera por um resultado positivo previsto para comprar o S&P 500 no preço de Opening e vendê-lo no preço de Closing , então nossa esperança é ter a menor taxa de falsos positivos para evitar perdas. Em outras palavras, esperamos que nosso modelo tenha a maior taxa de precisão .

Como podemos ver, há dois falsos negativos (em 2013-04-10 e 2013-04-11) e dois falsos positivos (em 2013-04-15 e 2013-04-18) dentro dos primeiros dez valores previstos do conjunto de testes.

Com um cálculo simples, considerando este pequeno conjunto de dez previsões:

  • precisão = 6/10 = 0,6 ou 60%
  • precisão = 3/5 = 0,6 ou 60%
  • recordação = 3/5 = 0,6 ou 60%

Observe que geralmente os números acima são diferentes entre si, mas neste caso eles são os mesmos.

Backtesting do modelo

Agora simulamos como o modelo negociaria usando seus valores previstos. Se o resultado previsto for igual a +1 , significa que esperamos um dia Up . Com um dia Up , compramos o índice no início da sessão e vendemos o índice no final da sessão durante o mesmo dia. Por outro lado, se o resultado previsto for igual a -1 , esperamos um dia de baixa, portanto, não negociaremos durante esse dia.

O Lucro e Perda ( pnl ) para uma negociação diária completa, também chamada de round turn , neste exemplo é dada por:

  • pnl = Close - Open (para cada dia de negociação)

Com o código mostrado abaixo, chamo a função auxiliar plot_equity_chart para criar um gráfico com a curva de ganhos cumulativos (curva de patrimônio). Sem ir muito fundo, ele simplesmente obtém uma série de valores de lucros e perdas e calcula a série de somas cumulativas para plotar.

 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') 

Modelo de árvore de decisão, geralmente indo para cima e para a direita, conforme renderizado pelo código Python acima.

 Mean of PnL is 1.843504 Sharpe is 1.972835 Round turns 511

Aqui, Sharpe é o índice anual de Sharpe, um importante indicador da qualidade do modelo de negociação.

A razão de Sharpe é igual à raiz quadrada de 252, depois multiplicada pela média de pnl dividida pelo desvio padrão de pnl.

Considerando os negócios expressos dia a dia, a mean é a média da lista de ganhos e perdas e sd é o desvio padrão. Para simplificar a fórmula descrita acima, considerei um retorno livre de risco igual a 0.

Algumas noções básicas sobre negociação

A negociação do índice requer a compra de um ativo, que é derivado diretamente do índice. Muitas corretoras replicam o índice S&P 500 com um produto derivativo chamado CFD (Contract for Difference), que é um acordo entre duas partes para trocar a diferença entre o preço de abertura e o preço de fechamento de um contrato.

Exemplo : Compre 1 CFD S&P 500 na Open (o valor é 2000), venda-o no Close do dia (o valor é 2020). A diferença, daí o ganho, é de 20 pontos. Se cada ponto tiver um valor de $ 25:

  • O Ganho Bruto é 20 points x $25 = $500 com 1 contrato CFD.

Digamos que a corretora mantenha uma derrapagem de 0,6 pontos para sua própria receita:

  • O ganho líquido é (20 - 0.6) points x $25 = $485 .

Outro aspecto importante a considerar é evitar perdas significativas dentro de uma negociação. Eles podem acontecer sempre que o resultado previsto for +1 , mas o valor do resultado real for -1 , portanto, é um falso positivo . Nesse caso, a sessão final acaba sendo um dia de baixa com um preço de fechamento menor que o de abertura, e temos uma perda.

Uma ordem de stop loss deve ser colocada para proteger contra uma perda máxima que toleraríamos dentro de uma negociação, e tal ordem é acionada sempre que o preço do ativo cair abaixo de um valor fixo que definimos anteriormente.

Se observarmos a série temporal baixada do Yahoo Finance no início deste artigo, todos os dias têm um preço Low , que é o preço mais baixo alcançado durante esse dia. Se definirmos um nível de stop de -3 pontos longe do preço de Opening e Low - Open = -5 a ordem de stop será acionada e a posição aberta será fechada com uma perda de -3 pontos em vez de -5 . Este é um método simples para reduzir o risco. O código a seguir representa minha função auxiliar para simular uma negociação com um nível 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']

Custos de negociação

Os custos de transação são despesas incorridas na compra ou venda de títulos. Os custos de transação incluem comissões e spreads dos corretores (a diferença entre o preço que o negociante pagou por um título e o preço que o comprador paga), e eles precisam ser considerados se quisermos testar nossa estratégia, de forma semelhante a um cenário real. A derrapagem na negociação de ações geralmente ocorre quando há uma mudança no spread. Neste exemplo e nas próximas simulações em andamento, os custos de negociação são fixados como:

  • Deslizamento = 0,6 pontos
  • Comissão = 1$ para cada negociação (uma rodada custará 2$)

Apenas para escrever alguns números, se nosso ganho bruto fosse de 10 pontos, 1 ponto = $ 25, então $ 250 incluindo custos de negociação, nosso ganho líquido seria (10 - 0.6)*$25 - 2 = $233 .

O código a seguir mostra uma simulação da estratégia de negociação anterior com um stop loss de -3 pontos. A curva azul é a curva de retornos cumulativos. Os únicos custos contabilizados são a derrapagem (0,6 ponto), e o resultado é expresso em pontos base (a mesma unidade base dos valores do S&P 500 baixados do 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)) 

Modelo de árvore de decisão, geralmente indo para cima e para a direita, mas com picos menos pronunciados, conforme renderizado pelo código Python acima.

 Mean of PnL is 2.162171 Sharpe is 3.502897 Round turns 511 Slippage is 0.6 STOP level at -3

O código a seguir é usado para fazer previsões de uma maneira um pouco diferente. Por favor, preste atenção ao método de predict que é chamado com um parâmetro adicional output_type = “probability” . Este parâmetro é usado para retornar probabilidades de valores previstos em vez de sua previsão de classe ( +1 para um resultado previsto positivamente, -1 para um resultado previsto negativamente). Uma probabilidade maior ou igual a 0.5 está associada a um valor previsto +1 e um valor de probabilidade menor que 0.5 está relacionado a um valor previsto de -1 . Quanto maior essa probabilidade, mais chance temos de prever um 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)

Agora, testamos o modelo com uma função auxiliar chamada backtest_ml_model , que calcula a série de retornos cumulativos, incluindo derrapagens e comissões, e plota seus valores. Para resumir, sem explicar completamente a função backtest_ml_model , o detalhe importante a ser destacado é que, em vez de filtrar esses dias com um outcome = 1 , como fizemos no exemplo anterior, agora filtramos essas predictions_prob iguais ou maiores que um threshold = 0.5 , como segue:

 trades = testing[predictions_prob>=0.5][('datetime', 'gain', 'ho', 'lo', 'open', 'close')]

Lembre-se que o ganho líquido de cada dia de negociação é: Net gain = (Gross gain - SLIPPAGE) * MULT - 2 * COMMISSION .

Outra métrica importante usada para avaliar a qualidade de uma estratégia de negociação é o Rebaixamento Máximo . Em geral, mede a maior queda única do pico ao fundo, no valor de uma carteira investida. No nosso caso, é a queda mais significativa do pico para o fundo da curva de ações (temos apenas um ativo em nosso portfólio, o S&P 500). Assim, dado um SArray de lucros e perdas pnl , calculamos o rebaixamento como:

 drawdown = pnl - pnl.cumulative_max() max_drawdown = min(drawdown)

Dentro da função auxiliar backtest_summary é calculado:

  • Rebaixamento máximo (em dólares) conforme mostrado acima.
  • Precisão, com o método Graphlab.evaluation .
  • Precisão, com método Graphlab.evaluation .
  • Lembre-se, com o método Graphlab.evaluation .

Juntando tudo, o exemplo a seguir mostra a curva de patrimônio representando os retornos acumulados da estratégia do modelo, com todos os valores expressos em 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) 

Gráfico DecisionTree, com o eixo Y rotulado como "dólares" e indo até 30.000, e o eixo X rotulado como "# de voltas" e estendendo-se para 600, conforme renderizado pelo código Python acima. Os dados gráficos em si são idênticos à renderização anterior.

 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 a precisão dos valores previstos, em vez de uma probabilidade padrão de 0.5 (50%), escolhemos um valor de limite mais alto, para ter mais confiança de que o modelo prevê um dia Up .

 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) 

Gráfico DecisionTree, com o eixo Y rotulado "dólares" e indo até 30.000, e o eixo X rotulado "# de voltas" e estendendo-se para apenas 250 desta vez, conforme renderizado pelo código Python acima. Os dados gráficos em si são semelhantes à renderização anterior, mas ainda mais suavizados.

 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 pelo gráfico acima, a curva de equidade está muito melhor do que antes (Sharpe é 6,5 em vez de 3,5), mesmo com menos rodadas.

A partir deste ponto, consideraremos todos os próximos modelos com um limite superior a um valor padrão.

Treinando um classificador logístico

Podemos aplicar nossa pesquisa, como fizemos anteriormente com a árvore de decisão, em um modelo de Classificador Logístico. O GraphLab Create tem a mesma interface com o objeto Logistic Classifier, e vamos chamar o método create para construir nosso modelo com a mesma lista de parâmetros. Além disso, preferimos prever o vetor de probabilidade em vez do vetor de classe previsto (composto por +1 para um resultado positivo e -1 para um resultado negativo), então teríamos um limite maior que 0.5 para obter uma melhor precisão em nosso previsão.

 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) 

Gráfico LogisticClassifier, com o eixo Y rotulado como "dólares" e desta vez indo para 50.000, e o eixo X rotulado como "# de voltas" e estendendo-se agora para 450, conforme renderizado pelo código Python acima. Os dados gráficos em si são semelhantes à renderização anterior em sua tendência geral.

 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

Neste caso, há um resumo muito semelhante à Árvore de Decisão. Afinal, ambos os modelos são classificadores, eles apenas predizem uma classe de resultados binários ( +1 , -1 ).

Treinando um modelo de regressão linear

A principal diferença deste modelo é que ele lida com valores contínuos ao invés de classes binárias, como mencionado anteriormente. Não precisamos treinar o modelo com uma variável de destino igual a +1 para dias de alta e -1 para dias de baixa , nossa meta deve ser uma variável contínua. Como queremos prever um ganho positivo, ou em outras palavras, um preço de fechamento superior ao preço de abertura , agora o alvo deve ser a coluna de ganho do nosso conjunto de treinamento. Além disso, a lista de recursos deve ser composta por valores contínuos, como o anterior Open , Close , etc.

Para resumir, não entrarei em detalhes de como selecionar os recursos certos, pois isso está além do escopo deste artigo, que está mais inclinado a mostrar como devemos aplicar diferentes modelos de Machine Learning em um conjunto de dados. A lista de parâmetros passados ​​para o método create são:

  • training - é um conjunto de treinamento contendo colunas de recursos e uma coluna de destino.
  • target - é o nome da coluna que contém a variável de destino.
  • validation_set - é um conjunto de dados para monitorar o desempenho de generalização do modelo. No nosso caso, não temos validation_set .
  • features - é uma lista de nomes de colunas de features usadas para treinar o modelo, para este modelo usaremos outro conjunto em relação aos modelos do Classificador.
  • verbose - se true , imprimirá informações de progresso durante o treinamento.
  • max_iterations - é o número máximo de passagens permitidas pelos dados. Mais passagens sobre os dados podem resultar em um modelo treinado com mais precisão.
 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)

Até agora, temos previsões que são SArray de ganhos previstos, enquanto SArray predictions_prob valores de predictions normalizados. Para ter uma boa precisão e um certo número de voltas redondas, comparável aos modelos anteriores, escolhi um valor limite de 0.4 . Para uma predictions_prob menor que 0.4 , a função auxiliar backtest_linear_model não abrirá uma negociação porque um dia de baixa é esperado. Caso contrário, uma negociação será aberta.

 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) 

Gráfico de regressão linear, com o eixo Y rotulado como "dólares" e indo até 45.000, e o eixo X rotulado como "# de voltas" e estendendo-se para 350, conforme renderizado pelo código Python acima. Os dados gráficos em si são novamente semelhantes, mas não exatamente idênticos, à renderização anterior.

 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

Treinando uma Árvore Impulsionada

Como treinamos anteriormente uma árvore de decisão, agora vamos treinar um classificador de árvore impulsionado com os mesmos parâmetros usados ​​para outros modelos de classificador. Além disso, definimos o número de max_iterations = 12 para aumentar o número máximo de iterações para reforço. Cada iteração resulta na criação de uma árvore extra. Também definimos um valor de limite superior a 0.5 para aumentar a precisão.

 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) 

Gráfico BoostedTreesClassifier, com o eixo Y rotulado como "dólares" e indo até 25.000, e o eixo X rotulado como "# de voltas" e estendendo-se para 250, conforme renderizado pelo código Python acima. Os dados gráficos em si são novamente semelhantes à renderização anterior, com um aumento mais acentuado em torno de 175 no eixo X.

 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

Treinando uma Floresta Aleatória

Este é o nosso último modelo treinado, um Random Forest Classifier, composto por um ensemble de árvores de decisão. O número máximo de árvores a serem usadas no modelo é definido como num_trees = 10 , para evitar muita complexidade e overfitting.

 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) 

Gráfico RandomForestClassifier, com o eixo Y rotulado "dólares" e indo até 40.000, e o eixo X rotulado "# of roundturns" e estendendo-se para 350, conforme renderizado pelo código Python acima. Os dados gráficos em si são novamente semelhantes à renderização anterior.

 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.

nome accuracy precisão 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%.

Conclusão

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.

Relacionado: Uma introdução à teoria do aprendizado de máquina e suas aplicações: um tutorial visual com exemplos

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.