Controllo dello Slope della Curva di Saldo Durante il Lavoro di un Expert Advisor
Introduzione
Questo articolo descrive uno degli approcci che consente di migliorare le prestazioni degli Expert Advisor attraverso la creazione di un feedback. In questo caso, il feedback si baserà sulla misurazione della pendenza della curva di bilanciamento. Il controllo della pendenza viene eseguito automaticamente regolando il volume di lavoro. Un Expert Advisor può negoziare nelle seguenti modalità: con un volume tagliato, con la quantità di lavoro di lotti (in base a quello inizialmente regolato) e con un volume intermedio. La modalità di lavoro viene commutata automaticamente.
Diverse caratteristiche di regolazione sono utilizzate nella catena di feedback: a gradini, a gradini con isteresi, lineari. Permette di regolare il sistema di controllo dello slope della curva di equilibrio alle caratteristiche di un determinato sistema.
L'idea principale è quella di automatizzare il processo di prendere decisioni per un trader monitorando il proprio sistema di trading. È ragionevole ridurre i rischi durante i periodi sfavorevoli del suo funzionamento. Ritornando alla normale modalità di lavoro i rischi possono essere ripristinati al livello iniziale.
Naturalmente, questo sistema non è una panacea e non trasformerà un Expert Advisor perdente in uno redditizio. In qualche modo, questa è un'aggiunta al MM (gestione del denaro) di Expert Advisor che gli impedisce di ottenere perdite considerevoli su un conto.
L'articolo include una libreria che consente di incorporare questa funzione nel codice di qualsiasi Expert Advisor.
Principio di Operazione
Diamo un'occhiata al principio di operazione del sistema, che controlla lo slope della curva di equilibrio. Supponiamo di avere un Expert Advisor di trading. La sua ipotetica curva di equilibrio appare come segue:
Figura 1. Principio di funzionamento del sistema che controlla la pendenza della curva di bilanciamento
La curva iniziale di equilibrio per l'Expert Advisor che utilizza un volume costante di operazioni di trading è mostrata sopra. I trade chiusi sono mostrati con i punti rossi. Colleghiamo quei punti con una linea di curva, che rappresenta il cambiamento di equilibrio dell'Expert Advisor durante il trading (linea nera spessa).
Ora tracceremo continuamente l'angolo di pendenza di questa linea rispetto all'asse del tempo (mostrato con sottili linee blu). O per essere più precisi, prima di aprire ogni operazione con un segnale, calcoleremo l'angolo dello slope di due operazioni precedentemente chiuse (o di due operazioni, affinché la descrizione sia più semplice). Se l'angolo dello slope diventa inferiore al valore specificato, il nostro sistema di controllo inizia a funzionare; diminuisce il volume in base al valore calcolato dell'angolo e alla funzione di regolazione specificata.
In tal modo, se il trading entra in un periodo senza successo, il volume diminuisce da Vmax. a Vmin. all'interno del Т3...Т5 periodo di trading. Dopo che il trading di punti Т5 viene eseguito con un volume minimo specificato - nella modalità di rifiuto del volume degli scambi. Una volta ripristinata la redditività dell'Expert Advisor e l'angolo dello slope della curva di bilanciamento sale al di sopra del valore specificato, il volume inizia ad aumentare. Questo accade all'interno dell’intervalloТ8...Т10. Dopo il punto Т10 il volume delle operazioni commerciali viene ripristinato allo stato iniziale Vmax.
La curva di equilibrio formata a seguito di tale regolazione è mostrata nella parte inferiore della fig. 1. Puoi vedere che il drawdown iniziale da B1 a B2 è diminuito ed è diventato da B1 a B2*. Puoi anche osservare che il profitto è leggermente diminuito entro il periodo di ripristino del volume massimo Т8 ... Т10 - questo è il rovescio della medaglia.
Il colore verde evidenzia la parte della curva di bilanciamento quando il trading è stato eseguito con un volume minimo specificato. Il colore giallo rappresenta le parti di transizione dal volume massimo a quello minimo e ritorno. Sono possibili diverse varianti di transizione qui:
- a gradini - variazioni di volume in passi discreti dal volume massimo al minimo e ritorno;
- lineare - il volume viene modificato linearmente a seconda dell'angolo di pendenza della curva di equilibrio all'interno dell'intervallo regolato;
- a gradini con isteresi - la transizione dal volume massimo al minimo e il ritorno viene eseguita a valori di differenza dell'angolo di pendenza;
Illustriamolo in immagini:
Figura 2. Tipi di caratteristiche di regolazione
Le caratteristiche di regolazione influenzano i tassi del sistema di controllo - il ritardo di abilitazione / disabilitazione, il processo di transizione dal volume massimo al minimo e ritorno. Si consiglia di scegliere una caratteristica su base sperimentale quando si raggiungono i migliori risultati dei test.
Pertanto, miglioriamo il sistema di trading con il feedback basato sull'angolo dello slope della curva di bilanciamento. Si noti che tale regolazione del volume è adatta solo per quei sistemi che non hanno il volume come parte del sistema di trading stesso. Ad esempio, se si utilizza il principio Martingale, non è possibile utilizzare questo sistema direttamente senza modifiche nell'Expert Advisor iniziale.
Inoltre, dobbiamo attirare la nostra attenzione sui seguenti punti importanti:
- l'efficacia della gestione dello slope della linea di bilanciamento dipende direttamente dal rapporto tra il volume di lavoro in modalità normale di funzionamento e il volume nella modalità di rifiuto del volume. Maggiore è questo rapporto, più efficace è la gestione. Ecco perché il volume di lavoro iniziale dovrebbe essere considerevolmente maggiore di quello minimo possibile.
- il periodo medio di alterazione degli aumenti e delle cadute del saldo di Expert Advisor dovrebbe essere considerevolmente superiore al tempo di reazione del sistema di controllo. In caso contrario, il sistema non riuscirà a regolare la pendenza della curva di bilanciamento. Maggiore è il rapporto tra il periodo medio e il tempo di reazione, più efficace è il sistema. Questo requisito riguarda quasi tutti i sistemi di regolazione automatica.
Implementazione in MQL5 Utilizzando la Programmazione Orientata agli Oggetti
Scriviamo una libreria che realizzi l'approccio sopra descritto. Per farlo, usiamo la nuova funzionalità di MQL5 - approccio orientato agli oggetti. Questo approccio consente di sviluppare ed espandere facilmente la nostra libreria in futuro senza riscrivere grandi parti del codice da zero.
Classe TradeSymbol
Poiché il test multi-valuta è implementato nella nuova piattaforma MetaTrader 5, abbiamo bisogno di una classe, che incapsula in sé l'intero lavoro con qualsiasi simbolo di lavoro. Permette di utilizzare questa libreria in Expert Advisor multi-valuta. Questa classe non riguarda direttamente il sistema di controllo, è ausiliaria. Pertanto, verrà utilizzata per le operazioni con il simbolo di lavoro.
//--------------------------------------------------------------------- // Operations with work symbol: //--------------------------------------------------------------------- class TradeSymbol { private: string trade_symbol; // work symbol private: double min_trade_volume; // minimum allowed volume for trade operations double max_trade_volume; // maximum allowed volume for trade operations double min_trade_volume_step; // minimum change of volume double max_total_volume; // maximum change of volume double symbol_point; // size of one point double symbol_tick_size; // minimum change of price int symbol_digits; // number of digits after decimal point protected: public: void RefreshSymbolInfo( ); // refresh market information about the work symbol void SetTradeSymbol( string _symbol ); // set/change work symbol string GetTradeSymbol( ); // get work symbol double GetMaxTotalLots( ); // get maximum cumulative volume double GetPoints( double _delta ); // get change of price in points public: double NormalizeLots( double _requied_lot ); // get normalized trade volume double NormalizePrice( double _org_price ); // get normalized price with consideration of step of change of quote public: void TradeSymbol( ); // constructor void ~TradeSymbol( ); // destructor };
La struttura della classe è molto semplice. Lo scopo è ottenere, archiviare ed elaborare le informazioni di mercato correnti con un simbolo specificato. I metodi principali sono TradeSymbol::RefreshSymbolInfo, TradeSymbol::NormalizeLots, TradeSymbol::NormalizePrice. Consideriamoli uno per uno.
Il metodo TradeSymbol::RefreshSymbolInfo ha lo scopo di aggiornare le informazioni di mercato con il simbolo di lavoro.
//--------------------------------------------------------------------- // Refresh market information by work symbol: //--------------------------------------------------------------------- void TradeSymbol::RefreshSymbolInfo( ) { // If a work symbol is not set, don't do anything: if( GetTradeSymbol( ) == NULL ) { return; } // Calculate parameters necessary for normalization of volume: min_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MIN ); max_trade_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_MAX ); min_trade_volume_step = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_STEP ); max_total_volume = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_VOLUME_LIMIT ); symbol_point = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_POINT ); symbol_tick_size = SymbolInfoDouble( GetTradeSymbol( ), SYMBOL_TRADE_TICK_SIZE ); symbol_digits = ( int )SymbolInfoInteger( GetTradeSymbol( ), SYMBOL_DIGITS ); }
Presta attenzione a un punto importante che viene utilizzato in diversi metodi. Poiché l'attuale realizzazione di MQL5 non consente l'utilizzo di un costruttore con parametri, è necessario chiamare il seguente metodo per l'impostazione primaria dei simboli di lavoro:
void SetTradeSymbol( string _symbol ); // set/change work symbol
Il metodo TradeSymbol::NormalizeLots viene utilizzato per ottenere un volume corretto e normalizzato. Sappiamo che la dimensione di una posizione non può essere inferiore al valore minimo possibile consentito dal broker. Il passaggio minimo di cambiamento di una posizione è determinato anche dal broker e può differire. Questo metodo restituisce il valore di volume più vicino dal basso.
Controlla anche se il volume della presunta posizione supera il valore massimo consentito dal broker.
//--------------------------------------------------------------------- // Get normalized trade volume: //--------------------------------------------------------------------- // - input necessary volume; // - output is normalized volume; //--------------------------------------------------------------------- double TradeSymbol::NormalizeLots( double _requied_lots ) { double lots, koeff; int nmbr; // If a work symbol is not set, don't do anything: if( GetTradeSymbol( ) == NULL ) { return( 0.0 ); } if( this.min_trade_volume_step > 0.0 ) { koeff = 1.0 / min_trade_volume_step; nmbr = ( int )MathLog10( koeff ); } else { koeff = 1.0 / min_trade_volume; nmbr = 2; } lots = MathFloor( _requied_lots * koeff ) / koeff; // Lower limit of volume: if( lots < min_trade_volume ) { lots = min_trade_volume; } // Upper limit of volume: if( lots > max_trade_volume ) { lots = max_trade_volume; } lots = NormalizeDouble( lots, nmbr ); return( lots ); }
Il metodo TradeSymbol::NormalizePrice viene utilizzato per ottenere un prezzo corretto e normalizzato. Poiché il numero di cifre significative dopo il punto decimale (precisione del prezzo) deve essere determinato per un determinato simbolo, è necessario troncare il prezzo. Oltre ad esso, alcuni simboli (ad esempio, i future) hanno un passo minimo di variazione del prezzo maggiore di un punto. Ecco perché dobbiamo fare in modo che i valori del prezzo siano multipli della discrepanza minima.
//--------------------------------------------------------------------- // Normalization of price with consideration of step of price change: //--------------------------------------------------------------------- double TradeSymbol::NormalizePrice( double _org_price ) { // Minimal step of quote change in points: double min_price_step = NormalizeDouble( symbol_tick_size / symbol_point, 0 ); double norm_price = NormalizeDouble( NormalizeDouble(( NormalizeDouble( _org_price / symbol_point, 0 )) / min_price_step, 0 ) * min_price_step * symbol_point, symbol_digits ); return( norm_price ); }
Il prezzo non normalizzato necessario viene inserito nella funzione. E restituisce il prezzo normalizzato che è il più vicino a quello necessario.
Lo scopo degli altri metodi è chiaramente descritto nei commenti; non richiede ulteriori descrizioni.
Classe TBalanceHistory
Questa classe, è destinata ad operare con la storia del saldo di un conto che è chiaro per il suo nome. È anche una classe base per diverse classi descritte di seguito. Lo scopo principale di questa classe è l'accesso alla storia commerciale di un Expert Advisor. Inoltre, è possibile filtrare la cronologia per simbolo di lavoro, per "numero magico", per data di inizio del monitoraggio dell'Expert Advisor o per tutti e tre gli elementi contemporaneamente.
//--------------------------------------------------------------------- // Operations with balance history: //--------------------------------------------------------------------- class TBalanceHistory { private: long current_magic; // value of "magic number" when accessing the history of deals ( 0 - any number ) long current_type; // type of deals ( -1 - all ) int current_limit_history; // limit of depth of history ( 0 - all history ) datetime monitoring_begin_date; // date of start of monitoring history of deals int real_trades; // number of actual trades already performed protected: TradeSymbol trade_symbol; // operations with work symbol protected: // "Raw" arrays: double org_datetime_array[ ]; // date/time of trade double org_result_array[ ]; // result of trade // Arrays with data grouped by time: double group_datetime_array[ ]; // date/time of trade double group_result_array[ ]; // result of trade double last_result_array[ ]; // array for storing results of last trades ( points on the Y axis ) double last_datetime_array[ ]; // array for storing time of last trades ( points on the X axis ) private: void SortMasterSlaveArray( double& _m[ ], double& _s[ ] ); // synchronous ascending sorting of two arrays public: void SetTradeSymbol( string _symbol ); // set/change work symbol string GetTradeSymbol( ); // get work symbol void RefreshSymbolInfo( ); // refresh market information by work symbol void SetMonitoringBeginDate( datetime _dt ); // set date of start of monitoring datetime GetMonitoringBeginDate( ); // get date of start of monitoring void SetFiltrParams( long _magic, long _type = -1, int _limit = 0 );// set parameters of filtration of deals public: // Get results of last trades: int GetTradeResultsArray( int _max_trades ); public: void TBalanceHistory( ); // constructor void ~TBalanceHistory( ); // destructor };
Le impostazioni di filtraggio durante la lettura dei risultati delle ultime operazioni e della cronologia vengono impostate utilizzando il metodo TBalanceHistory::SetFiltrParams. Ha i seguenti parametri di input:
- _magia - "numero magico" di trade che dovrebbero essere letti dalla cronologia. Se viene specificato il valore zero, verranno letti trade con qualsiasi "numero magico".
- _tipo - tipo di offerte che devono essere lette. Può avere i seguenti valori: DEAL_TYPE_BUY (solo per la lettura di trade lunghi), DEAL_TYPE_SELL (solo per la lettura di trade corti) e -1 (per la lettura di trade lunghi e corti).
- _limit - limita la profondità della cronologia analizzata dei trade. Se è uguale a zero, viene analizzata tutta la cronologia disponibile.
Per impostazione predefinita, quando viene creato l'oggetto della classe TBalanceHistory vengono impostati i seguenti valori: __magic = 0, _type = -1, _limit = 0.
Il metodo principale di questa classe è TBalanceHistory::GetTradeResultsArray. È destinato al riempimento di array di membri della classe last_result_array e last_datetime_array con i risultati degli ultimi trade. Il metodo ha i seguenti parametri di input:
- _max_trades - numero massimo di trade che devono essere letti dalla cronologia ed essere scritti negli array di output. Poiché abbiamo bisogno di almeno due punti per calcolare l'angolo di pendenza, questo valore non dovrebbe essere inferiore a due. Se questo valore è uguale a zero, viene analizzata l'intera cronologia disponibile delle negoziazioni. In pratica, il numero di punti necessari per il calcolo della pendenza della curva di bilanciamento è specificato qui.
//--------------------------------------------------------------------- // Reads the results of last (by time) trades to arrays: //--------------------------------------------------------------------- // - returns the number of actually read trades but not more than specified; //--------------------------------------------------------------------- int TBalanceHistory::GetTradeResultsArray( int _max_trades ) { int index, limit, count; long deal_type, deal_magic, deal_entry; datetime deal_close_time, current_time; ulong deal_ticket; // ticket of deal double trade_result; string symbol, deal_symbol; real_trades = 0; // Number of trades should be no less than two: if( _max_trades < 2 ) { return( 0 ); } // If a work symbol is not specified, don't do anything: symbol = trade_symbol.GetTradeSymbol( ); if( symbol == NULL ) { return( 0 ); } // Request the history of deals and orders from the specified time to the current moment: if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true ) { return( 0 ); } // Calculate number of trades: count = HistoryDealsTotal( ); // If there are less trades in the history than it is necessary, then exit: if( count < _max_trades ) { return( 0 ); } // If there are more trades in the history than it is necessary, then limit them: if( current_limit_history > 0 && count > current_limit_history ) { limit = count - current_limit_history; } else { limit = 0; } // If needed, adjust dimension of "raw" arrays by the specified number of trades: if(( ArraySize( org_datetime_array )) != ( count - limit )) { ArrayResize( org_datetime_array, count - limit ); ArrayResize( org_result_array, count - limit ); } // Fill the "raw" array with trades from history base: real_trades = 0; for( index = count - 1; index >= limit; index-- ) { deal_ticket = HistoryDealGetTicket( index ); // If those are not closed deals, don't go further: deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY ); if( deal_entry != DEAL_ENTRY_OUT ) { continue; } // Check "magic number" of deal if necessary: deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC ); if( current_magic != 0 && deal_magic != current_magic ) { continue; } // Check symbol of deal: deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL ); if( symbol != deal_symbol ) { continue; } // Check type of deal if necessary: deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE ); if( current_type != -1 && deal_type != current_type ) { continue; } else if( current_type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL )) { continue; } // Check time of closing of deal: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; } // So, we can read another trade: org_datetime_array[ real_trades ] = deal_close_time / 60; org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME ); real_trades++; } // if there are less trades than necessary, return: if( real_trades < _max_trades ) { return( 0 ); } count = real_trades; // Sort the "raw" array by date/time of closing the order: SortMasterSlaveArray( org_datetime_array, org_result_array ); // If necessary, adjust dimension of group arrays for the specified number of points: if(( ArraySize( group_datetime_array )) != count ) { ArrayResize( group_datetime_array, count ); ArrayResize( group_result_array, count ); } ArrayInitialize( group_datetime_array, 0.0 ); ArrayInitialize( group_result_array, 0.0 ); // Fill the output array with grouped data ( group by the identity of date/time of position closing ): for( index = 0; index < count; index++ ) { // Get another trade: deal_close_time = ( datetime )org_datetime_array[ index ]; trade_result = org_result_array[ index ]; // Now check if the same time already exists in the output array: current_time = ( datetime )group_datetime_array[ real_trades ]; if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 ) { real_trades++; // move the pointer to the next element group_result_array[ real_trades ] = trade_result; group_datetime_array[ real_trades ] = deal_close_time; } else { group_result_array[ real_trades ] += trade_result; group_datetime_array[ real_trades ] = deal_close_time; } } real_trades++; // now this is the number of unique elements // If there are less trades than necessary, exit: if( real_trades < _max_trades ) { return( 0 ); } if( ArraySize( last_result_array ) != _max_trades ) { ArrayResize( last_result_array, _max_trades ); ArrayResize( last_datetime_array, _max_trades ); } // Write the accumulated data to the output arrays with reversed indexation: for( index = 0; index < _max_trades; index++ ) { last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ]; last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ]; } // In the output array replace the results of single trades with the accumulating total: for( index = 1; index < _max_trades; index++ ) { last_result_array[ index ] += last_result_array[ index - 1 ]; } return( _max_trades ); }
I controlli obbligatori vengono eseguiti all'inizio - se viene specificato un simbolo di lavoro e se i parametri di input sono corretti.
Quindi leggiamo la cronologia delle offerte e degli ordini dalla data specificata al momento corrente. Viene eseguito nella seguente parte del codice:
// Request the history of deals and orders from the specified time to the current moment: if( HistorySelect( monitoring_begin_date, TimeCurrent( )) != true ) { return( 0 ); } // Calculate number of trades: count = HistoryDealsTotal( ); // If there are less trades in the history than it is necessary, then exit: if( count < _max_trades ) { return( 0 ); }
Inoltre, viene controllato il numero totale di offerte nella cronologia. Se è inferiore a quanto specificato, ulteriori azioni sono prive di significato. Non appena gli array "grezzi" vengono preparati, viene eseguito il ciclo di riempimento con le informazioni della cronologia dei trade. È fatto nel modo seguente:
// Fill the "raw" array from the base of history of trades: real_trades = 0; for( index = count - 1; index >= limit; index-- ) { deal_ticket = HistoryDealGetTicket( index ); // If the trades are not closed, don't go further: deal_entry = HistoryDealGetInteger( deal_ticket, DEAL_ENTRY ); if( deal_entry != DEAL_ENTRY_OUT ) { continue; } // Check "magic number" of deal if necessary: deal_magic = HistoryDealGetInteger( deal_ticket, DEAL_MAGIC ); if( _magic != 0 && deal_magic != _magic ) { continue; } // Check symbols of deal: deal_symbol = HistoryDealGetString( deal_ticket, DEAL_SYMBOL ); if( symbol != deal_symbol ) { continue; } // Check type of deal if necessary: deal_type = HistoryDealGetInteger( deal_ticket, DEAL_TYPE ); if( _type != -1 && deal_type != _type ) { continue; } else if( _type == -1 && ( deal_type != DEAL_TYPE_BUY && deal_type != DEAL_TYPE_SELL )) { continue; } // Check time of closing of deal: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; } // So, we can rad another trade: org_datetime_array[ real_trades ] = deal_close_time / 60; org_result_array[ real_trades ] = HistoryDealGetDouble( deal_ticket, DEAL_PROFIT ) / HistoryDealGetDouble( deal_ticket, DEAL_VOLUME ); real_trades++; } // If there are less trades than necessary, exit: if( real_trades < _max_trades ) { return( 0 ); }
All'inizio, il ticket dell'affare dalla cronologia viene letto utilizzando la funzione HistoryDealGetTicket; un'ulteriore lettura dei dettagli dell'affare viene eseguita utilizzando il biglietto ottenuto. Poiché siamo interessati solo ai trade chiusi (analizzeremo il saldo), il tipo di affare viene controllato in un primo momento. Viene eseguito chiamando la funzione HistoryDealGetInteger con il parametro DEAL_ENTRY. Se la funzione ritorna DEAL_ENTRY_OUT, allora sta chiudendo una posizione.
Dopo che il "numero magico" dell'affare, il tipo di affare (è specificato il parametro di input del metodo) e il simbolo dell'affare vengono controllati. Se tutti i parametri dell'affare soddisfano i requisiti, viene controllato l'ultimo parametro: il tempo di chiusura dell'affare. È fatto nel modo seguente:
// Check the time of closing of deal: deal_close_time = ( datetime )HistoryDealGetInteger( deal_ticket, DEAL_TIME ); if( deal_close_time < monitoring_begin_date ) { continue; }
La data / ora dell'affare viene confrontata con la data / ora di inizio del monitoraggio della cronologia. Se la data / ora dell'affare è maggiore di quella data, allora andiamo a leggere il nostro trade nell'array - leggiamo il risultato del trade in punti e l'ora del trade in minuti (in questo caso l'ora della chiusura). Successivamente, il contatore delle offerte di lettura real_trades viene aumentato; e il ciclo continua.
Una volta che gli array "grezzi" sono pieni della quantità necessaria di informazioni, dovremmo selezionare l'array in cui è memorizzato il tempo di chiusura degli affari. Allo stesso tempo, dobbiamo mantenere la corrispondenza del tempo di chiusura nell'array org_datetime_array e i risultati degli affari nell'array org_result_array. Questo viene fatto usando il metodo appositamente scritto:
TBalanceHistory::SortMasterSlaveArray( double& _master[ ], double& _slave[ ] ). Il primo parametro è _master - l'array che è ordinato in modo ascendente. Il secondo parametro è _slave - l'array, i cui elementi devono essere spostati in modo sincrono con gli elementi del primo array. Lo smistamento viene eseguito tramite il metodo "bubble".
Dopo tutte le operazioni sopra descritte, abbiamo due array con tempo e risultati delle offerte ordinati per tempo. Poiché solo un punto sulla curva di bilanciamento (punto sull'asse Y) può corrispondere a ciascun momento del tempo (punto sull'asse X), dobbiamo raggruppare gli elementi dell'array con lo stesso tempo di chiusura (se ci sono). La parte seguente del codice esegue questa operazione:
// Fill the output array with grouped data ( group by identity of date/time of closing of position ): real_trades = 0; for( index = 0; index < count; index++ ) { // Get another trade: deal_close_time = ( datetime )org_datetime_array[ index ]; trade_result = org_result_array[ index ]; // Now check, if the same time already exists in the output array: current_time = ( datetime )group_datetime_array[ real_trades ]; if( current_time > 0 && MathAbs( current_time - deal_close_time ) > 0.0 ) { real_trades++; // move the pointer to the next element group_result_array[ real_trades ] = trade_result; group_datetime_array[ real_trades ] = deal_close_time; } else { group_result_array[ real_trades ] += trade_result; group_datetime_array[ real_trades ] = deal_close_time; } } real_trades++; // now this is the number of unique elements
In pratica, tutte i trade con lo "stesso" tempo di chiusura sono riassunti qui. I risultati vengono scritti negli array TBalanceHistory::group_datetime_array (ora di chiusura) e TBalanceHistory::group_result_array (risultati dei trade). Dopodiché otteniamo due array ordinati con elementi unici. L'identità del tempo in questo caso viene considerata entro un minuto. Questa trasformazione può essere illustrata graficamente:
Figura 3. Insieme di affari con lo stesso tempo
Tutte gli affari entro un minuto (parte sinistra della figura) sono raggruppati in uno con arrotondamento del tempo e somma dei risultati (parte destra della figura). Permette di appianare il "chiacchiericcio" del tempo di chiusura degli affari e migliorare la stabilità della regolamentazione.
Dopodiché è necessario effettuare altre due trasformazioni delle matrici ottenute. Invertire l'ordine degli elementi per far corrispondere il primo accordo all'elemento zero; e sostituire i risultati delle singole operazioni con il totale cumulativo, cioè con il saldo. Viene eseguito nel seguente frammento di codice:
// Write the accumulated data into output arrays with reversed indexation: for( index = 0; index < _max_trades; index++ ) { last_result_array[ _max_trades - 1 - index ] = group_result_array[ index ]; last_datetime_array[ _max_trades - 1 - index ] = group_datetime_array[ index ]; } // Replace the results of single trades with the cumulative total in the output array: for( index = 1; index < _max_trades; index++ ) { last_result_array[ index ] += last_result_array[ index - 1 ]; }
Classe TBalanceSlope
Questa classe è destinata a effettuare operazioni con la curva di saldo di un conto. Viene generato dalla classe TBalanceHistory ed eredita tutti i suoi dati e metodi protetti e pubblici. Diamo un'occhiata dettagliata alla sua struttura:
//--------------------------------------------------------------------- // Operations with the balance curve: //--------------------------------------------------------------------- class TBalanceSlope : public TBalanceHistory { private: double current_slope; // current angle of slope of the balance curve int slope_count_points; // number of points ( trades ) for calculation of slope angle private: double LR_koeff_A, LR_koeff_B; // rates for the equation of the straight-line regression double LR_points_array[ ]; // array of point of the straight-line regression private: void CalcLR( double& X[ ], double& Y[ ] ); // calculate the equation of the straight-line regression public: void SetSlopePoints( int _number ); // set the number of points for calculation of angle of slope double CalcSlope( ); // calculate the slope angle public: void TBalanceSlope( ); // constructor void ~TBalanceSlope( ); // destructor };
Determineremo l'angolo di pendenza della curva di bilanciamento dall'angolo di pendenza della linea di regressione lineare disegnata per la quantità specificata di punti (scambi) sulla curva di bilanciamento. Quindi, prima di tutto, dobbiamo calcolare l'equazione della regressione in linea retta della seguente forma: A*x + B. Il metodo seguente esegue questo lavoro:
//--------------------------------------------------------------------- // Calculate the equation of the straight-line regression: //--------------------------------------------------------------------- // input parameters: // X[ ] - arras of values of number series on the X axis; // Y[ ] - arras of values of number series on the Y axis; //--------------------------------------------------------------------- void TBalanceSlope::CalcLR( double& X[ ], double& Y[ ] ) { double mo_X = 0, mo_Y = 0, var_0 = 0, var_1 = 0; int i; int size = ArraySize( X ); double nmb = ( double )size; // If the number of points is less than two, the curve cannot be calculated: if( size < 2 ) { return; } for( i = 0; i < size; i++ ) { mo_X += X[ i ]; mo_Y += Y[ i ]; } mo_X /= nmb; mo_Y /= nmb; for( i = 0; i < size; i++ ) { var_0 += ( X[ i ] - mo_X ) * ( Y[ i ] - mo_Y ); var_1 += ( X[ i ] - mo_X ) * ( X[ i ] - mo_X ); } // Value of the A coefficient: if( var_1 != 0.0 ) { LR_koeff_A = var_0 / var_1; } else { LR_koeff_A = 0.0; } // Value of the B coefficient: LR_koeff_B = mo_Y - LR_koeff_A * mo_X; // Fill the array of points that lie on the regression line: ArrayResize( LR_points_array, size ); for( i = 0; i < size; i++ ) { LR_points_array[ i ] = LR_koeff_A * X[ i ] + LR_koeff_B; } }
Qui usiamo il metodo dei minimi quadrati per calcolare l'errore minimo di posizione della linea di regressione relativamente ai dati iniziali. Viene riempito anche l’array che memorizza le coordinate Y, che si trovano sulla linea calcolata. Questo array non viene utilizzato per il momento ed è destinato a ulteriori sviluppi.
Il metodo principale utilizzato nella classe data è TBalanceSlope::CalcSlope. Restituisce l'angolo di pendenza della curva di bilanciamento che viene calcolato dalla quantità specificata delle ultime operazioni. Ecco la sua realizzazione:
//--------------------------------------------------------------------- // Calculate slope angle: //--------------------------------------------------------------------- double TBalanceSlope::CalcSlope( ) { // Get result of trading from the history of trades: int nmb = GetTradeResultsArray( slope_count_points ); if( nmb < slope_count_points ) { return( 0.0 ); } // Calculate the regression line by the results of last trades: CalcLR( last_datetime_array, last_result_array ); current_slope = LR_koeff_A; return( current_slope ); }
Prima di tutto, viene analizzata la quantità specificata degli ultimi punti della curva di bilanciamento. Viene eseguito chiamando il metodo della classe base TBalanceSlope::GetTradeResultsArray. Se la quantità di punti di lettura non è inferiore a quella specificata, viene calcolata la linea di regressione. Viene eseguito utilizzando il metodo TBalanceSlope::CalcLR. Riempiti nel passaggio precedente, gli array last_result_array e last_datetime_array, che appartengono alla classe base, vengono utilizzate come argomenti.
Il resto dei metodi sono semplici e non richiedono una descrizione dettagliata.
Classe TBalanceSlopeControl
È la classe base che gestisce la pendenza della curva di bilanciamento modificando il volume di lavoro. Viene generato dalla classe TBalanceSlope ed eredita tutti i suoi metodi e dati pubblici e protetti. L'unico scopo di questa classe è calcolare il volume di lavoro corrente in base all'angolo di pendenza corrente della curva di bilanciamento. Diamo un'occhiata dettagliata:
//--------------------------------------------------------------------- // Managing slope of the balance curve: //--------------------------------------------------------------------- enum LotsState { LOTS_NORMAL = 1, // mode of trading with normal volume LOTS_REJECTED = -1, // mode of trading with lowered volume LOTS_INTERMEDIATE = 0, // mode of trading with intermediate volume }; //--------------------------------------------------------------------- class TBalanceSlopeControl : public TBalanceSlope { private: double min_slope; // slope angle that corresponds to the mode of volume rejection double max_slope; // slope angle that corresponds to the mode of normal volume double centr_slope; // slope angle that corresponds to the mode of volume switching without hysteresis private: ControlType control_type; // type of the regulation function private: double rejected_lots; // volume in the rejection mode double normal_lots; // volume in the normal mode double intermed_lots; // volume in the intermediate mode private: LotsState current_lots_state; // current mode of volume public: void SetControlType( ControlType _control ); // set type of the regulation characteristic void SetControlParams( double _min_slope, double _max_slope, double _centr_slope ); public: double CalcTradeLots( double _min_lots, double _max_lots ); // get trade volume protected: double CalcIntermediateLots( double _min_lots, double _max_lots, double _slope ); public: void TBalanceSlopeControl( ); // constructor void ~TBalanceSlopeControl( ); // destructor };
Prima di calcolare il volume corrente, dobbiamo impostare i parametri iniziali. Viene eseguito chiamando i seguenti metodi:
void SetControlType( ControlType _control ); // set type of the regulation characteristic
Parametro di input_control - questo è il tipo di caratteristica di regolazione. Può avere il seguente valore:
- STEP_WITH_HYSTERESISH - passo con la caratteristica di regolazione dell'isteresi;
- STEP_WITHOUT_HYSTERESIS - passo senza caratteristica di regolazione dell'isteresi;
- LINEARE - caratteristica di regolazione lineare;
- NON_LINEAR - caratteristica di regolazione non lineare (non implementata in questa versione);
void SetControlParams( double _min_slope, double _max_slope, double _centr_slope );
I parametri di input sono i seguenti:
- _min_slope - angolo di pendenza della curva di bilanciamento che corrisponde al trading con un volume minimo;
- _max_slope - angolo di pendenza della curva di bilanciamento che corrisponde al trading con il volume massimo;
- _centr_slope - angolo di pendenza della curva di bilanciamento che corrisponde alla caratteristica di regolazione a gradini senza isteresi;
Il volume viene calcolato utilizzando il seguente metodo:
//--------------------------------------------------------------------- // Get trade volume: //--------------------------------------------------------------------- double TBalanceSlopeControl::CalcTradeLots( double _min_lots, double _max_lots ) { // Try to calculate slope of the balance curve: double current_slope = CalcSlope( ); // If the specified amount of trades is not accumulated yet, trade with minimal volume: if( GetRealTrades( ) < GetSlopePoints( )) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); return( rejected_lots ); } // If the regulation function is stepped without hysteresis: if( control_type == STEP_WITHOUT_HYSTERESIS ) { if( current_slope < centr_slope ) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); return( rejected_lots ); } else { current_lots_state = LOTS_NORMAL; normal_lots = trade_symbol.NormalizeLots( _max_lots ); return( normal_lots ); } } // If the slope of linear regression for the balance curve is less than the allowed one: if( current_slope < min_slope ) { current_lots_state = LOTS_REJECTED; rejected_lots = trade_symbol.NormalizeLots( _min_lots ); return( rejected_lots ); } // If the slope of linear regression for the balance curve is greater than specified: if( current_slope > max_slope ) { current_lots_state = LOTS_NORMAL; normal_lots = trade_symbol.NormalizeLots( _max_lots ); return( normal_lots ); } // The slope of linear regression for the balance curve is within specified borders (intermediate state): current_lots_state = LOTS_INTERMEDIATE; // Calculate the value of intermediate volume: intermed_lots = CalcIntermediateLots( _min_lots, _max_lots, current_slope ); intermed_lots = trade_symbol.NormalizeLots( intermed_lots ); return( intermed_lots ); }
I principali punti significativi di implementazione del metodo TBalanceSlopeControl::CalcTradeLotsono i seguenti:
- Fino a quando non viene accumulata la quantità minima specificata di trade, fai trading con un volume minimo. È logico, perché non è noto, in quale periodo (redditizio o meno) si trova attualmente l'Expert Advisor, subito dopo averlo impostato per il trading.
- Se la funzione di regolazione è quella a gradini senza isteresi, per impostare l'angolo di commutazione tra le modalità di trading tramite il metodo TBalanceSlopeControl::SetControlParams è necessario utilizzare solo il parametro _centr_slope. I parametri _min_slope e _max_slope vengono ignorati. Viene fatto per eseguire l'ottimizzazione corretta da questo parametro nello Strategy Tester MetaTrader 5.
A seconda dell'angolo di pendenza calcolato, il trading viene eseguito con un volume minimo, massimo o intermedio. Il volume intermedio viene calcolato tramite il metodo semplice - TBalanceSlopeControl::CalcIntermediateLots. Questo metodo è protetto e viene utilizzato all'interno della classe. Il suo codice è mostrato di seguito:
//--------------------------------------------------------------------- // Calculation of intermediate volume: //--------------------------------------------------------------------- double TBalanceSlopeControl::CalcIntermediateLots( double _min_lots, double _max_lots, double _slope ) { double lots; // If the regulation function is stepped with hysteresis: if( control_type == STEP_WITH_HYSTERESISH ) { if( current_lots_state == LOTS_REJECTED && _slope > min_slope && _slope < max_slope ) { lots = _min_lots; } else if( current_lots_state == LOTS_NORMAL && _slope > min_slope && _slope < max_slope ) { lots = _max_lots; } } // If the regulation function is linear: else if( control_type == LINEAR ) { double a = ( _max_lots - _min_lots ) / ( max_slope - min_slope ); double b = normal_lots - a * .max_slope; lots = a * _slope + b; } // If the regulation function is non-linear ( not implemented yet ): else if( control_type == NON_LINEAR ) { lots = _min_lots; } // If the regulation function is unknown: else { lots = _min_lots; } return( lots ); }
Altri metodi di questa classe non richiedono alcuna descrizione.
Esempio di Incorporamento del Sistema in un Expert Advisor
Consideriamo il processo di implementazione del sistema di controllo della pendenza della curva di bilanciamento in un Expert Advisor passo dopo passo.
Passaggio 1 - aggiunta delle istruzioni per collegare la libreria sviluppata all'Expert Advisor:
#include <BalanceSlopeControl.mqh>
Passaggio 2 - aggiunta delle variabili esterne per l'impostazione dei parametri del sistema di controllo della pendenza della linea di bilanciamento all'Expert Advisor:
//--------------------------------------------------------------------- // Parameters of the system of controlling the slope of the balance curve; //--------------------------------------------------------------------- enum SetLogic { No = 0, Yes = 1, }; //--------------------------------------------------------------------- input SetLogic UseAutoBalanceControl = No; //--------------------------------------------------------------------- input ControlType BalanceControlType = STEP_WITHOUT_HYSTERESIS; //--------------------------------------------------------------------- // Amount of last trades for calculation of LR of the balance curve: input int TradesNumberToCalcLR = 3; //--------------------------------------------------------------------- // Slope of LR to decrease the volume to minimum: input double LRKoeffForRejectLots = -0.030; //--------------------------------------------------------------------- // Slope of LR to restore the normal mode of trading: input double LRKoeffForRestoreLots = 0.050; //--------------------------------------------------------------------- // Slope of LR to work in the intermediate mode: input double LRKoeffForIntermedLots = -0.020; //--------------------------------------------------------------------- // Decrease the initial volume to the specified value when the LR is inclined down input double RejectedLots = 0.10; //--------------------------------------------------------------------- // Normal work volume in the mode of MM with fixed volume: input double NormalLots = 1.0;
Passaggio 3 - aggiunta dell'oggetto del tipo TBalanceSlopeControl a Expert Advisor:
TBalanceSlopeControl BalanceControl;
Questa dichiarazione può essere aggiunta all'inizio dell'Expert Advisor, prima delle definizioni delle funzioni.
Passaggio 4 - aggiunta del codice per l'inizializzazione del sistema di controllo della curva di bilanciamento alla funzione OnInit dell'Expert Advisor:
// Adjust our system of controlling the slope of the balance curve: BalanceControl.SetTradeSymbol( Symbol( )); BalanceControl.SetControlType( BalanceControlType ); BalanceControl.SetControlParams( LRKoeffForRejectLots, LRKoeffForRestoreLots, LRKoeffForIntermedLots ); BalanceControl.SetSlopePoints( TradesNumberToCalcLR ); BalanceControl.SetFiltrParams( 0, -1, 0 ); BalanceControl.SetMonitoringBeginDate( 0 );
Passaggio 5 - aggiunta della chiamata di metodo per aggiornare le informazioni di mercato correnti alla funzione OnTick dell'Expert Advisor:
// Refresh market information:
BalanceControl.RefreshSymbolInfo( );
La chiamata di questo metodo può essere aggiunta all'inizio della funzione OnTick o dopo il controllo della nuova barra in arrivo (per gli Expert Advisor con tale controllo).
Passaggio 6 - aggiunta del codice per il calcolo del volume corrente prima del codice in cui vengono aperte le posizioni:
if( UseAutoBalanceControl == Yes ) { current_lots = BalanceControl.CalcTradeLots( RejectedLots, NormalLots ); } else { current_lots = NormalLots; }
Se nell'Expert Advisor viene utilizzato un sistema di Money Management, allora invece dei NormalLots dovresti scrivere il metodo TBalanceSlopeControl::CalcTradeLots - il volume corrente calcolato dal sistema MM dell'Expert Advisor.
Test Expert Advisor BSCS-TestExpert.mq5 con il sistema integrato sopra descritto è allegato a questo articolo. Il principio del suo funzionamento si basa sull'intersezione dei livelli dell'indicatore CCI. Questo Expert Advisor è sviluppato per i test e non è adatto per lavorare su conti reali. Lo testeremo al timeframe H4 (2008.07.01 - 2010.09.01) di EURUSD.
Analizziamo il risultato del funzionamento di questo EA. Di seguito è riportato il grafico del cambio di equilibrio con il sistema di controllo della pendenza disabilitato. Per visualizzarlo, impostare il valore No per il parametro esterno UseAutoBalanceControl.
Figura 4. Grafico iniziale della variazione del saldo
Ora impostare il parametro esterno UseAutoBalanceControl su Yes e testare Expert Advisor. Otterrai il grafico con il sistema abilitato di controllo della pendenza del saldo.
Figura 5. Grafico della variazione del saldo con il sistema di controllo abilitato
Puoi vedere che la maggior parte dei periodi nel grafico superiore (fig.4) appaiono come vengono tagliati e hanno una forma piatta nel grafico inferiore (fig.5). Questo è il risultato del funzionamento del nostro sistema. È possibile confrontare i principali parametri di funzionamento dell'Expert Advisor:
Parametro | UseAutoBalanceControl = No | UseAutoBalanceControl = Sì |
---|---|---|
Utile netto totale: | 18 378.00 | 17 261.73 |
Fattore di profitto | 1.47 | 1.81 |
Fattore di recupero | 2.66 | 3.74 |
Profitto previsto | 117.81 | 110.65 |
Prelievo assoluto del saldo: | 1 310.50 | 131.05 |
Prelievo assoluto di capitale: | 1 390.50 | 514.85 |
Prelievo massimo di equilibrio: | 5 569.50 (5.04%) | 3 762.15 (3.35%) |
Prelievo massimo di capitale: | 6 899.50 (6.19%) | 4 609.60 (4.08%) |
I migliori parametri tra quelli confrontati sono evidenziati con il colore verde. Il profitto e il payoff previsti sono leggermente diminuiti; questo è l'altro lato della regolamentazione che appare come risultato dei ritardi di commutazione tra gli stati del volume di lavoro. Tutto sommato, c'è un miglioramento dei tassi di lavoro dell'Expert Advisor. In particolare, un miglioramento del drawdown e del fattore di profitto.
Conclusione
Noto diversi modi per migliorare questo sistema:- Utilizzo del trading virtuale quando l'Expert Advisor entra in un periodo di lavoro sfavorevole. Quindi il normale volume di lavoro non avrà più importanza. Permetterà di diminuire il drawdown.
- Utilizzo di algoritmi più complessi per determinare lo stato attuale di funzionamento dell'Expert Advisor (redditizio o meno). Ad esempio, possiamo provare ad applicare una rete neuronale per tale analisi. In questo caso, ovviamente, sono necessarie ulteriori indagini.
Pertanto, abbiamo considerato il principio e il risultato del funzionamento del sistema, che consente di migliorare le caratteristiche qualitative di un Expert Advisor. L'operazione congiunta con il sistema di gestione del denaro, in alcuni casi, consente di aumentare la redditività senza aumentare il rischio.
Vi ricordo ancora una volta: nessun sistema ausiliario può rendere redditizio un Expert Advisor da uno perdente.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/145
- 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