Sviluppare un Expert Advisor per il trading da zero (Parte 9): Un salto concettuale (II)

Daniel Jose | 18 ottobre, 2022

Introduzione

Nella parte precedente abbiamo creato un sistema di base che consente di utilizzare i modelli all'interno di una finestra mobile. Anche se abbiamo apportato molte modifiche, il codice non è ancora finito. Questo è stato fatto intenzionalmente per mantenere la spiegazione semplice, perché mentre l'uso dei modelli nelle finestre mobili è piuttosto semplice, l'uso degli oggetti è qualcosa di molto più complicato. Quindi, preparatevi ad un lavoro completamente nuovo.

In effetti, la difficoltà maggiore legata all'uso degli oggetti che utilizziamo per creare l'interfaccia CHART TRADE nella finestra mobile è che MetaTrader 5 non è stato concepito per questo scopo. Alcuni lettori potrebbero dire che si potrebbe usare la libreria standard per creare la finestra CHART TRADE. Ma a me piace complicare le cose e voglio permettere a tutti di creare la propria interfaccia nello stesso modo in cui abbiamo discusso qualche articolo fa. Tuttavia, in quell'articolo tutto era semplice. Ora dobbiamo capire le restrizioni di MetaTrader 5 per poterle aggirare.


Pianificazione

Cominciamo con le basi. Il codice seguente si comporterà come ci aspettiamo:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID();
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand();
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+


Non c'è nulla di complicato in questo caso, poiché l'effetto è stato esattamente quello previsto. Ma MQL5 consente di andare un po' più in là, anche se possono sorgere delle difficoltà quando si cerca di fare qualcosa che vada oltre ciò per cui il sistema è stato originariamente progettato. Quindi, se cambiamo il codice di cui sopra in qualcosa di simile a quello che segue, le cose si fanno interessanti.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

Abbiamo aggiunto al codice le linee evidenziate. Se lo si esegue su un grafico, il risultato sarà come questo:


Che cosa è successo? Abbiamo posizionato un grafico su un grafico. Potremmo inserire qualsiasi oggetto qui, dato che MQL5 lo consente, ma questo ha sia vantaggi che svantaggi. Date un'occhiata alla prossima modifica del codice per capire il vantaggio di questo passaggio.

int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}

L'aggiunta delle linee evidenziate ha generato il seguente risultato:


Ciò significa che tutto ciò che è all'interno dell'oggetto rimarrà all'interno dell'oggetto. Questo è richiesto quando si utilizzano le finestre mobili, in quanto semplifica notevolmente la logica di controllo. Ma non tutto è perfetto: MetaTrader 5 non è stata originariamente progettata per questo. Pertanto, sorge un problema quando un oggetto si trova all'interno di un altro: non è possibile inviare eventi agli oggetti interni. Per capire questo, apportiamo qualche altra modifica al codice. Il codice finale sarà il seguente:

#property copyright "Daniel Jose"
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        long id = ChartID(), handle;
        string sz0 = (string)ObjectsTotal(id, -1, -1) + (string)MathRand(), sz1 = (string)MathRand();
        
        ObjectCreate(id, sz0, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(id, sz0, OBJPROP_XDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_YDISTANCE, 10);
        ObjectSetInteger(id, sz0, OBJPROP_XSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_YSIZE, 300);
        ObjectSetInteger(id, sz0, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(id, sz0, OBJPROP_SELECTED, true);
        
        handle = ObjectGetInteger(id, sz0, OBJPROP_CHART_ID);
        ObjectCreate(handle, sz1, OBJ_CHART, 0, 0, 0);
        ObjectSetInteger(handle, sz1, OBJPROP_XDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_YDISTANCE, 50);
        ObjectSetInteger(handle, sz1, OBJPROP_XSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_YSIZE, 300);
        ObjectSetInteger(handle, sz1, OBJPROP_PRICE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_DATE_SCALE, false);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTABLE, true);
        ObjectSetInteger(handle, sz1, OBJPROP_SELECTED, true);
        ChartRedraw(handle);    
  
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        if (id == CHARTEVENT_OBJECT_CLICK) Print(sparam);
}
//+------------------------------------------------------------------+

Ecco il risultato dell'esecuzione del codice nella piattaforma:


Notare che quando clicchiamo sull'oggetto interno, in realtà si fa clic sull'oggetto esterno e qui le cose si complicano. Ma il programmatore si sforza sempre di diventare un esperto nella risoluzione dei problemi: bisogna risolvere i problemi per ottenere il risultato desiderato. Utilizzando queste conoscenze, costruiremo il sistema in modo da creare CHART TRADE in una finestra mobile e garantire che sia funzionale e abbia un aspetto individuale.

C'è un'ultima fase in questo processo di pianificazione. Sebbene questa parte non sia così fine per i computer moderni, deve comunque essere presa in considerazione: l'ottimizzazione del tempo di elaborazione. Il problema è legato al numero di operazioni che il processore deve eseguire, piuttosto che al tempo necessario per elaborare le informazioni. Il sistema di finestre mobili proposto contiene quattro oggetti che dovrebbero essere in grado di muoversi reagendo alle azioni dell'utente. Di conseguenza, qualsiasi informazione inserita nella finestra di visualizzazione sarà soggetta alle modifiche della finestra stessa. Se non altro CHART TRADE aumenterà il numero di oggetti. Anche se non c'è un costo computazionale corrispondente, il codice diventa sgradevole e sembra essere poco ottimizzato. Basterebbe aggiungere un sistema di controllo per risolvere il problema. Ma c'è un suggerimento più elegante. Anche se sembra più dispendioso in termini di tempo e fatica, in realtà riduce il numero di oggetti che devono essere mantenuti e manipolati.


Implementazione

Per prima cosa, divideremo la creazione della finestra mobile in più fasi per favorire il riutilizzo del codice. Quindi, creeremo due nuove funzioni nella classe oggetto C_ChartFloating:

//+------------------------------------------------------------------+
bool StageLocal01(string sz0, 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;
        CreateBarTitle();
        CreateCaption(sz0);
        CreateBtnMaxMin();
        CreateRegion(TimeFrame, Scale);
	m_Win[m_MaxCounter].handle = ObjectGetInteger(Terminal.Get_ID(), m_Win[m_MaxCounter].szRegionChart, OBJPROP_CHART_ID);
                                
        return true;
}
//+------------------------------------------------------------------+
void StageLocal02(int x, int y, int w, int h)
{
        y = (y < 0 ? m_MaxCounter * def_SizeBarCaption : y);                            
        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++;
}
//+------------------------------------------------------------------+

Il nuovo codice che aggiunge una finestra mobile sarà il seguente:

bool AddIndicator(string sz0, int x = 0, int y = -1, int w = 300, int h = 200, ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT, int Scale = -1)
{
	if (!StageLocal01(sz0, TimeFrame, Scale)) return false;
        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);
        StageLocal02(x, y, w, h);

        return true;
}

Questo non influisce sul sistema già creato, ma ne consente un migliore utilizzo. Fate attenzione alle linee evidenziate: ora creeremo una funzione per utilizzare il nostro IDE. L'inizio è mostrato di seguito:

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        StageLocal02(x, y, w, h);
        return true;
}

Presta attenzione al fatto che le linee evidenziate sono le stesse utilizzate nel codice precedente. In altre parole, stiamo riutilizzando il codice e solo gli elementi cha abbiamo bisogno di modificare sono differenti. Ora possiamo informare il nostro sistema che abbiamo gli strumenti per gestire l'IDE. Per fare ciò, si modifica la classe oggetto C_TemplateChart. Il codice sottostante mostra esattamente ciò che verrà modificato nella funzione, quindi d'ora in poi possiamo concentrarci sull'implementazione di una finestra mobile con l'IDE, poiché tutto il supporto necessario funzionerà già correttamente.

void AddTemplate(void)
{

// .... Function code....

        if (h == 0)
        {
                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)
        {
                if ((h > 0) && (w > 0)) Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h); else
                {
                        C_Chart_IDE::Create(GetIdSubWinEA());
                        m_Info[m_Counter - 1].szVLine = "";
                }
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); 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);
                }
        }
}

Vediamo come il codice è configurato per essere il più flessibile possibile. In questo modo si evita che il sistema si trasformi in Frankenstein. Quando modifichiamo il codice, teniamolo sempre a mente: non è necessario riscrivere il codice da zero e verificare più volte la stessa cosa. Cercate sempre di controllare le cose una sola volta. Dopodichè puoi utilizzare ed esplorare le cose il più possibile prima di dover fare nuovi test. In questo modo, il sistema crescerà con buone premesse, mentre il codice rimarrà sostenibile ed espandibile nel tempo.

Se esegui il sistema ora, mostrerà qualcosa sul grafico. Ma abbiamo bisogno che mostri almeno l'interfaccia che abbiamo scritto in precedenza. Pertanto, è necessario apportare ulteriori modifiche al codice. Ora abbiamo il seguente codice:

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}

Ecco il risultato dell'avvio dell'applicazione:


Sarebbe molto bello se fosse possibile accedere agli oggetti che si trovano nel modello (il modello viene caricato nella linea evidenziata nel codice qui sopra). Tuttavia, non è possibile. Ed ecco il dettaglio importante: invece di creare oggetti nel modo considerato in precedenza, creiamo solo gli oggetti che devono essere manipolati! In questo modo si risparmierà molto tempo di elaborazione quando dovremo muovere la finestra. Abbiamo ancora un altro problema, ma prima risolviamo il problema della gestione e rendiamo il sistema funzionale. In realtà, questa parte è già pronta, necessita solo di qualche aggiustamento per far funzionare le cose.

Cominciamo a modificare la sequenza di ereditarietà tra le classi. Dobbiamo farlo perché non abbiamo l'ereditarietà multipla, quindi la nuova struttura sarà così:


Ma questo cambiamento non deve preoccupare: una modifica nella sequenza di ereditarietà non cambierà affatto il codice, ma gli permetterà di essere quasi pronto. Le parti modificate sono evidenziate nel codice sottostante.

bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
{
	if ((w <= 0) || (h <= 0)) return false;
        if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
        ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
        StageLocal02(x, y, w, h);
        return true;
}
void AddTemplate(void)
{
// ..... Código ....
        if (h == 0)
        {
                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(Add_RAD_IDE(m_Params.Param[TEMPLATE], 0, -1, w, h));
                m_Info[m_Counter - 1].szVLine = "";
        }else
        {
                if ((w > 0) && (h > 0)) AddIndicator(m_Params.Param[TEMPLATE], 0, -1, w, h, timeframe, i); 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);
                }
        }
}
bool Create(bool bFloat)
{
        m_CountObject = 0;
        if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false;
        FileReadInteger(m_fp, SHORT_VALUE);
                        
        for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = "";
	m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA());
        m_szLine = "";
        while (m_szLine != "</chart>")
        {
                if (!FileReadLine()) return false;
                if (m_szLine == "<object>")
                {
                        if (!FileReadLine()) return false;
                        if (m_szLine == "type")
                        {
                                if (m_szValue == "102") if (!LoopCreating(OBJ_LABEL)) return false;
                                if (m_szValue == "103") if (!LoopCreating(OBJ_BUTTON)) return false;
                                if (m_szValue == "106") if (!LoopCreating(OBJ_BITMAP_LABEL)) return false;
                                if (m_szValue == "107") if (!LoopCreating(OBJ_EDIT)) return false;
                                if (m_szValue == "110") if (!LoopCreating(OBJ_RECTANGLE_LABEL)) return false;
                        }
                }
        }
        FileClose(m_fp);
        DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, 0, szMsgIDE[eLABEL_SYMBOL]);
        return true;
}

bool LoopCreating(ENUM_OBJECT type)
{
#define macro_SetInteger(A, B) ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
#define macro_SetString(A, B) ObjectSetString(Terminal.Get_ID(), m_ArrObject[c0].szName, A, B)
        int c0;
        bool b0;
        string sz0 = m_szValue;
        while (m_szLine != "</object>") if (!FileReadLine()) return false; else
        {
                if (m_szLine == "name")
                {
                        b0 = false;
                        StringToUpper(m_szValue);
                        for(c0 = eRESULT; (c0 <= eEDIT_STOP) && (!(b0 = (m_szValue == szMsgIDE[c0]))); c0++);
                        if (!b0 && m_IsFloating) return true; else c0 = (b0 ? c0 : m_CountObject);
                        m_ArrObject[c0].szName = StringFormat("%s%04s>%s", def_HeaderMSG, sz0, m_szValue);

//... The rest of the function...

}

Questo può sembrare strano. Ma non c'è niente di complicato! Ricordate che quando non utilizziamo una finestra mobile, il sistema è già in grado di gestire gli eventi in esecuzione nel nostro IDE. Ma a causa di quella finestra mobile, dobbiamo ricostruire tutto. Tuttavia, non dobbiamo farlo da zero: modificheremo il codice esistente per aggiungere l'IDE nel posto giusto. È sufficiente aggiungere gli oggetti che ricevono gli eventi. Queste modifiche ci permettono di sapere se è necessario creare tutti gli elementi o solo alcuni di essi.

Dopo queste modifiche, abbiamo le informazioni IDE sul grafico. Ma gli oggetti IDE creeranno un vero e proprio pasticcio, poiché gli oggetti non sono associati alla finestra mobile. E’ necessaria una correzzione.

Per prima cosa, è necessario ottenere i punti in cui si trova la finestra mobile sul grafico. È sufficiente conoscere i punti dell'oggetto che rappresenta la finestra. Ma poiché ci atteniamo alla programmazione orientata agli oggetti, dovremo apportare alcune modifiche, anche se il risultato principale è il codice seguente.

//+------------------------------------------------------------------+
struct IDE_Struct
{
        int     X,
                Y,
                Index;
        bool    IsMaximized;
};
//+------------------------------------------------------------------+
class C_ChartFloating
{

// ... Class code ...

//+------------------------------------------------------------------+
                void SetPosition(const int X, const int Y, const int c0)
                        {

// ... Function code ...

                                if (c0 == m_IDEStruct.Index)
                                {
                                        m_IDEStruct.X = m_Win[c0].PosX + 3;
                                        m_IDEStruct.Y = m_Win[c0].PosY + def_SizeBarCaption + 3;
                                        m_IDEStruct.IsMaximized = m_Win[c0].IsMaximized;
                                }
                        }
//+------------------------------------------------------------------+

// ... Class code ...

//+------------------------------------------------------------------+
inline IDE_Struct GetIDE_Struct(void) const { return m_IDEStruct; }
//+------------------------------------------------------------------+
                bool Add_RAD_IDE(string sz0, int x, int y, int w, int h)
                        {
                                if ((w <= 0) || (h <= 0)) return false;
                                if (!StageLocal01(sz0, PERIOD_CURRENT, -1)) return false;
                                ChartApplyTemplate(m_Win[m_MaxCounter].handle, "\\Files\\Chart Trade\\IDE.tpl");
                                m_IDEStruct.Index = m_MaxCounter;
                                StageLocal02(x, y, w, h);
                                return true;
                        }
//+------------------------------------------------------------------+

//... The rest of the class code 
}

Siccome la soluzione si concentra su una sola finestra, la nostra CHART TRADER, non ha senso complicare le cose dopo questo punto. Ora possiamo posizionare correttamente gli oggetti nella finestra, almeno inizialmente. Questo viene fatto nella funzione seguente.

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX);
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY);
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

La funzione precedente fornisce il corretto posizionamento iniziale degli oggetti e il risultato è visibile qui sotto:


Oltre al corretto posizionamento iniziale, gli oggetti reagiscono già agli eventi. Tuttavia, il sistema presenta ancora dei bug che devono essere corretti. Ecco la prima cosa che cambieremo:


Come si può vedere, la finestra è stata ridotta a icona, ma gli oggetti rimangono sullo schermo. Questo perché gli oggetti non si trovano all'interno dell'area della finestra (ho spiegato prima il perché). Questo deve essere risolto prima di andare avanti. È possibile farlo con una semplice modifica del codice illustrato di seguito:

void Resize(int x)
{
        for (int c0 = 0; c0 < m_CountObject; c0++) if (m_IsFloating)
        {
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, GetIDE_Struct().X + m_ArrObject[c0].iPosX + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetWidth()));
                ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_YDISTANCE, GetIDE_Struct().Y + m_ArrObject[c0].iPosY + (GetIDE_Struct().IsMaximized ? 0 : Terminal.GetHeight()));
        }else   ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[c0].szName, OBJPROP_XDISTANCE, x + m_ArrObject[c0].iPosX);
};

Il risultato è il seguente:

Ora correggiamo il problema con il movimento. Può essere risolto utilizzando il codice qui sotto

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        static double AccumulatedRoof = 0.0;
        bool            b0;
        double  d0;
        static int px = -1, py = -1;
                                
        C_ChartFloating::DispatchMessage(id, lparam, dparam, sparam);
        if (m_CountObject < eEDIT_STOP) return;
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        if ((GetIDE_Struct().X != px) || (GetIDE_Struct().Y != py))
                        {
                                px = GetIDE_Struct().X;
                                py = GetIDE_Struct().Y;
                                Resize(-1);
                        }
                        break;

//... The rest of the function ...

Il risultato finale è il seguente:

 


Conclusioni

Vedete com'è bella la programmazione: iniziamo con un problema e poi, senza grandi modifiche al codice, risolviamo i problemi uno per uno e alla fine abbiamo un codice funzionante con la minor quantità di codice possibile.


Importante! Se si vedono informazioni strane sullo sfondo di Chart Trade, ciò è dovuto all'aggiornamento 3228, che rende opachi gli oggetti con colore clrNONE. Utilizzare il file IDE allegato per risolvere il problema.