Análise de moeda com coleções de séries temporais #2 — Cálculo da média móvel simples e da média móvel exponencial
Avalie esse Tutorial
Na postagem anterior, aprendemos como agrupar dados de moeda com base em determinados intervalos de tempo para gerar gráficos de velas para realizar análises de tendências. Neste artigo, aprenderemos como a média móvel pode ser calculada em dados de séries temporais.
A média móvel é um indicador técnico financeiro bem conhecido que é comumente usado sozinho ou em combinação com outros indicadores. Além disso, a média móvel é incluída como parâmetro de outros indicadores técnicos financeiros, como o MACD. A principal razão para usar este indicador é suavizar as atualizações de preços para refletir as mudanças recentes de preços de acordo. Existem muitos tipos de médias móveis, mas aqui vamos nos concentrar em dois deles: Média Móvel Simples (SMA) e Média Móvel Exponencial (EMA).
Esse é o valor médio do preço de uma moeda/ação em um determinado período.
Vamos calcular o SMA para a moeda BTC-USD nos últimos três intervalos de dados, incluindo os dados atuais. Lembre-se de que cada stick no gráfico de velas representa intervalos de cinco minutos. Portanto, para cada intervalo, procuraríamos os três intervalos anteriores.
Primeiro, agruparemos os dados de moeda BTC-USD em intervalos de cinco minutos:
1 db.ticker.aggregate([ 2 { 3 $match: { 4 symbol: "BTC-USD", 5 }, 6 }, 7 { 8 $group: { 9 _id: { 10 symbol: "$symbol", 11 time: { 12 $dateTrunc: { 13 date: "$time", 14 unit: "minute", 15 binSize: 5 16 }, 17 }, 18 }, 19 high: { $max: "$price" }, 20 low: { $min: "$price" }, 21 open: { $first: "$price" }, 22 close: { $last: "$price" }, 23 }, 24 }, 25 { 26 $sort: { 27 "_id.time": 1, 28 }, 29 }, 30 ]);
E teremos o seguinte gráfico de velas:
Temos quatro métricas para cada intervalo e escolheremos o preço de fechamento como o valor numérico para nosso cálculo de média móvel. Estamos interessados apenas em
_id
(um campo aninhado que inclui o símbolo e as informações de tempo) e o preço de fechamento. Portanto, como não estamos interessados em preços altos, baixos e abertos para o cálculo SMA, nós o excluiremos do aggregation pipeline com o estágio de aggregation$project
:1 { 2 $project: { 3 _id: 1, 4 price: "$close", 5 }, 6 }
Depois de agruparmos e cortarmos, teremos o seguinte conjunto de dados:
1 {"_id": {"time": ISODate("20210101T17:00:00"), "symbol" : "BTC-USD"}, "price": 35050} 2 {"_id": {"time": ISODate("20210101T17:05:00"), "symbol" : "BTC-USD"}, "price": 35170} 3 {"_id": {"time": ISODate("20210101T17:10:00"), "symbol" : "BTC-USD"}, "price": 35280} 4 {"_id": {"time": ISODate("20210101T17:15:00"), "symbol" : "BTC-USD"}, "price": 34910} 5 {"_id": {"time": ISODate("20210101T17:20:00"), "symbol" : "BTC-USD"}, "price": 35060} 6 {"_id": {"time": ISODate("20210101T17:25:00"), "symbol" : "BTC-USD"}, "price": 35150} 7 {"_id": {"time": ISODate("20210101T17:30:00"), "symbol" : "BTC-USD"}, "price": 35350}
Depois de termos o conjunto de dados acima, queremos enriquecê-los com o indicador de média móvel simples, conforme mostrado abaixo. Cada intervalo em cada símbolo terá mais um campo (sma) para representar o indicador SMA, incluindo os três intervalos atuais e os últimos:
1 {"_id": {"time": ISODate("20210101T17:00:00"), "symbol" : "BTC-USD"}, "price": 35050, "sma": ?} 2 {"_id": {"time": ISODate("20210101T17:05:00"), "symbol" : "BTC-USD"}, "price": 35170, "sma": ?} 3 {"_id": {"time": ISODate("20210101T17:10:00"), "symbol" : "BTC-USD"}, "price": 35280, "sma": ?} 4 {"_id": {"time": ISODate("20210101T17:15:00"), "symbol" : "BTC-USD"}, "price": 34910, "sma": ?} 5 {"_id": {"time": ISODate("20210101T17:20:00"), "symbol" : "BTC-USD"}, "price": 35060, "sma": ?} 6 {"_id": {"time": ISODate("20210101T17:25:00"), "symbol" : "BTC-USD"}, "price": 35150, "sma": ?} 7 {"_id": {"time": ISODate("20210101T17:30:00"), "symbol" : "BTC-USD"}, "price": 35350, "sma": ?}
Como é calculado? Para o tempo
17:00:00
, o cálculo da SMA é muito simples. Como não temos os três pontos de dados anteriores, podemos considerar o preço existente (35050) naquele momento como média. Se não tivermos três pontos de dados anteriores, podemos obter todas as informações de preço possíveis disponíveis e dividir pelo número de dados de preço.A parte mais difícil é quando temos mais de três pontos de dados anteriores. Se tivermos mais de três pontos de dados anteriores, precisaremos remover os mais antigos. E temos que continuar fazendo isso à medida que tivermos mais dados para um único símbolo. Portanto, calcularemos a média considerando apenas até três pontos de dados anteriores. A tabela abaixo representa o cálculo passo a passo para cada intervalo:
Hora | Cálculo de SMA para a janela (3 pontos de dados anteriores + atuais) |
---|---|
17:00:00 | 35050/1 |
17:05:00 | (35050+35170)/2 |
17:10:00 | (35050+35170+35280)/3 |
17:15:00 | (35050+35170+35280+34910)/4 |
17:20:00 | (35170+35280+34910+35060)/4 *dados de preços mais antigos (35050) descartados do cálculo |
17:25:00 | (35280+34910+35060+35150)/4 *dados de preços mais antigos (35170) descartados do cálculo |
17:30:00 | (34190+35060+35150+35350)/4 *dados de preços mais antigos (35280) descartados do cálculo |
Como você pode ver acima, a janela para o cálculo médio está se movendo à medida que temos mais dados.
Até agora, aprendemos a teoria do cálculo da média móvel. Como podemos usar o MongoDB para fazer esse cálculo para todas as moedas?
MongoDB 5.0 introduziu um novo estágio de agregação,
$setWindowFields
, para executar operações em uma faixa específica de documentos (janela) nas partições definidas. Como ela também suporta o cálculo de média em uma janela por meio do operador$avg
, podemos usá-la facilmente para calcular a Média Móvel Simples:1 { 2 $setWindowFields: { 3 partitionBy: "_id.symbol", 4 sortBy: { "_id.time": 1 }, 5 output: { 6 sma: { 7 $avg: "$price", 8 window: { documents: [-3, 0] }, 9 }, 10 }, 11 }, 12 }
Escolhemos o campo de símbolo como chave de partição. Para cada moeda, temos uma partição, e cada partição terá sua própria janela para processar esses dados específicos da moeda. Portanto, quando quisermos processar dados sequenciais de uma única moeda, não misturaremos os dados da outra moeda.
Depois de definir o campo de partição, aplicamos a classificação para processar os dados de forma ordenada. O campo de partição fornece processamento conjunto de dados de moeda única. No entanto, queremos processar os dados conforme ordenados por tempo. Como vemos na forma como a SMA é calculada no papel, a ordem dos dados é importante e, portanto, precisamos especificar o campo para ordenação.
Depois que as partições forem definidas e classificadas, poderemos processar os dados de cada partição. Geramos mais um campo, “
sma
”, e definimos o método de cálculo deste campo derivado. Aqui definimos três coisas:- O operador que será executado (
$avg
). - O campo (
$price
) em que o operador será executado. - Os limites da janela (
[-3,0]
). [-3
: “start from 3 previous data points”.0]
: “end up with including current data point”.- Também podemos definir o segundo parâmetro da janela como “
current
” para incluir o ponto de dados atual em vez de fornecer um valor numérico.
Mover a janela nos dados particionados e classificados terá a seguinte aparência. Para cada símbolo, teremos uma partição e todos os registros pertencentes a essa partição serão classificados pelas informações de tempo:
Em seguida, teremos o campo
sma
calculado para cada documento no fluxo de entrada. Você pode aplicar $round
operador para cortar para a casa decimal especificada em um estágio de agregação$set
:1 { 2 $set: { 3 sma: { $round: ["$sma", 2] }, 4 }, 5 }
Se reunirmos todos os estágios de agregação, teremos esse pipeline de agregação:
1 db.ticker.aggregate([ 2 { 3 $match: { 4 symbol: "BTC-USD", 5 }, 6 }, 7 { 8 $group: { 9 _id: { 10 symbol: "$symbol", 11 time: { 12 $dateTrunc: { 13 date: "$time", 14 unit: "minute", 15 binSize: 5, 16 }, 17 }, 18 }, 19 high: { $max: "$price" }, 20 low: { $min: "$price" }, 21 open: { $first: "$price" }, 22 close: { $last: "$price" }, 23 }, 24 }, 25 { 26 $sort: { 27 "_id.time": 1, 28 }, 29 }, 30 { 31 $project: { 32 _id: 1, 33 price: "$close", 34 }, 35 }, 36 { 37 $setWindowFields: { 38 partitionBy: "_id.symbol", 39 sortBy: { "_id.time": 1 }, 40 output: { 41 sma: { 42 $avg: "$price", 43 window: { documents: [-3, 0] }, 44 }, 45 }, 46 }, 47 }, 48 { 49 $set: { 50 sma: { $round: ["$sma", 2] }, 51 }, 52 }, 53 ]);
Talvez você queira adicionar mais campos calculados com opções diferentes. Por exemplo, você pode ter dois cálculos SMA com parâmetros diferentes. Um deles pode incluir os últimos três pontos, como já fizemos, e o outro pode incluir os últimos 10 pontos, e você pode querer comparar os dois. Encontre a query abaixo:
1 { 2 $setWindowFields: { 3 partitionBy: "_id.symbol", 4 sortBy: { "_id.time": 1 }, 5 output: { 6 sma_3: { 7 $avg: "$price", 8 window: { documents: [-3, 0] }, 9 }, 10 sma_10: { 11 $avg: "$price", 12 window: { documents: [-10, 0] }, 13 }, 14 }, 15 }, 16 }
Aqui no código acima, definimos dois campos derivados. O campo
sma_3
representa a média móvel para os últimos três pontos de dados e o camposma_10
representa a média móvel para os 10 últimos pontos de dados. Além disso, você pode comparar essas duas médias móveis para assumir uma posição na moeda ou usá-las como parâmetro para seu próprio indicador técnico.O gráfico abaixo mostra dois cálculos de média móvel. A linha com a cor azul representa a média móvel simples com a janela
[-3,0]
. A linha com a cor turquesa representa a média móvel simples com a janela[-10,0]
. Como você pode ver, quando a janela é maior, a reação à mudança de preço fica mais lenta:Você pode até enriquecê-lo ainda mais com as operações adicionais, como covariância, desvio padrão e assim por diante. Verifique as opções completas com suporte aqui. Abordaremos a Média Móvel Exponencial aqui como uma operação adicional.
EMA é uma espécie de média móvel. No entanto, os dados recentes pesam mais. No cálculo da Média Móvel Simples, ponderamos igualmente todos os parâmetros de entrada. No entanto, na Média Móvel Exponencial, com base no parâmetro fornecido, os dados recentes se tornam mais importantes. Portanto, a Média Móvel Exponencial reage mais rápido do que a Média Móvel Simples às atualizações recentes de preços dentro da janela de tamanho semelhante.
$expMovingAvg
foi introduzido no MongoDB 5.0. São necessários dois parâmetros: o nome do campo, que inclui valor numérico para o cálculo, e valor N
ou alpha
. Definiremos o parâmetro N
para especificar quantos pontos de dados anteriores precisam ser avaliados ao calcular a média móvel e, portanto, registros recentes dentro dos pontos de dadosN
terão mais peso do que os dados mais antigos. Você pode consultar a documentação para obter mais informações:1 { 2 $expMovingAvg: { 3 input: "$price", 4 N: 5 5 } 6 }
No diagrama abaixo, a SMA é representada com a linha azul e a EMA é representada com a linha vermelha, e ambas são calculadas por cinco pontos de dados recentes. Você pode ver como a Média Móvel Simples reage mais lentamente às atualizações recentes de preços do que a Média Móvel Exponencial, embora ambas tenham os mesmos registros no cálculo:
MongoDB 5.0, com a introdução da função Windowing, torna os cálculos muito mais fáceis em uma janela. Existem muitos operadores de agregação que podem ser executados em uma janela e vimos
$avg
e $expMovingAvg
neste artigo.Aqui, nos exemplos dados, definimos os limites da janela incluindo os documentos posicionais. Em outras palavras, começamos a incluir documentos de três pontos de dados anteriores ao ponto de dados atual (
documents: [-3,0]
). Você também pode definir um intervalo de documentos em vez de definir a posição.Por exemplo, se a janela for classificada por tempo, você poderá incluir os últimos 30 minutos de dados (independentemente do número de documentos que você tiver), especificando a opção de intervalo da seguinte forma:
range: [-30,0], unit: "minute".
Agora, podemos ter centenas de documentos em a janela, mas sabemos que incluímos apenas os documentos que não tenham mais de 30 minutos do que os dados atuais.Você também pode materializar a saída da query em outra collection por meio dos estágios de aggregation
$out
ou $merge
. Além disso, você pode ativar change stream ou triggerde dados na visualização materializada para acionar automaticamente ações de compra/venda com base no resultado de alterações de indicadores técnicos.