![Sviluppare un Expert Advisor per il trading da zero (Parte 8): Un salto concettuale](https://c.mql5.com/2/49/Developing_a_trading_Expert_Advisor_from_scratch_013_600x314.jpg)
Sviluppare un Expert Advisor per il trading da zero (Parte 8): Un salto concettuale
Introduzione
A volte, durante lo sviluppo di alcuni progetti, possiamo trovare nuovi ideali e nuove possibili caratteristiche che possono tornare utili e fornire un grande miglioramento al sistema che stiamo creando. Ma la domanda sorge spontanea: qual è il modo più semplice per implementare la nuova funzionalità?
Il problema è che a volte dobbiamo dimenticare tutto ciò che è già stato sviluppato e ricominciare da zero. Ed è abbastanza demotivante. Nel corso del tempo, dopo oltre 20 anni di programmazione C++, ho sviluppato una certa linea di pensiero. Sviluppiamo alcuni concetti che aiutano a pianificare e apportare modifiche con il minimo sforzo, ma a volte le cose possono cambiare e diventare molto più complesse di quanto inizialmente stimato.
Fino ad ora, abbiamo costruito l'EA in modo tale che possa ricevere nuovo codice senza perdere le funzionalità correnti: abbiamo semplicemente creato e aggiunto classi. Ora dobbiamo fare un passo indietro e poi fare due passi avanti. Questo passo indietro ci permetterà di introdurre nuove funzionalità. Questa funzionalità è una classe per finestre con alcune informazioni basate su dei modelli; questa sarà la prima parte. Cambieremo radicalmente il codice, mantenendo tutte le funzionalità che abbiamo al momento, e nella seconda parte ci occuperemo dell'IDE.
Progettazione
Il nostro Expert Advisor è attualmente strutturato in una classe di oggetti. Questo può essere visto nel diagramma qui sotto.
Il sistema è attualmente ben funzionante ed è molto stabile. Ma ora dovremo ristrutturare l'EA come mostrato di seguito. Potrai notare che c'è una classe aggiuntiva, mentre le posizioni di C_TemplateChart e C_SubWindow sono cambiate.
Qual è lo scopo di una tale ristrutturazione? Il problema è che il modo in cui sono state implementate le finestre fluttuanti non è adatto per le finestre contenenti dati di asset, e quindi tali modifiche sono necessarie. Tuttavia, questo cambiamento non è solo estetico in termini di struttura, ma ha anche richiesto un cambiamento estremo nel codice, quindi sarà molto diverso dal codice precedente.
Quindi, mettiamoci al lavoro.
Implementazione pratica
1. Modifiche al codice interno dell'Expert Advisor
Il primo grande cambiamento inizia nel file di inizializzazione dell'EA. Vedi il codice qui sotto:
input group "Window Indicators" input string user01 = ""; //Sottofinestra dell'indicatore input group "WallPaper" input string user10 = "Wallpaper_01"; //Bitmap Utilizzata input char user11 = 60; //Trasparenza (da 0 a 100) input C_WallPaper::eTypeImage user12 = C_WallPaper::IMAGEM; //Tipo di sfondo del grafico input group "Chart Trader" input int user20 = 1; //fattore di leva input int user21 = 100; //Take Profit (finanziario) input int user22 = 75; //Stop Loss (finanziario) input color user23 = clrBlue; //Colore della linea del prezzo input color user24 = clrForestGreen; //Colore della linea del take Profit input color user25 = clrFireBrick; //Colore linea di Stop input bool user26 = true; //Giorno di trade? input group "Volume At Price" input color user30 = clrBlack; //Colore delle barre input char user31 = 20; //Trasparenza (da 0 a 100 ) //+------------------------------------------------------------------+ C_TemplateChart Chart; C_WallPaper WallPaper; C_VolumeAtPrice VolumeAtPrice; //+------------------------------------------------------------------+ int OnInit() { static string memSzUser01 = ""; Terminal.Init(); WallPaper.Init(user10, user12, user11); if (memSzUser01 != user01) { Chart.ClearTemplateChart(); Chart.AddThese(memSzUser01 = user01); } Chart.InitilizeChartTrade(user20, user21, user22, user23, user24, user25, user26); VolumeAtPrice.Init(user24, user25, user30, user31); OnTrade(); EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+
Da notare che ora abbiamo solo una variabile che indica quali modelli verranno caricati. Il resto del codice sembra essere come era, ad eccezione della parte evidenziata. Potrebbe non essere del tutto chiaro cosa fa qui, o perché questo codice evidenziato è stato inserito nella parte di inizializzazione dell' EA. Quando carichiamo l'EA su un grafico, crea alcune cose e nell'uso normale possono essere modificate. In precedenza, non aveva senso aggiungere il codice evidenziato, perché tutto era destinato a funzionare insieme, e le modifiche non hanno influenzato il comportamento o l'aspetto dell'EA. Ma quando aggiungiamo indicatori mobili, succede qualcosa di fastidioso: ogni volta che cambiamo il timeframe, l'EA si riavvia e le finestre tornano allo stato originale. Se non eliminate, cose inutili verranno accumulate sul grafico, e se le elimini in modo errato, verranno ricostruite nei loro luoghi originali che è anche un grande seccatura. Se l'utente non modifica i modelli richiesti, il codice evidenziato impedirà che le finestre mobili vengano eliminate in modo errato, ma se ci sono modifiche ai modelli, l'EA si riavvierà normalmente. Questo è molto semplice ma estremamente efficiente.
La prossima cosa a cui prestare attenzione è legata al sistema di messaggistica interna. In precedenza aveva una variabile aggiuntiva, ma l'abbiamo rimossa, quindi il codice ora appare come segue:
void OnTick() { Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, NanoEA.CheckPosition(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eRESULT]); }
Il sistema ora utilizza il sistema di scambio di messaggi MQL5 in modo più efficiente, mentre l'invio di messaggi è molto simile alla funzione OnChartEvent stessa. Questo permette il passaggio dei parametri senza stress alla classe oggetto in modo che ogni classe possa elaborare i messaggi di evento generati dal sistema MetaTrader 5 nel modo più appropriato. In questo modo, isoliamo ulteriormente ogni classe di oggetti, e quindi l'EA può assumere forme più diverse per ogni tipo di utente e con meno sforzo.
2. Modifiche al codice di supporto della sottofinestra
Fino a questo punto, il codice della sotto finestra era molto semplice, ma aveva un problema: per un motivo o per l'altro, l'EA non poteva eliminare la sotto finestra creata. Quando l'Expert Advisor è stato riaperto, è stata creata una nuova sotto-finestra, a causa della quale il controllo nel sistema è stato perso. Sorprendentemente, è stato molto facile risolvere il problema. Innanzitutto, dai un'occhiata al frammento del file di supporto mostrato di seguito:
int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "SubWinSupport"); return INIT_SUCCEEDED; }
La riga evidenziata creerà un alias per il file di supporto. L'EA vedrà questo alias EA e controllerà se il sistema della sottofinestra è caricato o meno. Questo viene fatto indipendentemente dal nome del file in quanto l'EA controlla solo l'alias. Lo stesso tipo di codice verrà utilizzato in seguito per supportare altre cose nell'EA. Non entrerò troppo nei dettagli ma più avanti, in un altro articolo, spiegherò come sfruttare il codice evidenziato.
Ora che questo è fatto, diamo un'occhiata al codice che carica e crea le sottofinestre. E' mostrato di seguito:
void Init(void) { int i0; if ((i0 = ChartWindowFind(Terminal.Get_ID(), def_Indicador)) == -1) ChartIndicatorAdd(Terminal.Get_ID(), i0 = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL), iCustom(NULL, 0, "::" + def_Resource)); m_IdSubWinEA = i0; }
Come potete vedere, è molto più semplice, ma questo codice non è public, vi si accede attraverso un altro codice che è public:
inline int GetIdSubWinEA(void) { if (m_IdSubWinEA < 0) Init(); return m_IdSubWinEA; }
Ma perché viene implementato in questo modo? Può succedere che l'EA non utilizzi indicatori nella sotto-finestra, e quando il sistema lo realizza, rimuove la sotto-finestra dal grafico e la crea solo se necessario. Ma questa decisione non è presa dal codice dell'EA, ma dalla classe C_TemplateChart.
Dai un'occhiata alla seguente animazione:
Notate che ora abbiamo una linea verticale che indica dove stiamo analizzando. Tali linee sono indipendenti l'una dall'altra. Non erano disponibili in precedenza, il che ha reso difficile analizzare alcuni punti dell'indicatore a seconda del grafico. Questo è uno dei miglioramenti inclusi nella classe C_TemplateChart. Diamo un'occhiata al codice all'interno della classe per capire ulterioriormente i cambiamenti, in quanto erano sostanziali.
Diamo un'occhiata al seguente codice, dove vengono dichiarate le variabili:
class C_TemplateChart : public C_SubWindow { #define def_MaxTemplates 8 #define def_NameTemplateRAD "IDE" //+------------------------------------------------------------------+ private : //+------------------------------------------------------------------+ enum eParameter {TEMPLATE = 0, PERIOD, SCALE, WIDTH, HEIGHT}; //+------------------------------------------------------------------+ struct st { string szObjName, szSymbol, szTemplate, szVLine; int width, scale; ENUM_TIMEFRAMES timeframe; long handle; }m_Info[def_MaxTemplates]; int m_Counter, m_CPre, m_Aggregate; struct st00 { int counter; string Param[HEIGHT + 1]; }m_Params;
La prima cosa a cui prestare attenzione è che la classe C_TemplateChart estenderà la classe C_SubWindow. Questa parte di codice non sembra speciale, ma presta attenzione alla parte evidenziata: indica un sistema interno di analisi dei dati con cui è possibile creare e presentare in modo appropriato gli indicatori richiesti dall'utente. Ora il sistema per descrivere come l'utente indicherà le cose inizia ad essere standardizzato. Anche se sembra confuso, diventerà chiaro nel tempo. Per spiegare il nuovo formato, è necessario analizzare il seguente frammento, che è responsabile dell'analisi della richiesta dell'utente:
int GetCommand(int iArg, const string szArg) { for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = ""; m_Params.counter = 0; for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg]) { case ')': case ';': m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2); for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++); return iArg + 1; case ' ': c2 += (c1 == iArg ? 0 : 1); c1 = (c1 == iArg ? iArg + 1 : c1); break; case '(': case ',': c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2); m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2); c2 = 0; c1 = iArg + 1; break; default: c2++; break; } return -1; }
Prima di tutto, cancellare tutti i dati che erano in precedenza nella struttura. Quindi iniziamo ad analizzare e ricevere parametro per parametro, se ce ne sono. Non sono richiesti, ma indicano come le cose saranno presentate all'utente. Questo sistema è autoespandibile. Quindi, se vuoi aggiungere più informazioni, devi solo specificarle nell'enumerazione eParameter. Attualmente il sistema ha cinque parametri, e dovrebbero essere specificati nello stesso ordine come sono indicati nell'enumerazione eParameters. Questa enum è evidenziata nella parte della dichiarazione delle variabili. I parametri nell'ordine corretto e le relative spiegazioni sono riportate di seguito.
Parametro | Risultato |
---|---|
1. TEMPLATE or ASSET | Specifica quale modello o risorsa visualizzare |
2. PERIOD | Se specificato, imposterà l'indicatore al periodo fisso specificato, proprio come è stato utilizzato prima |
3. SCALE | Se specificato, l'indicatore sarà legato a una scala fissa. |
4. WIDTH | Se specificato, il parametro imposterà la larghezza dell'indicatore nella finestra. |
5. HEIGHT | Questo nuovo parametro sarà discusso più avanti in questo articolo - indica l'uso di una finestra mobile. |
L'unica struttura che non trarrà beneficio dal parametro 5 al momento è l'IDE, ma questo sarà risolto e nel prossimo articolo mostrerò come trarre beneficio dall'IDE. Questo articolo si concentrerà su altri sistemi.
Ora supponiamo che per un motivo o un altro vogliamo consentire all'utente di controllare il colore della linea verticale dell'indicatore. Non c'è bisogno di apportare modifiche al codice di analisi dei parametri - basta apportare la modifica come segue:
class C_TemplateChart : public C_SubWindow { #define def_MaxTemplates 8 #define def_NameTemplateRAD "IDE" //+------------------------------------------------------------------+ private : //+------------------------------------------------------------------+ enum eParameter {TEMPLATE = 0, COLOR_VLINE, PERIOD, SCALE, WIDTH, HEIGHT}; //+------------------------------------------------------------------+ // ... Codice interno.... struct st00 { int counter; string Param[HEIGHT + 1]; }m_Params;
Il sistema riconoscerà automaticamente che ci possono essere sei parametri in una chiamata. Ora sorge un altro problema. Sebbene il codice GetCommand sopra funzioni correttamente, è presente un bug. Spesso non notiamo questo bug quando creiamo il sistema che noi stessi useremo. Ma quando il sistema viene reso disponibile per l'uso da parte di altre persone, l'errore diventa evidente e potrebbe lasciare alcuni programmatori meno esperti incerti su come risolvere il problema. Ecco perché la programmazione orientata agli oggetti è così apprezzata - permette di creare il modello più appropriato per l'uso nei programmi di maggiore interesse. Uno dei prerequisiti di OOP è assicurarsi che i dati e le variabili della classe siano inizializzati correttamente. Per garantire questo, è importante testare tutto. Anche se il codice GetCommand sembra corretto, contiene un bug - non controlla il limite massimo dei parametri. Se il modello originale accetta solo cinque parametri, cosa succede se l'utente imposta sei parametri? Questo è qualcosa da evitare: non dobbiamo presumere che tutto funzionerà, ma garantire che tutto funzionerà. Pertanto, il codice deve essere corretto come mostrato di seguito (le correzioni sono evidenziate).
int GetCommand(int iArg, const string szArg) { for (int c0 = TEMPLATE; c0 <= HEIGHT; c0++) m_Params.Param[c0] = ""; m_Params.counter = 0; for (int c1 = iArg, c2 = 0; szArg[iArg] != 0x00; iArg++) switch (szArg[iArg]) { case ')': case ';': m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2); for (; (szArg[iArg] != 0x00) && (szArg[iArg] != ';'); iArg++); return iArg + 1; case ' ': c2 += (c1 == iArg ? 0 : 1); c1 = (c1 == iArg ? iArg + 1 : c1); break; case '(': case ',': if (m_Params.counter == HEIGHT) return StringLen(szArg) + 1; c2 = (m_Params.counter == SCALE ? (c2 >= 1 ? 1 : c2) : c2); m_Params.Param[m_Params.counter++] = StringSubstr(szArg, c1, c2); c2 = 0; c1 = iArg + 1; break; default: c2++; break; } return -1; }
Aggiungendo semplicemente una sola riga eviteremo al sistema di generare un risultato inaspettato, perché se l'ultimo parametro atteso è HEIGHT, ma non è l'ultimo, questo logicamente significa che qualcosa non va, quindi il sistema ignora tutto ciò che è dichiarato in seguito e quindi evita problemi.
Se non capisci come il sistema riconosce modelli e parametri, ecco la sintassi:
Parameter_00 (Parameter_01, Parameter_02, Parameter_03, Parameter_04)
Dove Parameter_00 specifica il modello da usare e il resto separati da virgole ( , ) indicano i valori che sono definiti nell'enumerazione eParameter. Se vogliamo cambiare solo Parameter_03, possiamo lasciare il resto vuoto, come mostrato in figura. In questa figura, mostro che il sistema funziona nel modo desiderato dall'utente.
Si noti che abbiamo un'indicazione standardizzata che è molto simile alle chiamate di funzione, ma può sembrare fonte di confusione. Ecco cosa è successo: specifichiamo il modello RSI, quindi non specifichiamo né il periodo né la scala. Questi valori sono lasciati vuoti in modo che il sistema capisca che deve seguire il grafico principale. Ma specifichiamo la larghezza e l'altezza, in modo che il sistema capisca che questo dovrebbe essere visualizzato in una finestra mobile, e quindi l'indicatore RSI appaia in una finestra mobile. Nel modello ADX, indichiamo solo la larghezza in modo che il sistema lo visualizzi con la larghezza definita nella sotto finestra. L'indicatore Stoch prenderà la sottofinestra rimanente per intero, condividendo lo spazio con ADX. Ma se l'utente vuole cambiare qualcosa, non sarà difficile, guarda cosa succede quando specifichiamo l'altezza per l'ADX.
Il sistema cambierà immediatamente il modo in cui l'ADX viene presentato - lo metterà in una finestra mobile, lasciando l'intera sotto-finestra allo Stoch. Ogni finestra mobile sarà completamente indipendente l'una dall'altra. Ma va oltre ciò che si può vedere, guarda la prossima foto.
Notare che la sotto finestra è stata cancellata perché non è più necessaria. Ma quale funzione è responsabile di tutto ciò? E 'mostrato di seguito - un sacco di cose interessanti possono essere fatte con poche modifiche:
void AddTemplate(void) { ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT; string sz0 = m_Params.Param[PERIOD]; int w, h, i; bool bIsSymbol; if (sz0 == "1M") timeframe = PERIOD_M1; else if (sz0 == "2M") timeframe = PERIOD_M2; else if (sz0 == "3M") timeframe = PERIOD_M3; else if (sz0 == "4M") timeframe = PERIOD_M4; else if (sz0 == "5M") timeframe = PERIOD_M5; else if (sz0 == "6M") timeframe = PERIOD_M6; else if (sz0 == "10M") timeframe = PERIOD_M10; else if (sz0 == "12M") timeframe = PERIOD_M12; else if (sz0 == "15M") timeframe = PERIOD_M15; else if (sz0 == "20M") timeframe = PERIOD_M20; else if (sz0 == "30M") timeframe = PERIOD_M30; else if (sz0 == "1H") timeframe = PERIOD_H1; else if (sz0 == "2H") timeframe = PERIOD_H2; else if (sz0 == "3H") timeframe = PERIOD_H3; else if (sz0 == "4H") timeframe = PERIOD_H4; else if (sz0 == "6H") timeframe = PERIOD_H6; else if (sz0 == "8H") timeframe = PERIOD_H8; else if (sz0 == "12H") timeframe = PERIOD_H12; else if (sz0 == "1D") timeframe = PERIOD_D1; else if (sz0 == "1S") timeframe = PERIOD_W1; else if (sz0 == "1MES") timeframe = PERIOD_MN1; if ((m_Counter >= def_MaxTemplates) || (m_Params.Param[TEMPLATE] == "")) return; bIsSymbol = SymbolSelect(m_Params.Param[TEMPLATE], true); w = (m_Params.Param[WIDTH] != "" ? (int)StringToInteger(m_Params.Param[WIDTH]) : 0); h = (m_Params.Param[HEIGHT] != "" ? (int)StringToInteger(m_Params.Param[HEIGHT]) : 0); i = (m_Params.Param[SCALE] != "" ? (int)StringToInteger(m_Params.Param[SCALE]) : -1); i = (i > 5 || i < 0 ? -1 : i); if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); else { SetBase(m_Params.Param[TEMPLATE], (bIsSymbol ? m_Params.Param[TEMPLATE] : _Symbol), timeframe, i, w); if (!ChartApplyTemplate(m_Info[m_Counter - 1].handle, m_Params.Param[TEMPLATE] + ".tpl")) if (bIsSymbol) ChartApplyTemplate(m_Info[m_Counter - 1].handle, "Default.tpl"); if (m_Params.Param[TEMPLATE] == def_NameTemplateRAD) { C_Chart_IDE::Create(GetIdSubWinEA()); m_Info[m_Counter - 1].szVLine = ""; }else { m_Info[m_Counter - 1].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Info[m_Counter - 1].handle, m_Info[m_Counter - 1].szVLine, OBJPROP_COLOR, clrBlack); } ChartRedraw(m_Info[m_Counter - 1].handle); } }
Le parti evidenziate mostrano la selezione del modo in cui i dati saranno presentati sullo schermo. Il codice non differisce molto dal precedente. Ma questi test garantiscono che il sistema si comporterà nel modo desiderato dall'utente. Tutto questo finora non richiede una ristrutturazione del codice in un nuovo modello, ma quando guardiamo la funzione responsabile del ridimensionamento della sotto finestra, l'immagine cambia.
void Resize(void) { #define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, A, B) int x0, x1, y; if (!ExistSubWin()) return; x0 = 0; y = (int)(ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS, GetIdSubWinEA())); x1 = (int)((Terminal.GetWidth() - m_Aggregate) / (m_Counter > 0 ? (m_CPre == m_Counter ? m_Counter : (m_Counter - m_CPre)) : 1)); for (char c0 = 0; c0 < m_Counter; x0 += (m_Info[c0].width > 0 ? m_Info[c0].width : x1), c0++) { macro_SetInteger(OBJPROP_XDISTANCE, x0); macro_SetInteger(OBJPROP_XSIZE, (m_Info[c0].width > 0 ? m_Info[c0].width : x1)); macro_SetInteger(OBJPROP_YSIZE, y); if (m_Info[c0].szTemplate == "IDE") C_Chart_IDE::Resize(x0); } ChartRedraw(); #undef macro_SetInteger }
La linea evidenziata impedisce la realizzazione del sistema sulla vecchia base. Questa linea controllerà se c'è una sotto-finestra aperta e gestita dall'EA, altrimenti ritorna e la funzione non fa altro. Ma se esiste una tale sottofinestra, tutte le cose in essa contenute dovrebbero essere ridimensionate secondo necessità e, proprio a causa di questo test, il sistema è stato completamente rinnovato.
Di seguito un'altra funzione che è stata modificata:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { int mx, my; datetime dt; double p; C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam); switch (id) { case CHARTEVENT_MOUSE_MOVE: mx = (int)lparam; my = (int)dparam; ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p); for (int c0 = 0; c0 < m_Counter; c0++) if (m_Info[c0].szVLine != "") { ObjectMove(m_Info[c0].handle, m_Info[c0].szVLine, 0, dt, 0); ChartRedraw(m_Info[c0].handle); } break; case CHARTEVENT_CHART_CHANGE: Resize(); for (int c0 = 0; c0 < m_Counter; c0++) { ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_PERIOD, m_Info[c0].timeframe); ObjectSetInteger(Terminal.Get_ID(), m_Info[c0].szObjName, OBJPROP_CHART_SCALE,(m_Info[c0].scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Info[c0].scale)); } break; } }
La parte evidenziata merita davvero un'attenzione speciale. Che cosa fa? Questo codice presenterà la linea verticale nel posto giusto e nel modello giusto. Il resto del codice manterrà e regolerà semplicemente i modelli man mano che il grafico cambia.
Ci sono molti vantaggi nel farlo qui nella classe oggetto piuttosto che nell'EA all'interno del sistema OnChartEvent. La cosa principale è che ogni classe può gestire l'evento che MetaTrader 5 invia all'EA. Invece di centralizzare tutto in un'unica funzione, lasciamo che ogni classe faccia il suo lavoro, e se non vogliamo usare la classe nell'EA, la rimuoviamo semplicemente senza effetti collaterali per il resto del codice.
La programmazione è BELLISSIMA, vero? AMO la programmazione...
Prima di passare al punto successivo in questo articolo, vorrei brevemente commentare i valori che possono essere utilizzati nei parametri 1 e 2. Al parametro 1 possono essere assegnati i seguenti valori:1M, 2M, 3M, 4M, 5M, 6M, 10M, 12M, 15M, 20M, 30M, 1H, 2H, 3H, 4H, 6H, 8H, 12H, 1D, 1S, 1MONTH. Questi valori non sono casuali - provengono dall'enumerazione ENUM_TIMEFRAME e ripete esattamente cosa faresti se usassi un grafico regolare. Il parametro 2 può assumere valori da 0 a 5, dove 0 è la scala più lontana e 5 è quella più vicina. per maggiori dettagli vedi CHART_SCALE
3.4 Supporto delle finestre mobili
Ora cerchiamo di capire come vengono create e mantenute le finestre mobili, perché senza capirlo sarà impossibile sfruttare davvero il sistema. La classe oggetto responsabile era chiamata C_ChartFloating. Uno potrebbe pensare: Perché non usare la classe Control della libreria standard MQL5? Il motivo è semplice. La classe control ci permette di creare e mantenere una finestra con una funzionalità molto simile a quella del sistema operativo presente sulla macchina, ma è troppo esagerata per il nostro scopo. Abbiamo bisogno qualcosa di più semplice. Usare la classe control per fare quello che vogliamo sarebbe come usare un bazooka per uccidere una mosca, per questo motivo usiamo la classe C_ChartFloating - contiene gli elementi minimi necessari per supportare le finestre mobili, permettendoci di controllarle.
La classe stessa non ha bisogno di molte spiegazioni, dato che l'unica cosa che facciamo è creare 4 oggetti grafici. Ma tra le funzioni interne, ce ne sono due che meritano particolare attenzione. Iniziamo con la funzione che crea la finestra:
bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1) { m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS); m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS); if (m_MaxCounter >= def_MaxFloating) return false; y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y); CreateBarTitle(); CreateCaption(sz0); CreateBtnMaxMin(); CreateRegion(TimeFrame, Scale); m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID); ChartApplyTemplate(m_Win[m_MaxCounter].handle, sz0 + ".tpl"); m_Win[m_MaxCounter].szVLine = (string)ObjectsTotal(Terminal.Get_ID(), -1, -1) + (string)MathRand(); ObjectCreate(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJ_VLINE, 0, 0, 0); ObjectSetInteger(m_Win[m_MaxCounter].handle, m_Win[m_MaxCounter].szVLine, OBJPROP_COLOR, clrBlack); m_Win[m_MaxCounter].PosX = -1; m_Win[m_MaxCounter].PosY = -1; m_Win[m_MaxCounter].PosX_Minimized = m_Win[m_MaxCounter].PosX_Maximized = x; m_Win[m_MaxCounter].PosY_Minimized = m_Win[m_MaxCounter].PosY_Maximized = y; SetDimension(w, h, true, m_MaxCounter); SetPosition(x, y, m_MaxCounter); ChartRedraw(m_Win[m_MaxCounter].handle); m_MaxCounter++; return true; }
Questo codice crea tutto il supporto necessario in modo da poter applicare il modello all'oggetto CHART. È implementato nella parte di codice evidenziata, da notare che l'unico parametro realmente richiesto per chiamare questa funzione è il nome del modello. Tutti gli altri valori sono pre-inizializzati, ma nulla impedisce di specificare quali sono necessari. Per ogni nuova finestra creata, per impostazione predefinita quella successiva verrà leggermente spostata in modo da non sovrapporsi alle altre finestre. Questo è mostrato nella seguente riga:
y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);
La prossima interessante funzione in questa classe gestisce i messaggi. Ecco il suo codice:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { int mx, my; datetime dt; double p; static int six = -1, siy = -1, sic = -1; switch (id) { case CHARTEVENT_MOUSE_MOVE: mx = (int)lparam; my = (int)dparam; if ((((int)sparam) & 1) == 1) { if (sic == -1) for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--) sic = (((mx > m_Win[c0].PosX) && (mx < (m_Win[c0].PosX + m_Win[c0].Width)) && (my > m_Win[c0].PosY) && (my < (m_Win[c0].PosY + def_SizeBarCaption))) ? c0 : -1); if (sic >= 0) { if (six < 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false); six = (six < 0 ? mx - m_Win[sic].PosX : six); siy = (siy < 0 ? my - m_Win[sic].PosY : siy); SetPosition(mx - six, my - siy, sic); } }else { if (six > 0) ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true); six = siy = sic = -1; } ChartXYToTimePrice(Terminal.Get_ID(), mx, my, my, dt, p); for (int c0 = 0; c0 < m_MaxCounter; c0++) ObjectMove(m_Win[c0].handle, m_Win[c0].szVLine, 0, dt, 0); break; case CHARTEVENT_OBJECT_CLICK: for (int c0 = 0; c0 < m_MaxCounter; c0++) if (sparam == m_Win[c0].szBtnMaxMin) { SwapMaxMin((bool)ObjectGetInteger(Terminal.Get_ID(), m_Win[c0].szBtnMaxMin, OBJPROP_STATE), c0); break; } break; case CHARTEVENT_CHART_CHANGE: for(int c0 = 0; c0 < m_MaxCounter; c0++) { ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_PERIOD, m_Win[c0].TimeFrame); ObjectSetInteger(Terminal.Get_ID(), m_Win[c0].szRegionChart, OBJPROP_CHART_SCALE,(m_Win[c0].Scale < 0 ? ChartGetInteger(Terminal.Get_ID(),CHART_SCALE):m_Win[c0].Scale)); } m_LimitX = (int)ChartGetInteger(Terminal.Get_ID(), CHART_WIDTH_IN_PIXELS); m_LimitY = (int)ChartGetInteger(Terminal.Get_ID(), CHART_HEIGHT_IN_PIXELS); break; } for (int c0 = 0; c0 < m_MaxCounter; c0++) ChartRedraw(m_Win[c0].handle); }
Questa funzione implementa tutta la gestione degli eventi che la classe C_ChartFloating supporta. Non importa quante finestre sono presenti, le gestirà tutte allo stesso modo. Se lo facessimo all'interno della funzione OnChartEvent nell'Expert Advisor, la funzione sarebbe estremamente complessa e non molto stabile. E implementando la funzionalità qui, nella classe oggetto, garantiamo l'integrità del codice. Quindi, se non abbiamo bisogno di usare finestre mobili, allora tutto quello che dobbiamo fare è rimuovere il file dalla classe e dai punti in cui è possibile accedervi. Questa implementazione consente di rendere il codice molto più veloce e più facile.
C'è anche una parte interessante nel codice sopra. È evidenziato e il suo codice interno è il seguente:
void SwapMaxMin(const bool IsMax, const int c0) { m_Win[c0].IsMaximized = IsMax; SetDimension((m_Win[c0].IsMaximized ? m_Win[c0].MaxWidth : 100), (m_Win[c0].IsMaximized ? m_Win[c0].MaxHeight : 0), false, c0); SetPosition((m_Win[c0].IsMaximized ? m_Win[c0].PosX_Maximized : m_Win[c0].PosX_Minimized), (m_Win[c0].IsMaximized ? m_Win[c0].PosY_Maximized : m_Win[c0].PosY_Minimized), c0); }
Cosa fa il codice sopra? Ti sembra troppo confuso? Per capire, diamo uno sguardo da vicino l'animazione qui sotto.
Quando viene creata una finestra mobile, ha un punto di ancoraggio iniziale, specificato dal programmatore o dal sistema di posizionamento della classe stessa. Questo punto di ancoraggio è lo stesso sia per la finestra massimizzata che per quella minimizzata. Questi valori non sono fissi, ovvero l'utente può facilmente modificare questi punti.
Supponiamo di aver bisogno di un determinato punto su un grafico vuoto, quindi puoi spostare la finestra massimizzata in un luogo facile e veloce da leggere, poi ridurre a icona la stessa finestra e spostarla in un altro posto, ad esempio, nell'angolo dello schermo . Il sistema lo ricorderà, e quando si massimizza la finestra, salterà all'ultimo punto di ancoraggio dove era prima che fosse minimizzata. Lo stesso vale per la situazione inversa quando la finestra è minimizzata.
Conclusioni
Per ora è tutto. Nel prossimo articolo estenderemo questa funzionalità alla classe di supporto IDE.
Tradotto dal portoghese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/pt/articles/10353
![Impara come progettare un sistema di trading con il Parabolic SAR](https://c.mql5.com/2/46/why-and-how__5.png)
![Sviluppare un Expert Advisor per il trading da zero (Parte 7): Aggiunta dei Volumi al Prezzo (I)](https://c.mql5.com/2/45/variety_of_indicators__5.png)
![Sviluppare un Expert Advisor per il trading da zero (Parte 9): Un salto concettuale (II)](https://c.mql5.com/2/46/development__1.png)
![Scienza dei dati e apprendimento automatico (Parte 03): Regressioni a matrice](https://c.mql5.com/2/48/matrix_regression__1.png)
![MQL5 - Linguaggio delle strategie di trading integrato nel client terminal MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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