
Test rapidi delle idee di trading sul grafico
Introduzione
Il sesto Campionato di Trading Automatizzato è finalmente iniziato. Tutta l'eccitazione iniziale è finita e finalmente possiamo rilassarci un po 'ed esaminare i robot di trading presentati. Ho deciso di fare una piccola ricerca per scoprire le caratteristiche più evidenti dei moderni robot di trading e definire cosa possiamo aspettarci dalla loro attività di trading.
Ciò si è rivelato abbastanza difficile. Pertanto, i miei calcoli non si possono definire perfettamente accurati o completi, poiché le mie uniche fonti sono state le descrizioni di Expert Advisor e qualche commento degli sviluppatori. Tuttavia, possiamo ancora trarre alcune conclusioni e di seguito sono riportati i risultati dei miei calcoli: 451 Expert Advisor partecipano al campionato, ma solo 316 di loro contengono alcune descrizioni significative. Gli sviluppatori dei rimanenti hanno riempito le loro descrizioni con saluti ai loro amici e familiari, messaggi alle civiltà extraterrestri o lodi rivolte a loro stessi.
Le strategie più popolari su ATC 2012:
- fare trading utilizzando varie costruzioni grafiche (importanti livelli di prezzo, livelli di supporto-resistenza, canali) – 55;
- analisi del movimento dei prezzi (per vari intervalli di tempo) – 33;
- sistemi di tracciamento delle tendenze (immagino che queste parolone nascondano una combinazione sovraottimizzata di medie mobili ma potrei sbagliarmi) :) ) – 31;
- modelli statistici dei prezzi – 10:
- arbitrato, analisi della correlazione dei simboli – 8;
- analisi della volatilità – 8;
- Rete neurale -7;
- analisi delle candele giapponesi – 5;
- media – 5;
- pacchetti strategici – 5;
- tempo della sessione di trading – 4;
- generatore di numeri casuali – 4;
- trading delle notizie – 3,
- Onde di Elliott - 2
Le strategie di indicatore sono tradizionalmente le più popolari, ovviamente. È difficile definire il ruolo di ogni particolare indicatore in un particolare Expert Advisor ma è possibile stimare il numero assoluto del loro utilizzo:
- Media Mobile - 75
- MACD – 54;
- Oscillatore Stocastico - 25
- RSI – 23;
- Bande di Bollinger - 19;
- Frattali - 8
- CCI, ATR – 7 indicatori ciascuno;
- Zigzag, SAR parabolico – 6 indicatori ciascuno;
- ADX – 5;
- Impulso - 4;
- indicatori personalizzati (che intrigante :)) – 4;
- Ichimoku, AO – 3 indicatori ciascuno;
- ROC, WPR, StdDev, Volumi – 2 indicatori ciascuno.
I dati suggeriscono le seguenti conclusioni: la maggior parte dei partecipanti utilizza strategie commerciali con indicatori. Forse, mi sono perso qualcosa durante la raccolta dei dati, e vedremo l'avvento di alcune personalità eccezionali nel campo del trading automatizzato ma sembra improbabile per ora. Penso che il problema principale sia che ai nuovi arrivati attratti dal mercato nella maggior parte vengono date regole invece di conoscenze.
Ad esempio, queste sono le regole per l'utilizzo di MACD, e questi sono i segnali: ora ottimizza i parametri e guadagna. Che ne dici di usare un po' il cervello? Sciocchezza! Gli standard sono già stati sviluppati! Perché reinventare la ruota? Tuttavia, spesso dimentichiamo che gli indicatori che sono così popolari ora sono stati inventati anche da trader proprio come me e te. Avevano anche i loro standard e le loro autorità. Forse, un nuovo indicatore che porta il tuo nome diventerà uno standard tra una decina d'anni.
Vorrei condividere il mio metodo di ricerca di idee di trading, così come il metodo che uso per testare rapidamente queste idee.
Descrizione del metodo
Tutta l'analisi tecnica si basa su un semplice assioma: i prezzi considerano tutto. Ma c'è un problema: questa affermazione manca di dinamiche. Guardiamo il grafico e vediamo un'immagine statica: il prezzo ha effettivamente considerato tutto. Tuttavia, vogliamo sapere quale sarà il prezzo considerato in un certo periodo di tempo in futuro e dove andrà, in modo da poter realizzare profitti. Gli indicatori derivati dal prezzo sono stati progettati esattamente per prevedere possibili movimenti futuri.
Come sappiamo dalla fisica, la derivata del primo ordine della grandezza è la velocità. Pertanto, gli indicatori calcolano l'attuale velocità di variazione dei prezzi. Sappiamo anche che le grandezze significative hanno inerzia che impedisce alla velocità di variazioni brusche del suo valore senza l'intervento di considerevoli forze esterne. È così che ci avviciniamo gradualmente al concetto di tendenza - lo stato del prezzo quando il suo derivato di primo ordine (velocità) mantiene il suo valore durante il periodo di tempo in cui le forze esterne (notizie, politiche delle banche centrali, ecc.) non influenzano il mercato.
Ma torniamo da dove siamo partiti: i prezzi considerano tutto. Per sviluppare nuove idee, dovremmo esaminare il comportamento del prezzo e dei suoi derivati allo stesso intervallo di tempo. Solo un attento esame dei grafici dei prezzi aumenterà il tuo trading dalla fede cieca fino al livello di comprensione genuina.
Ciò potrebbe non portare a cambiamenti immediati nei risultati di trading, ma la capacità di rispondere a numerose domande sul perché, prima o poi giocherà un ruolo positivo. Inoltre, l'analisi visiva di grafici e indicatori ti consentirà di trovare alcune correlazioni nuove di zecca tra prezzi e indicatori non previsti dai loro sviluppatori.
Supponiamo di aver trovato una nuova correlazione che apparentemente funziona a tuo favore. Che novità ci sono? Il modo più semplice è scrivere un Expert Advisor e testarlo su dati storici assicurandosi che la tua ipotesi sia corretta. In caso contrario, dobbiamo scegliere un modo comune di ottimizzare i parametri. La cosa peggiore è che non siamo stati in grado di rispondere alla domanda sul perché. Perché il nostro Expert Advisor si è rivelato redditizio e in perdita? Perché c'è stato un drawdown così enorme? Senza le risposte, non sarai in grado di implementare la tua idea in modo efficiente.
Eseguo le seguenti azioni per visualizzare i risultati di una correlazione ottenuta direttamente sul grafico:
- Creo o cambio l'indicatore necessario, in modo che generi un segnale: -1 per vendere e 1 per comprare.
- Collego l'indicatore di bilanciamento che visualizza i punti di entrata e di uscita al grafico. L'indicatore mostra anche le variazioni del saldo e dell'equità (in punti) durante l'elaborazione del segnale.
- Analizzo in quali casi e circostanze le mie ipotesi sono corrette.
Il metodo ha alcuni vantaggi.
- Innanzitutto, l'indicatore di bilanciamento viene interamente calcolato utilizzando il metodo OnCalculate che fornisce la massima velocità di calcolo e la disponibilità automatica dei dati storici negli array di calcolo di input.
- In secondo luogo, l'aggiunta del segnale all'indicatore esistente è un passaggio intermedio tra la creazione di un Expert Advisor tramite Wizard e lo sviluppo da soli.
- In terzo luogo, un'idea e un risultato finale possono essere visti su un singolo grafico. Naturalmente, il metodo ha alcune limitazioni: un segnale è legato al prezzo di chiusura della barra, il saldo è calcolato per il lotto costante, non ci sono opzioni per il trading utilizzando ordini in sospeso. Tuttavia, tutte queste limitazioni possono essere facilmente risolte / migliorate.
Implementazione
Sviluppiamo un semplice indicatore di segnale per capire come funziona e valutare la praticità del metodo. Ho sentito parlare a lungo dei modelli di candele giapponesi. Quindi, perché non controllare il loro lavoro in pratica? Ho selezionato i modelli inversi "martello" e "stella cadente" come segnali di acquisto e vendita, rispettivamente. Le immagini qui sotto mostrano il loro aspetto schematico:
Figura 1. Modelli di candele giapponesi "Hammer" e "shooting star"
Ora, definiamo le regole di ingresso nel mercato quando appare il modello "martello".
- Il valore più basso della candela dovrebbe essere inferiore a quello delle cinque candele precedenti;
- Il corpo della candela non deve superare il 50% della sua altezza totale;
- L'ombra superiore della candela non deve superare lo 0% della sua altezza totale;
- L'altezza della candela non deve essere inferiore al 100% dell'altezza media delle cinque candele prima di essa;
- Il prezzo di chiusura del modello dovrebbe essere inferiore alla media mobile a 10periodi.
Se queste condizioni sono soddisfatte, dovremmo aprire una posizione lunga. Le regole sono le stesse per il modello "stella cadente". L'unica differenza è che dovremmo aprire una posizione corta:
- Il valore più alto della candela dovrebbe essere superiore a quello delle cinque candele precedenti;
- Il corpo della candela non deve superare il 50% della sua altezza totale;
- L'ombra inferiore della candela non deve superare lo 0% della sua altezza totale;
- L'altezza della candela non deve essere inferiore al 100% dell'altezza media delle cinque candele prima di essa;
- Il prezzo di chiusura del modello dovrebbe essere superiore alla media mobile a 10 periodi.
Ho usato lo stile grassetto per i parametri che ho usato basati su disegni che possono essere ottimizzati in futuro (se il modello mostra risultati accettabili). Le limitazioni che voglio implementare ci permettono di cancellare i modelli da quelli che hanno un aspetto inappropriato (pp. 1-3), così come da quelli consapevolmente deboli che non possono essere accettati come segnali.
Inoltre, dovremmo determinare i momenti di uscita. Poiché i modelli menzionati appaiono come segnali di inversione di tendenza, la tendenza esiste nel momento in cui appare la candela appropriata. Pertanto, sarà presente anche la media mobile che insegue il prezzo. Il segnale di uscita è formato dall'incrocio del prezzo e della sua media mobile a 10periodi.
Ora è il momento di fare un po 'di programmazione. Sviluppiamo un nuovo indicatore personalizzato in MQL5 Wizard, denominalo PivotCandles e descriviamone il comportamento. Definiamo i valori restituiti per collegare l'indicatore di bilanciamento:
- -1 – aprire una posizione di vendita;
- -2 – chiudere una posizione di acquisto;
- 0 – nessun segnale;
- 1 – posizione di acquisto aperta;
- 2 – chiudi la posizione di vendita.
Come sapete, i veri programmatori non cercano modi semplici. Cercano i più semplici. :) Io non sono un'eccezione. Mentre ascoltavo musica in cuffia e bevevo caffè aromatico, ho creato il file con la classe da implementare in un indicatore e in un Expert Advisor (nel caso decidessi di svilupparlo in base all'indicatore). Forse, può anche essere modificato per altri modelli di candele giapponesi. Il codice non contiene nulla di nuovo. Credo che i commenti implementati al codice coprano tutte le possibili domande.
//+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+
Possiamo vedere che l'intera parte di calcolo viene eseguita dalla classe CPivotCandlesClass. È considerata una buona programmazione separare la parte di calcolo da quella visiva e cerco di fare del mio meglio per seguire questa raccomandazione. I benefici non tardano ad arrivare - di seguito è riportato il codice dell'indicatore stesso:
//+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+
L'indicatore è pronto. Ora, testiamolo su uno qualsiasi dei grafici. Per fare ciò, installare l'indicatore compilato sul grafico. Successivamente, vedremo qualcosa di simile a quello mostrato nell'immagine qui sotto.
Figura 2. Indicatore dei modelli di candele giapponesi "martello" e "stella cadente"
I punti colorati indicano possibili entrate e uscite dal mercato. I colori sono selezionati come segue:
- rosso scuro – vendere;
- blu scuro – comprare;
- rosso chiaro – chiusura posizione lunga;
- rosso chiaro – chiusura posizione corta.
I segnali di chiusura si formano ogni volta che il prezzo raggiunge la sua media mobile. Il segnale viene ignorato, se non c'erano posizioni in quel momento.
Ora, passiamo all'argomento principale dell'articolo. Abbiamo l'indicatore con il buffer del segnale che genera solo alcuni segnali determinati. Mostriamo in una finestra separata dello stesso grafico quanto possono essere redditizi / in perdita questi segnali se effettivamente seguiti. L'indicatore è stato sviluppato appositamente per quel caso. Può connettersi a un altro indicatore e aprire / chiudere posizioni virtuali a seconda dei segnali in arrivo.
Proprio come con l'indicatore precedente, dovremmo dividere il codice in due parti: calcolo e visiva. Di seguito è riportato il risultato di una notte insonne ma spero che ne valga 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 classe di calcolo è pronta. Ora, dovremmo implementare la visualizzazione degli indicatori per vedere come funziona.
//+------------------------------------------------------------------+ //| 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 è finita! Compiliamolo ed esaminiamo i risultati.
Istruzioni per l'uso
Per valutare il funzionamento del nostro indicatore di nuova concezione, dovrebbe essere allegato al grafico contenente almeno un indicatore di segnale. Se hai seguito tutti i passaggi, allora abbiamo già un tale indicatore: PivotCandles. Quindi, dobbiamo configurare i parametri di input. Vediamo cosa dovremmo specificare:
- Nome dell'indicatore per il calcolo del saldo (stringa) - dovremmo tenere presente che l'associazione dell'indicatore di saldo viene eseguita per nome. Pertanto, questo campo è obbligatorio.
- Numero di indice del buffer di segnale (intero) – un altro parametro critico. L'indicatore di segnale può generare diversi segnali secondo l'algoritmo precedentemente definito. Pertanto, l'indicatore di saldo dovrebbe avere i dati relativi al segnale del buffer che dovrebbe calcolare.
- Data di inizio del calcolo (data/ora) – data iniziale del calcolo del saldo.
- Data di fine del calcolo (data/ora) – data di fine del calcolo del saldo. Se la data non è selezionata (uguale a zero), il calcolo verrà effettuato fino all'ultima barra.
La Figura 3 mostra la configurazione dei primi due parametri per il collegamento dell'indicatore di bilanciamento al terzo buffer dell'indicatore PivotCandles. I restanti due parametri possono essere impostati a proprio piacimento.
Figura 3. Parametri dell'indicatore di bilanciamento
Se tutti i passaggi precedenti sono stati eseguiti correttamente, dovresti vedere un'immagine molto simile a quella mostrata di seguito.
Figura 4. Curve di equilibrio e di equità generate utilizzando i segnali dell'indicatore PivotCandles
Ora possiamo provare diversi intervalli di tempo e simboli e scoprire le voci di mercato più redditizie e in perdita. Va aggiunto che questo approccio aiuta a trovare le correlazioni di mercato che influenzano i risultati di trading.
Originariamente, volevo confrontare il tempo trascorso a testare l'Expert Advisor in base agli stessi segnali con il tempo trascorso utilizzando il metodo sopra descritto. Ma poi ho abbandonato l'idea, poiché il ricalcolo dell'indicatore richiede circa un secondo. Un tempo così breve non può certamente essere raggiunto dall'Expert Advisor con il suo caricamento della cronologia e gli algoritmi che generano ticks.
Conclusione
Il metodo sopra descritto è molto veloce. Inoltre, fornisce la chiarezza nel testare gli indicatori che generano segnali di apertura / chiusura della posizione. Consente ai trader di analizzare i segnali e le risposte del deposito in un'unica finestra del grafico. Ma ha ancora i suoi limiti di cui dovremmo essere consapevoli:
- il buffer di segnale dell'indicatore analizzato deve essere preparato preliminarmente;
- i segnali sono legati all'orario di apertura della nuova barra;
- nessun ММ nel calcolo del saldo;
Tuttavia, nonostante queste carenze, spero che i benefici saranno più significativi e questo metodo di test prenderà il suo posto tra gli altri strumenti progettati per analizzare il comportamento del mercato ed elaborare i segnali generati dal mercato.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/505





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso