![Teste rápido das ideias de negociação no gráfico](https://c.mql5.com/i/articles/overlay.png)
Teste rápido das ideias de negociação no gráfico
Introdução
O sexto Campeonato de Negociação Automatizada (ATC) finalmente começou. A agitação inicial já passou e finalmente podemos relaxar um pouco e examinar os robôs de negociação enviados. Decidi fazer uma pequena pesquisa para encontrar os recursos mais notáveis dos robôs de negociação modernos e definir o que podemos esperar de suas atividades de negociação.
Isso provou ser uma tarefa bem difícil. Por essa razão, meus cálculos não podem ser considerados perfeitamente exatos ou completos, visto que apenas tive acesso às descrições de Expert Advisors e a raros comentários dos desenvolvedores. Entretanto, ainda podemos chegar a algumas conclusões e abaixo estão os resultados dos meus cálculos: 451 Expert Advisors participaram do Campeonato, mas apenas 316 deles contêm alguma descrição significativa. Os desenvolvedores dos EAs restantes preencheram suas descrições com saudações a seus amigos e familiares, mensagens a civilizações extraterrestres ou aplausos para si próprios.
Estratégias mais populares no ATC 2012:
- negociação utilizando diversas construções gráficas (níveis de preços importantes, níveis de suporte e resistência, canais) – 55;
- análise de movimento de preço (para vários períodos de tempo) – 33;
- sistemas de monitoramento de tendência (penso que essas palavras escondem alguma combinação excessivamente otimizada de médias móveis, mas posso estar enganado) :) ) – 31;
- padrões de preços estatísticos – 10:
- arbitragem, análise de correlação de símbolos – 8;
- análise de volatilidade – 8;
- redes neurais – 7;
- análise de candlestick – 5;
- averagers – 5;
- pacotes de estratégias – 5;
- tempo da sessão de negociação – 4;
- gerador de número aleatório – 4;
- negociando as novidades – 3,
- Ondas de Elliott – 2.
É claro que as estratégias de indicador são tradicionalmente as mais populares. É difícil definir o papel de cada indicador específico em um Expert Advisor em particular, mas é possível estimar o número absoluto de seu uso:
- Média Móvel – 75;
- MACD – 54;
- Oscilador Estocástico – 25;
- RSI – 23;
- Bandas de Bollinger – 19;
- Fractais – 8;
- CCI, ATR – 7 indicadores de cada;
- Zigzag, SAR Parabólico – 6 indicadores de cada;
- ADX – 5;
- Impulso – 4;
- indicadores personalizados (isso é intrigante :) ) – 4;
- Ichimoku, AO – 3 indicadores de cada;
- ROC, WPR, StdDev, Volumes – 2 indicadores de cada.
Os dados sugerem as seguintes conclusões - a maioria dos participantes utiliza estratégias de acompanhamento de negociação com indicadores. É possível que eu tenha perdido algo ao coletar os dados e nós veremos o surgimento de algumas personalidades extraordinárias no ramo da negociação automatizada, mas isso parece improvável no momento. Penso que o principal problema é que os iniciantes atraídos pelo mercado, na maioria dos casos, recebem regras em vez de conhecimento.
Por exemplo, aqui estão as regras de uso de MACD, aqui estão os sinais - agora otimize os parâmetros e ganhe dinheiro. E se eles utilizassem os cérebros um pouco? Besteira! Os padrões já foram desenvolvidos! Por que reinventar a roda? Entretanto, frequentemente nos esquecemos de que os indicadores que são tão populares no momento também foram inventados por negociadores como você e eu. Eles também tinham seus padrões e especialidades. Talvez um indicador com o seu nome se tornará padrão em cerca de dez anos.
Eu gostaria de compartilhar o meu método de busca de ideias de negociação, bem como o método que uso para testá-las rapidamente.
Descrição do método
Toda a análise técnica é baseada em um axioma simples - os preços consideram tudo. Mas há um problema - esta afirmação não tem dinâmica. Olhamos para o gráfico e observamos uma imagem estática: o preço realmente considerou tudo. Entretanto, queremos saber o que o preço considerará em um determinado período de tempo futuro e aonde ele irá, para que possamos ter lucro. Os indicadores derivados do preço foram projetados exatamente para prever possíveis movimentos futuros.
Como aprendemos com a física, o derivativo de primeira ordem de magnitude é velocidade. Assim, os indicadores calculam a velocidade de alteração do preço atual. Também sabemos que magnitudes significativas têm inércia que evita mudanças bruscas no valor da velocidade sem a intervenção de forças externas consideráveis. É assim que abordamos gradativamente o conceito de uma tendência - o estado de preço quando o seu derivativo de primeira ordem (velocidade) mantém o seu valor durante o período de tempo em que forças externas (notícias, políticas de bancos centrais, etc.) não afetam o mercado.
Agora vamos voltar ao início - os preços consideram tudo. Para desenvolver novas ideias, devemos examinar o comportamento do preço e seus derivativos no mesmo intervalo de tempo. Apenas uma análise cuidadosa dos gráficos de preço elevará a sua negociação de fé cega ao nível de entendimento verdadeiro.
Isso pode não levar a mudanças imediatas nos resultados de negociação, mas a capacidade de responder a inúmeros porquês terá um papel positivo mais cedo ou mais tarde. Além disso, a análise visual de gráficos e indicadores permitirá que você encontre correlações novas entre os preços e indicadores, as quais passaram totalmente despercebidas pelos desenvolvedores.
Imagine que você encontrou uma nova correlação que aparentemente trabalha a seu favor. Qual o próximo passo? A forma mais fácil é escrever um Expert Advisor e testá-lo em relação a dados históricos, certificando-se de que a sua hipótese esteja correta. Se esse não for o caso, temos que selecionar uma forma comum de otimização de parâmetros. A pior coisa é que não fomos capazes de responder ao porquê. Por que o nosso Expert Advisor causa perdas/lucros? Por que houve uma redução tão grande? Sem as respostas, você não será capaz de implementar sua ideia com eficiência.
Eu realizo as seguintes ações para visualizar os resultados de uma correlação obtida no gráfico:
- Eu crio o indicador necessário, para que ele gere um sinal: -1 para venda e 1 para compra.
- Eu conecto o indicador de balanço que exibe pontos de entrada e saída para o gráfico. O indicador também mostra as mudanças de balanço e participação (em pontos) ao processar o sinal.
- Eu analiso em quais casos e circunstâncias minhas hipóteses são corretas.
O método tem algumas vantagens.
- Primeiramente, o indicador de balanço é totalmente calculado utilizando o método OnCalculate, que proporciona velocidade máxima de cálculo e disponibilidade automática de dados históricos nas séries de cálculo de entrada.
- Em segundo lugar, a adição do sinal ao indicador existente é um passo intermediário entre a criação de um Expert Advisor via Assistente e desenvolvê-lo por conta própria.
- Em terceiro lugar, uma ideia e um resultado final podem ser visualizados em um único gráfico. É evidente que o método apresenta algumas limitações: um sinal está amarrado ao preço de fechamento da barra, o balanço é calculado para o lote constante, não há opções para negociar com ordens pendentes. Entretanto, todas essas limitações podem ser facilmente corrigidas/melhoradas.
Implementação
Vamos desenvolver um indicador de sinal simples para compreender como ele funciona e avaliar a conveniência do método. Já ouvi falar sobre os padrões de candlestick há muito tempo. Então por que não verificar o funcionamento prático deles? Eu selecionei os padrões reversos "martelo" e "estrela cadente" como sinais de compra e venda, respectivamente. As imagens abaixo apresentam o aspecto esquemático deles:
Figura 1. Padrões de candlestick "martelo" (hammer) e "estrela cadente" (shooting star)
Agora, vamos definir as regras de entrada no mercado quando o padrão "martelo" aparece.
- O valor mais baixo do candle deve ser inferior que os valores dos últimos cinco candles;
- O corpo do candle não deve exceder 50% de sua altura total;
- A sombra superior do candle não deve exceder 0% de sua altura total;
- A altura do candle não deve ser inferior a 100% a altura média dos cinco candles anteriores;
- O preço de fechamento do padrão deve ser inferior à Média Móvel de 10 períodos.
Se essas condições estiverem presentes, devemos abrir uma posição longa. As regras são as mesmas para o padrão "estrela cadente". A única diferença é que devemos abrir uma posição curta:
- O valor mais alto do candle deve ser superior aos valores dos últimos cinco candles;
- O corpo do candle não deve exceder 50% de sua altura total;
- A sombra inferior do candle não deve exceder 0% de sua altura total;
- A altura do candle não deve ser inferior a 100% a altura média dos cinco candles anteriores;
- O preço de fechamento do padrão deve ser superior à Média Móvel de 10 períodos.
Eu utilizei negrito para os parâmetros que usei com base em esboços que podem ser otimizados no futuro (se o padrão apresentar resultados aceitáveis). Os limites que desejo implementar permitem limparmos os padrões dos que apresentam aparência inapropriada (pp. 1-3) bem como dos conhecidamente fracos que não podem ser aceitos como sinais.
Ademais, devemos determinar os momentos de saída. Visto que os padrões mencionados aparecem como sinais de inversão de tendência, a tendência existe no momento em que o candle apropriado aparece. Dessa forma, a média móvel que segue o preço também estará presente. O sinal de saída é formado pelo cruzamento do preço e de sua média móvel de 10 períodos.
Agora é hora de programar um pouco. Vamos desenvolver um novo indicador personalizado no Assistente MQL5, nomeá-lo PivotCandles e descrever o seu comportamento. Vamos definir os valores retornados para conectar o indicador de balanço:
- -1 – abrir uma posição de venda;
- -2 – fechar uma posição de compra;
- 0 – sem sinal;
- 1 – abrir posição de compra;
- 2 – fechar posição de venda.
Como você sabe, os verdadeiros programadores não procuram caminhos fáceis. Eles procuram os mais fáceis de todos. :) Eu não sou uma exceção. Enquanto ouvia música com meus fones de ouvido e bebia café aromático, criei o arquivo com a classe a ser implementada em um indicador e em um Expert Advisor (caso eu decida desenvolvê-lo com base no indicador). É possível que ele até mesmo possa ser modificado para outros padrões de candlestick. O código não tem nada absolutamente novo. Acredito que os comentários implementados no código cobrem todas as perguntas possíveis.
//+------------------------------------------------------------------+ //| PivotCandlesClass.mqh | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "http://www.mql5.com" //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input int iMaxBodySize = 50; // Maximum candle body, % input int iMaxShadowSize = 0; // Maximum allowed candle shadow, % input int iVolatilityCandlesCount = 5; // Number of previous bars for calculation of an average volatility input int iPrevCandlesCount = 5; // Number of previous bars, for which the current bar should be an extremum input int iVolatilityPercent = 100; // Correlation of a signal candle with a previous volatility, % input int iMAPeriod = 10; // Period of a simple signal moving average //+------------------------------------------------------------------+ //| Class definition | //+------------------------------------------------------------------+ class CPivotCandlesClass { private: MqlRates m_candles[]; // Array for storing the history necessary for calculations int m_history_depth; // Array length for storing the history int m_handled_candles_count; // Number of the already processed candles double m_ma_value; // Current calculated moving average value double m_prev_ma_value; // Previous calculated moving average value bool m_is_highest; // Check if the current candle is the highest one bool m_is_lowest; // Check if the current candle is the lowest one double m_volatility; // Average volatility int m_candle_pattern; // Current recognized pattern void PrepareArrayForNewCandle(); // Prepare the array for accepting the new candle int CheckCandleSize(MqlRates &candle); // Check the candle for conformity with patterns void PrepareCalculation(); protected: int DoAnalizeNewCandle(); // Calculation function public: void CPivotCandlesClass(); void CleanupHistory(); // Clean up all calculation variables double MAValue() {return m_ma_value;} // Current value of the moving average int AnalizeNewCandle(MqlRates& candle); int AnalizeNewCandle( const datetime time, const double open, const double high, const double low, const double close, const long tick_volume, const long volume, const int spread ); }; //+------------------------------------------------------------------+ //| CPivotCandlesClass | //+------------------------------------------------------------------+ //| Class initialization | //+------------------------------------------------------------------+ void CPivotCandlesClass::CPivotCandlesClass() { // History depth should be enough for all calculations m_history_depth = (int)MathMax(MathMax( iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod); m_handled_candles_count = 0; m_prev_ma_value = 0; m_ma_value = 0; ArrayResize(m_candles, m_history_depth); } //+------------------------------------------------------------------+ //| CleanupHistory | //+------------------------------------------------------------------+ //| Clean up the candle buffer for recalculation | //+------------------------------------------------------------------+ void CPivotCandlesClass::CleanupHistory() { // Clean up the array ArrayFree(m_candles); ArrayResize(m_candles, m_history_depth); // Null calculation variables m_handled_candles_count = 0; m_prev_ma_value = 0; m_ma_value = 0; } //+-------------------------------------------------------------------+ //| AnalizeNewCandle | //+-------------------------------------------------------------------+ //| Preparations for analyzing the new candle and the analysis itself | //| based on candle's separate parameter values | //+-------------------------------------------------------------------+ int CPivotCandlesClass::AnalizeNewCandle( const datetime time, const double open, const double high, const double low, const double close, const long tick_volume, const long volume, const int spread ) { // Prepare the array for the new candle PrepareArrayForNewCandle(); // Fill out the current value of the candle m_candles[0].time = time; m_candles[0].open = open; m_candles[0].high = high; m_candles[0].low = low; m_candles[0].close = close; m_candles[0].tick_volume = tick_volume; m_candles[0].real_volume = volume; m_candles[0].spread = spread; // Check if there is enough data for calculation if (m_handled_candles_count < m_history_depth) return 0; else return DoAnalizeNewCandle(); } //+-------------------------------------------------------------------+ //| AnalizeNewCandle | //+-------------------------------------------------------------------+ //| Preparations for analyzing the new candle and the analysis itself | //| based on the received candle | //+-------------------------------------------------------------------+ int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle) { // Prepare the array for the new candle PrepareArrayForNewCandle(); // Add the candle m_candles[0] = candle; // Check if there is enough data for calculation if (m_handled_candles_count < m_history_depth) return 0; else return DoAnalizeNewCandle(); } //+------------------------------------------------------------------+ //| PrepareArrayForNewCandle | //+------------------------------------------------------------------+ //| Prepare the array for the new candle | //+------------------------------------------------------------------+ void CPivotCandlesClass::PrepareArrayForNewCandle() { // Shift the array by one position to write the new value there ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1); // Increase the counter of added candles m_handled_candles_count++; } //+------------------------------------------------------------------+ //| CalcMAValue | //+------------------------------------------------------------------+ //| Calculate the current values of the Moving Average, volatility | //| and the value extremality | //+------------------------------------------------------------------+ void CPivotCandlesClass::PrepareCalculation() { // Store the previous value m_prev_ma_value = m_ma_value; m_ma_value = 0; m_is_highest = true; // check if the current candle is the highest one m_is_lowest = true; // check if the current candle is the lowest one m_volatility = 0; // average volatility double price_sum = 0; // Variable for storing the sum for (int i=0; i<m_history_depth; i++) { if (i<iMAPeriod) price_sum += m_candles[i].close; if (i>0 && i<=iVolatilityCandlesCount) m_volatility += m_candles[i].high - m_candles[i].low; if (i>0 && i<=iPrevCandlesCount) { m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high); m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low); } } m_ma_value = price_sum / iMAPeriod; m_volatility /= iVolatilityCandlesCount; m_candle_pattern = CheckCandleSize(m_candles[0]); } //+------------------------------------------------------------------+ //| CheckCandleSize | //+------------------------------------------------------------------+ //| Check if the candle sizes comply with the patterns | //| The function returns: | //| 0 - if the candle does not comply with the patterns | //| 1 - if "hammer" pattern is detected | //| -1 - if "shooting star" pattern is detected | //+------------------------------------------------------------------+ int CPivotCandlesClass::CheckCandleSize(MqlRates &candle) { double candle_height=candle.high-candle.low; // candle's full height double candle_body=MathAbs(candle.close-candle.open); // candle's body height // Check if the candle has a small body if(candle_body/candle_height*100.0>iMaxBodySize) return 0; double candle_top_shadow=candle.high-MathMax(candle.open,candle.close); // candle upper shadow height double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // candle bottom shadow height // If the upper shadow is very small, that indicates the "hammer" pattern if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize) return 1; // If the bottom shadow is very small, that indicates the "shooting star" pattern else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize) return -1; else return 0; } //+------------------------------------------------------------------+ //| DoAnalizeNewCandle | //+------------------------------------------------------------------+ //| Real analysis of compliance with the patterns | //+------------------------------------------------------------------+ int CPivotCandlesClass::DoAnalizeNewCandle() { // Prepare data for analyzing the current situation PrepareCalculation(); // Process prepared data and set the exit signal int signal = 0; /////////////////////////////////////////////////////////////////// // EXIT SIGNALS // /////////////////////////////////////////////////////////////////// // If price crosses the moving average downwards, short position is closed if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value) signal = 2; // If price crosses the moving average upwards, long position is closed else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value) signal = -2; /////////////////////////////////////////////////////////////////// // ENTRY SIGNALS // /////////////////////////////////////////////////////////////////// // Check if the minimum volatility condition is met if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility) { // Checks for "shooting star" pattern if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value) signal = -1; // Checks for "hammer" pattern else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value) signal = 1; } return signal; } //+------------------------------------------------------------------+
Podemos perceber que toda a parte de cálculo é realizada pela classe CPivotCandlesClass. Considera-se boa prática de programação a separação da parte de cálculo da parte visual e eu faço o máximo esforço para seguir essa recomendação. Os benefícios não demoram a chegar - segue abaixo o código do próprio indicador:
//+------------------------------------------------------------------+ //| PivotCandles.mq5 | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property indicator_chart_window // Use four buffers, while drawing two #property indicator_buffers 4 #property indicator_plots 2 //--- plot SlowMA #property indicator_label1 "SlowMA" #property indicator_type1 DRAW_LINE #property indicator_color1 clrAliceBlue #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot ChartSignal #property indicator_label2 "ChartSignal" #property indicator_type2 DRAW_COLOR_ARROW #property indicator_color2 clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue #property indicator_style2 STYLE_SOLID #property indicator_width2 3 #include <PivotCandlesClass.mqh> //+------------------------------------------------------------------+ //| Common arrays and structures | //+------------------------------------------------------------------+ //--- Indicator buffers double SMA[]; // Values of the Moving Average double Signal[]; // Signal values double ChartSignal[]; // Location of signals on the chart double SignalColor[]; // Signal color array //--- Calculation class CPivotCandlesClass PivotCandlesClass; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,SMA,INDICATOR_DATA); SetIndexBuffer(1,ChartSignal,INDICATOR_DATA); SetIndexBuffer(2,SignalColor,INDICATOR_COLOR_INDEX); SetIndexBuffer(3,Signal,INDICATOR_CALCULATIONS); //--- set 0 as an empty value PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0); return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { // If there have not been calculations yet or (!) the new history is uploaded, clean up the calculation object if (prev_calculated == 0) PivotCandlesClass.CleanupHistory(); int end_calc_edge = rates_total-1; if (prev_calculated >= end_calc_edge) return end_calc_edge; for(int i=prev_calculated; i<end_calc_edge; i++) { int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]); Signal[i] = signal; SMA[i] = PivotCandlesClass.MAValue(); // Signals are processed, display them on the chart // Set the location of our signals... if (signal < 0) ChartSignal[i]=high[i]; else if (signal > 0) ChartSignal[i]=low[i]; else ChartSignal[i]=0; // .. as well as their color // Signals have a range of [-2..2], while color indices - [0..4]. Align them SignalColor[i]=signal+2; } // Set the Moving Average value similar to the previous one to prevent it from sharp fall SMA[end_calc_edge] = SMA[end_calc_edge-1]; //--- return value of prev_calculated for next call return(end_calc_edge); } //+------------------------------------------------------------------+
O indicador está pronto. Agora, vamos testá-lo em qualquer um dos gráficos. Para fazer isso, instale o indicador compilado no gráfico. Após isso, veremos algo similar ao apresentado na imagem abaixo.
Figura 2. Indicador dos padrões de candlestick "martelo" e "estrela cadente"
Os pontos coloridos indicam possíveis entradas e saídas de mercado. As cores são selecionadas da seguinte forma:
- vermelho escuro – vender;
- azul escuro – comprar;
- vermelho claro – fechar posição longa;
- vermelho claro – fechar posição curta;
Sinais de fechamento são formados sempre que o preço atinge a sua média móvel. O sinal é ignorado caso não houvesse posições naquele momento.
Agora, vamos ao tópico principal deste artigo. Nós temos um indicador com o buffer de sinal gerando apenas alguns sinais determinados. Vamos exibir em uma janela separada do mesmo gráfico o quanto esses sinais podem ser lucrativos ou causar prejuízos se realmente forem seguidos. O indicador foi desenvolvido especialmente para esse caso. Ele pode se conectar a outro indicador e abrir/fechar posições virtuais dependendo dos sinais que estão sendo recebidos.
Da mesma forma que fizemos com o indicador anterior, devemos separar o código em duas partes - cálculo e visual. Segue abaixo o resultado de uma noite sem dormir, mas espero que valha a pena. :)
//+------------------------------------------------------------------+ //| BalanceClass.mqh | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "http://www.mql5.com" //+------------------------------------------------------------------+ //| Common structures | //+------------------------------------------------------------------+ // Structure for returning calculation results // using only return command; struct BalanceResults { double balance; double equity; }; //+------------------------------------------------------------------+ //| Common function | //+------------------------------------------------------------------+ // Function for searching for the indicator handle by its name int FindIndicatorHandle(string _name) { // Receive the number of open charts int windowsCount = (int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); // Search all of them for(int w=windowsCount-1; w>=0; w--) { // How many indicators are attached to the current chart int indicatorsCount = ChartIndicatorsTotal(0,w); // Search by all chart indicators for(int i=0;i<indicatorsCount;i++) { string name = ChartIndicatorName(0,w,i); // If such an indicator is found, return its handle if (name == _name) return ChartIndicatorGet(0,w,name); } } // If there is no such an indicator, return the incorrect handle return -1; } //+------------------------------------------------------------------+ //| Base calculation class | //+------------------------------------------------------------------+ class CBaseBalanceCalculator { private: double m_position_volume; // Current open position volume double m_position_price; // Position opening price double m_symbol_points; // Value of one point for the current symbol BalanceResults m_results; // Calculation results public: void CBaseBalanceCalculator(string symbol_name = ""); void Cleanup(); BalanceResults Calculate( const double _prev_balance, const int _signal, const double _next_open, const double _next_spread ); }; //+------------------------------------------------------------------+ //| CBaseBalanceCalculator | //+------------------------------------------------------------------+ void CBaseBalanceCalculator::CBaseBalanceCalculator(string symbol_name = "") { // Clean up state variables Cleanup(); // Define point size (because we will calculate the profit in points) if (symbol_name == "") m_symbol_points = SymbolInfoDouble(Symbol(), SYMBOL_POINT); else m_symbol_points = SymbolInfoDouble(symbol_name, SYMBOL_POINT); } //+------------------------------------------------------------------+ //| Cleanup | //+------------------------------------------------------------------+ //| Clean up data on positions and prices | //+------------------------------------------------------------------+ void CBaseBalanceCalculator::Cleanup() { m_position_volume = 0; m_position_price = 0; } //+------------------------------------------------------------------+ //| Calculate | //+------------------------------------------------------------------+ //| Main calculation block | //+------------------------------------------------------------------+ BalanceResults CBaseBalanceCalculator::Calculate( const double _prev_balance, const int _signal, const double _next_open, const double _next_spread ) { // Clean up the output structure from the previous values ZeroMemory(m_results); // Initialize additional variables double current_price = 0; // current price (bid or ask depending on position direction) double profit = 0; // profit calculated value // If there was no signal, the balance remains the same if (_signal == 0) m_results.balance = _prev_balance; // the signal coincides with the direction or no positions are opened yet else if (_signal * m_position_volume >= 0) { // Position already exists, the signal is ignored if (m_position_volume != 0) // Balance is not changed m_results.balance = _prev_balance; // No positions yet, buy signal else if (_signal == 1) { // Calculate current ASK price, recalculate price, volume and balance current_price = _next_open + _next_spread * m_symbol_points; m_position_price = (m_position_volume * m_position_price + current_price) / (m_position_volume + 1); m_position_volume = m_position_volume + 1; m_results.balance = _prev_balance; } // No positions yet, sell signal else if (_signal == -1) { // Calculate current BID price, recalculate price, volume and balance current_price = _next_open; m_position_price = (-m_position_volume * m_position_price + current_price) / (-m_position_volume + 1); m_position_volume = m_position_volume - 1; m_results.balance = _prev_balance; } else m_results.balance = _prev_balance; } // Position is set already, the opposite direction signal is received else { // buy signal/close sell position if (_signal > 0) { // Close position by ASK price, recalculate profit and balance current_price = _next_open + _next_spread * m_symbol_points; profit = (current_price - m_position_price) / m_symbol_points * m_position_volume; m_results.balance = _prev_balance + profit; // If there is a signal for opening a new position, open it at once if (_signal == 1) { m_position_price = current_price; m_position_volume = 1; } else m_position_volume = 0; } // sell signal/close buy position else { // Close position by BID price, recalculate profit and balance current_price = _next_open; profit = (current_price - m_position_price) / m_symbol_points * m_position_volume; m_results.balance = _prev_balance + profit; // If there is a signal for opening a new position, open it at once if (_signal == -1) { m_position_price = current_price; m_position_volume = -1; } else m_position_volume = 0; } } // Calculate the current equity if (m_position_volume > 0) { current_price = _next_open; profit = (current_price - m_position_price) / m_symbol_points * m_position_volume; m_results.equity = m_results.balance + profit; } else if (m_position_volume < 0) { current_price = _next_open + _next_spread * m_symbol_points; profit = (current_price - m_position_price) / m_symbol_points * m_position_volume; m_results.equity = m_results.balance + profit; } else m_results.equity = m_results.balance; return m_results; } //+------------------------------------------------------------------+
A classe de cálculo está pronta. Agora, devemos implementar a exibição do indicador para ver como ele funciona.
//+------------------------------------------------------------------+ //| Balance.mq5 | //| Copyright 2012, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2012, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 4 #property indicator_plots 3 #property indicator_level1 0.0 #property indicator_levelcolor Silver #property indicator_levelstyle STYLE_DOT #property indicator_levelwidth 1 //--- plot Balance #property indicator_label1 "Balance" #property indicator_type1 DRAW_COLOR_HISTOGRAM #property indicator_color1 clrBlue,clrRed #property indicator_style1 STYLE_DOT #property indicator_width1 1 //--- plot Equity #property indicator_label2 "Equity" #property indicator_type2 DRAW_LINE #property indicator_color2 clrLime #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- plot Zero #property indicator_label3 "Zero" #property indicator_type3 DRAW_LINE #property indicator_color3 clrGray #property indicator_style3 STYLE_DOT #property indicator_width3 1 #include <BalanceClass.mqh> //+------------------------------------------------------------------+ //| Input and global variables | //+------------------------------------------------------------------+ input string iParentName = ""; // Indicator name for balance calculation input int iSignalBufferIndex = -1; // Signal buffer's index number input datetime iStartTime = D'01.01.2012'; // Calculation start date input datetime iEndTime = 0; // Calculation end date //--- Indicator buffers double Balance[]; // Balance values double BalanceColor[]; // Color index for drawing the balance double Equity[]; // Equity values double Zero[]; // Zero value for histogram's correct display //--- Global variables double Signal[1]; // Array for receiving the current signal int parent_handle; // Indicator handle, the signals of which are to be used CBaseBalanceCalculator calculator; // Object for calculating balance and equity //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Binding indicator buffers SetIndexBuffer(0,Balance,INDICATOR_DATA); SetIndexBuffer(1,BalanceColor,INDICATOR_COLOR_INDEX); SetIndexBuffer(2,Equity,INDICATOR_DATA); SetIndexBuffer(3,Zero,INDICATOR_DATA); // Search for indicator handle by its name parent_handle = FindIndicatorHandle(iParentName); if (parent_handle < 0) { Print("Error! Parent indicator not found"); return -1; } return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { // Set the borders for calculating the indicator int start_index = prev_calculated; int end_index = rates_total-1; // Calculate balance and equity values for(int i=start_index; i<end_index; i++) { // Check if the balance calculation corresponds the interval if (time[i] < iStartTime) { Balance[i] = 0; Equity[i] = 0; continue; } if (time[i] > iEndTime && iEndTime != 0) { Equity[i] = (i==0) ? 0 : Equity[i-1]; Balance[i] = Equity[i]; continue; } // Request a signal from the parent indicator if(CopyBuffer(parent_handle,iSignalBufferIndex,time[i],1,Signal)==-1) // Copy the indicator main line data { Print("Data copy error: " + IntegerToString(GetLastError())); return(0); // Finish the function operation and send indicator for the full recalculation } // Initialize balance and equity calculation // Since the signal is formed when the candle is closing, we will be able // to perform any operation only at the next candle's opening price BalanceResults results = calculator.Calculate(i==0?0:Balance[i-1], (int)Signal[0], open[i+1], spread[1+1]); // Fill out all indicator buffers Balance[i] = results.balance; Equity[i] = results.equity; Zero[i] = 0; if (Balance[i] >= 0) BalanceColor[i] = 0; else BalanceColor[i] = 1; } // Fill out buffers for the last candle Balance[end_index] = Balance[end_index-1]; Equity[end_index] = Equity[end_index-1]; BalanceColor[end_index] = BalanceColor[end_index-1]; Zero[end_index] = 0; return rates_total; } //+------------------------------------------------------------------+
Finalmente terminou! Vamos compilá-lo e examinar os resultados.
Instruções de uso
Para avaliar a operação de nosso indicador recém-desenvolvido, ele deve ser anexado ao gráfico que contém pelo menos um indicador de sinal. Se você seguiu todos os passos, já temos um indicador – PivotCandles. Agora temos que configurar os parâmetros de entrada. Vamos ver o que devemos especificar:
- Nome do indicador para cálculo de balanço (string) – devemos ter em mente que a ligação do indicador de balanço é realizada pelo nome. Assim, este campo é obrigatório.
- Número do índice do buffer de sinal (inteiro) – outro parâmetro crítico. O indicador de sinal poderá gerar diversos sinais de acordo com um algoritmo previamente definido. Dessa forma, o indicador de balanço deve ter os dados relativos ao sinal do buffer que ele irá calcular.
- Data de início do cálculo (data/hora) – data inicial do cálculo do balanço.
- Data de término do cálculo (data/hora) – data final do cálculo do balanço. Se a data não for selecionada (igual a zero), o cálculo será realizado até a última barra.
A Figura 3 mostra a configuração dos primeiros dois parâmetros para anexar o indicador de balanço ao terceiro buffer do indicador PivotCandles. Os dois parâmetros restantes podem ser configurados de acordo com a sua preferência.
Figura 3. Parâmetros do indicador de balanço
Se todos os passos anteriores foram executados corretamente, você deve observar uma imagem muito similar à que é exibida abaixo:
Figura 4. Curvas de balanço e participação geradas com o uso de sinais de indicador PivotCandles
Agora, podemos experimentar diferentes períodos de tempo e símbolos e encontrar as entradas de mercado mais lucrativas e as que causam mais prejuízos. Deve-se adicionar que essa abordagem ajuda a encontrar as correlações de mercado que afetam os seus resultados de negociação.
Originalmente, eu queria comparar o tempo gasto no teste do Expert Advisor com base nos mesmos sinais com o tempo gasto utilizando o método descrito acima. Mas abandonei a ideia, visto que o recálculo do indicador demora cerca de um segundo. Um tempo tão curto certamente ainda não é alcançado pelo Expert Advisor com seus algoritmos de upload de histórico e de geração de ticks.
Conclusão
O método descrito acima é muito rápido. Além disso, ele proporciona clareza no teste de indicadores que geram sinais de posição aberta/fechada. Ele permite que os negociadores analisem os sinais e as respostas do depósito a eles em uma única janela de gráfico. Mas precisamos estar cientes de algumas limitações que ele ainda possui:
- O buffer de sinal do indicador analisado deve ser preparado preliminarmente;
- Os sinais estão ligados à hora de abertura da nova barra;
- Nenhum MM ao calcular o balanço;
Entretanto, apesar dessas fraquezas, espero que os benefícios sejam mais significativos e que este método de teste obtenha o seu lugar entre as outras ferramentas projetadas para análise de comportamento de mercado e processamento de sinais gerados pelo mercado.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/505
![Sinais de negociação no MetaTrader 5: uma melhor alternativa às contas PAMM!](https://c.mql5.com/2/0/signal_avatar.png)
![Resultados da MetaTrader AppStore para o terceiro trimestre de 2013](https://c.mql5.com/2/0/avatar3.png)
![Fundamentos básicos da programação MQL5: arrays](https://c.mql5.com/2/0/array_avatar.png)
![Como ganhar dinheiro com o AppStore MetaTrader e Serviços de Sinal de Comércio se você não é um vendedor ou fornecedor](https://c.mql5.com/2/0/mql5_share_avatar.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso