
Simulación Rápida de Ideas de Trading en el Gráfico
Introducción
El sexto Automated Trading Championship (Campeonato de Trading Automatizado, o ATC 2012) ya ha empezado, por fin. Toda la emoción inicial se ha disipado, y ahora finalmente podemos relajarnos un poco y examinar los robots de trading presentados. He decidido investigar un poco para descubrir las cualidades más notables de los robots de trading modernos y definir qué podemos esperar de sus operaciones de trading.
Una tarea que no resultó nada fácil. Por tanto, mis cálculos no se pueden considerar totalmente precisos ni completos con el material del que disponía de las descripciones del Asesor Experto y los comentarios poco frecuentes de los creadores. No obstante, podemos extraer algunas conclusiones, y debajo puede ver los resultados de mis cálculos: 451 Asesores Expertos participan en el campeonato, pero solo 316 de ellos contienen descripciones significativas. Los creadores de los demás Asesores llenaron los campos de descripción con dedicatorias a sus amigos y familia, mensajes a civilizaciones extraterrestres o halagos por su propia labor.
Las estrategias más populares del ATC 2012:
- trading usando varias construcciones gráficas (niveles de precio importantes, niveles de soporte-resistencia, canales) – 55;
- análisis de movimiento de precios (para varios intervalos cronológicos) – 33;
- sistemas de rastreo de tendencias (supongo que este increíble concepto oculta alguna combinación super-optimizada de medias móviles, pero podría estar equivocado :) ) – 31;
- patrones de precio estadísticos – 10:
- arbitración, análisis de correlación de símbolos – 8;
- análisis de volatilidad – 8;
- redes neuronales – 7;
- análisis de velas – 5;
- mediadores – 5;
- conjuntos de estrategia – 5;
- tiempo de sesión de trading – 4;
- generador de números aleatorio – 4;
- trading conforme a las noticias de actualidad – 3,
- Ondas de Elliott – 2.
Las estrategias de indicador son tradicionalmente las más populares, por supuesto. Es difícil definir el papel de cada indicador particular en un Asesor Experto concreto, pero es posible calcular el número absoluto de su uso:
- Media Móvil (MA) – 75;
- MACD – 54;
- Oscilador Estocástico – 25;
- RSI – 23;
- Bollinger Bands – 19;
- Fractals – 8;
- CCI, ATR – 7 indicadores cada uno;
- Zigzag, SAR Parabólico – 6 indicadores cada uno;
- ADX – 5;
- Momentum – 4;
- indicadores personalizados (cuánta intriga :) ) – 4;
- Ichimoku, AO – 3 indicadores cada uno;
- ROC, WPR, StdDev, Volumes – 2 indicadores cada uno.
Los datos sugieren las siguientes conclusiones: la mayoría de los participantes practican trading siguiendo estrategias con indicadores. Es posible que me dejara algo al recopilar los datos, y que, al contrario de mis suposiciones, veamos la llegada de varios genios en el campo del trading automatizado, pero creo que las posibilidades son escasas por ahora. En mi opinión, el principal problema es que los recién llegados, atraídos por el mercado, en la mayoría de los casos adquieren reglas en lugar de conocimiento.
Por ejemplo: “Aquí están las reglas del uso de MACD, aquí están las señales: ahora optimice los parámetros y gane dinero". ¿Dónde quedó el usar la cabeza un poco? ¡No tiene sentido! ¡Los estándares ya se han desarrollado! ¿Por qué seguir dándoles vueltas? No obstante, a menudo olvidamos que los indicadores que son tan populares ahora mismo también los inventaron traders como usted y como yo. Ellos también tenían sus estándares y autoridades. A lo mejor, un nuevo indicador con su nombre se convertirá en uno estándar en diez años.
Me gustaría compartir mi método de búsqueda de ideas de trading, así como el método que uso para la simulación rápida de estas ideas.
Descripción del Método
Todos los análisis técnicos se basan en un simple axioma: los precios consideran todo. Pero hay un problema: a esta declaración le falta dinamismo. Al mirar el gráfico, vemos una imagen estática: el precio lo ha considerado realmente todo. Sin embargo, queremos saber qué considerará el precio en un determinado periodo de tiempo en el futuro y hacia dónde irá para poder sacar un beneficio. Los indicadores derivados del precio se han diseñado exactamente para predecir posibles movimientos futuros.
Como ya sabemos por la física, la derivada de primer orden de la magnitud es la velocidad. Por tanto, los indicadores calculan la velocidad de cambio del precio actual. También sabemos que magnitudes significativas tienen una inercia para evitar en la velocidad cambios drásticos de su valor sin la intervención de fuerzas externas considerables. Así es como enfocamos gradualmente el concepto de una tendencia: el estado del precio cuando su derivada de primer orden (velocidad) mantiene su valor durante el período de tiempo en el que fuerzas externas (noticias, políticas de los bancos centrales, etc) no afectan al mercado.
Pero volvamos al principio: los precios consideran todo. Para desarrollar nuevas ideas deberíamos examinar el comportamiento del precio y sus derivadas en el mismo intervalo cronológico. Solo el análisis detallado de gráficos de precio llevará a su estrategia de trading de la fe ciega al nivel del entendimiento genuino.
Puede que esto no lleve a cambios inmediatos en los resultados de trading, pero la capacidad de responder a numerosas preguntas esenciales le reportará ventajas antes o después. Además, el análisis visual de gráficos e indicadores le permitirá encontrar correlaciones nuevas entre precios e indicadores completamente imprevistas por sus creadores.
Imagine que encuentra una nueva correlación que aparentemente juega a su favor. ¿Qué viene después? La forma más fácil es escribir un Asesor Experto y simularlo en datos del historial, asegurándose de que sus suposiciones son correctas. Si no es el caso, deberemos elegir una forma común de optimizar parámetros. Lo peor de todo esto es que no pudimos responder al porqué. ¿Por qué nuestro Asesor Experto resultó ser rentable/no rentable? ¿Por qué tiene semejante reducción? Sin las respuestas, no podrá implementar su idea de forma eficiente.
Yo llevo a cabo las siguientes acciones para visualizar los resultados de una correlación obtenida dentro del gráfico:
- Creo o cambio el indicador necesario para que genere una señal: -1 para vender y 1 para comprar.
- Conecto el indicador de saldo que muestra los puntos de entrada y salida del gráfico. El indicador también muestra los cambios del saldo y beneficio (en puntos) al procesar la señal.
- Analizo en qué casos y circunstancias mis suposiciones son correctas.
El método tiene ciertas ventajas.
- En primer lugar, el indicador de saldo se calcula completamente usando el método OnCalculate, que facilita la mayor velocidad de cálculo y disponibilidad automática de datos del historial en los arrays de cálculo de entrada.
- En segundo lugar, añadir la señal al indicador existente es un paso intermedio entre la creación de un Asesor Experto a través de un Wizard y su desarrollo por su cuenta.
- En tercer lugar, una idea y el resultado final se pueden ver en un mismo gráfico. Por supuesto, el método tiene algunas limitaciones: una señal está vinculada al precio de cierre de la barra, el saldo se calcula para el lote constante, no hay opciones para trading usando órdenes pendientes. No obstante, todas estas limitaciones se pueden arreglar o mejorar fácilmente.
Implementación
Desarrollemos un indicador de señal simple para entender cómo funciona y evaluar la conveniencia del método. Hace mucho que oí hablar de los patrones de velas. ¿De modo que por qué no ver cómo funcionan en la práctica? He seleccionado los patrones inversos "hammer" y "shooting star" como señales de compra y venta respectivamente. Las imágenes de abajo muestran sus aspectos esquemáticos:
Figura 1. Patrones de velas "Hammer" y "shooting star".
Ahora definamos las reglas de entrada al mercado cuando aparece el patrón "hammer".
- El valor mínimo de la vela debe ser menor que los valores de las cinco velas anteriores;
- El cuerpo de la vela no debe superar el 50% de su altura total;
- La sombra superior de la vela no debe superar el 0% de su altura total;
- La altura de la vela no debe ser menor del 100% de la altura media de las cinco velas anteriores;
- El precio de cierre del patrón debe ser menor que la Media Móvil de 10 períodos.
Si estas condiciones se cumplen, debemos abrir una posición larga. Las mismas reglas se aplican al patrón "shooting star". La única diferencia es que, en este caso, debemos abrir una posición corta:
- El valor máximo de la vela debe ser mayor que los valores de las cinco velas anteriores;
- El cuerpo de la vela no debe superar el 50% de su altura total;
- La sombra inferior de la vela no debe superar el 0% de su altura total;
- La altura de la vela no debe ser menor del 100% de la altura media de las cinco velas anteriores;
- El precio de cierre del patrón debe ser mayor que la Media Móvil de 10 períodos.
Usé negrita para los parámetros que basé en dibujos que se pueden optimizar en el futuro (si el patrón muestra resultados aceptables). Las limitaciones que quiero implementar nos permiten filtrar los patrones que tienen una apariencia inapropiada (pp. 1-3), así como los que sabemos que son débiles y no pueden aceptarse como señales.
Además, deberíamos determinar los momentos de salida. Puesto que los patrones mencionados aparecen como señales de inversión de tendencia, la tendencia existe en el momento en que aparece la vela inapropiada. Por tanto, la media móvil que persigue el precio también estará presente. La señal de salida se forma con el cruce del precio y su media móvil de 10 períodos.
Ahora es el momento de hacer algo de programación. Desarrollemos un nuevo indicador personalizado en MQL5 Wizard, llamémoslo PivotCandles y describamos su comportamiento. Definamos los valores devueltos para conectar el indicador de saldo:
- -1 – abrir una posición de venta;
- -2 – cerrar una posición de compra;
- 0 – sin señal;
- 1 – abrir una posición de compra;
- 2 – cerrar una posición de venta.
Como ya sabe, los programadores genuinos no buscan maneras fáciles. Buscan las más fáciles. :) Yo no soy una excepción. Mientras escuchaba música por los auriculares y me tomaba un aromático café, creé el archivo con la clase para implementar en un indicador y en un Asesor Experto (en caso de que decidiera desarrollarlo basado en el indicador). Quizás incluso se pueda modificar para otros patrones de velas. El código no contiene nada especialmente nuevo. Creo que los comentarios implementados en el código pueden responder a todas las posibles preguntas.
//+------------------------------------------------------------------+ //| 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 ver que la parte entera del cálculo se lleva a cabo por la clase CPivotCandlesClass. Se considera una buena práctica de programación el separar la parte del cálculo de la visual, y yo trato de seguir esta recomendación en la medida de lo posible. Los beneficios no tardan en notarse. Abajo puede ver el código del indicador mismo:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
El indicador está listo. Ahora pongámoslo a prueba en cualquiera de los gráficos. Para ello, instale el indicador compilado en el gráfico. Después, veremos algo parecido a lo que se muestra en la imagen de abajo.
Figura 2. Indicador de los patrones de velas "hammer" y "shooting star".
Los puntos de colores indican las posibles entradas y salidas del mercado. Los colores se seleccionan de la siguiente manera:
- rojo oscuro – venta;
- azul oscuro – compra;
- rojo claro – cierre de posición larga;
- azul claro – cierre de posición corta.
Las señales de cierre se forman cada vez que el precio llega a su media móvil. La señal se ignora si no hay posiciones en ese momento.
Ahora pasemos al tema principal del artículo. Tenemos el indicador con el buffer de señal generando solo ciertas señales. Mostremos en una ventana separada del mismo gráfico lo rentable o no rentable que estas señales pueden resultar si se siguen de verdad. El indicador se ha desarrollado especialmente para ese caso. Puede conectarse a otro indicador y abrir/cerrar posiciones virtuales dependiendo de las señales que entran.
Al igual que en el indicador anterior, debemos dividir el código en dos partes: la parte del cálculo y la visual. Abajo puede ver el resultado de una noche de insomnio, pero espero que merezca la 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; } //+------------------------------------------------------------------+
La clase de cálculo está lista. Ahora debemos implementar la visualización del indicador para ver cómo 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; } //+------------------------------------------------------------------+
¡Lo conseguimos! Compilémoslo y examinemos los resultados.
Instrucciones para su uso
Para evaluar la operación de nuestro indicador recién creado, debe adjuntarse a un gráfico que contenga al menos un indicador de señal. Si siguió correctamente todos los pasos, ya tendrá un indicador así: PivotCandles. De modo que debemos configurar los parámetros de entrada. Veamos qué tenemos que especificar:
- Indicator name for balance calculation (Nombre del indicador para el cálculo de saldo) (cadena de caracteres) – debemos tener en cuenta que la vinculación del indicador de saldo se lleva a cabo según su nombre. Por tanto, este campo es obligatorio.
- Signal buffer's index number (Número de índice del buffer de señal) (íntegro) – otro parámetro crítico. El indicador de señal puede generar varias señales según el algoritmo anteriormente definido. Por tanto, el indicador de saldo debe tener los datos referentes a la señal del buffer que debe calcular.
- Calculation start date (Fecha de comienzo de cálculo) (fecha/hora) – fecha inicial del cálculo de saldo.
- Calculation end date (Fecha de final de cálculo) (fecha/hora) – fecha final del cálculo de saldo. Si no se selecciona la fecha (es igual a cero), el cálculo se llevará a cabo desde la última barra.
La Figura 3 muestra la configuración de los dos primeros parámetros para adjuntar el indicador de saldo al tercer buffer del indicador PivotCandles. Los dos parámetros restantes se pueden configurar a su gusto.
Figura 3. Parámetros del indicador de saldo.
Si siguió correctamente todos los pasos anteriores, debería ver una imagen muy similar a la que se muestra abajo.
Figura 4. Curvas de saldo y beneficio generadas usando las señales del indicador PivotCandles.
Ahora podemos probar diferentes intervalos cronológicos y símbolos y descubrir las entradas más y menos rentables al mercado. Debo añadir que este enfoque ayuda a encontrar las correlaciones de mercado que afectan a sus resultados de trading.
Originalmente quería comparar el tiempo invertido en la simulación del Asesor Experto en las mismas señales con el tiempo invertido en el uso del método descrito arriba. Pero después abandoné la idea, puesto que el recálculo del indicador dura un segundo. Un tiempo tan corto desde luego no se puede conseguir de momento con el Asesor Experto, con su carga del historial y los algoritmos de generación de ticks.
Conclusión
El método descrito arriba es muy rápido. Además, facilita claridad en la simulación de indicadores que generan señales de apertura y cierre de posiciones. Permite a los traders analizar las señales y las respuestas del depósito a ellas en una misma ventana de gráfico. Pero todavía tiene algunas limitaciones que debemos tener en cuenta:
- el buffer de señal de indicador analizado se debe preparar de forma preliminar;
- las señales están vinculadas al momento de apertura de la nueva barra;
- no hay MM al calcular el saldo;
No obstante, a pesar de ello, espero que los beneficios sean significativos, y que este método de simulación se posicione con las demás herramientas diseñadas para analizar el comportamiento del mercado y procesar las señales generadas por el mercado.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/505





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso