English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
preview
Desenvolvendo um EA de negociação do zero (Parte 09): Um salto conceitual (II)

Desenvolvendo um EA de negociação do zero (Parte 09): Um salto conceitual (II)

MetaTrader 5Indicadores | 5 abril 2022, 08:43
1 467 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior criamos o sistema base para utilizar templates dentro de uma janela flutuante. Apesar de ter sido feito uma grande quantidade de mudanças no código, não finalizei ele totalmente ali para não complicar a explicação, já que usar templates em janelas flutuantes é algo bem simples, mas usar objetos em uma janela flutuante, é uma outra história. Então preparem-se pois aqui a coisa vai ser bem envolvente.

A maior complicação encontrada, de fato, em se usar objetos que usamos para fazer nossa interface do CHART TRADE em uma janela flutuante é o fato de que o MetaTrader 5 não foi de fato desenvolvido para este proposito. Muitos argumentariam que poderíamos usar a biblioteca padrão e criar assim a nossa janela CHART TRADE, mas eu gosto de complicar e quero permitir que qualquer um possa criar a sua própria interface da mesma forma como foi mostrado a alguns artigos atras, onde mostrei como fazer isto. O fato é que lá a coisa foi simples, mas aqui teremos que entender as limitações do MetaTrader 5 para assim poder contornar estas limitações.


Planejamento

Vamos começar no básico, o código abaixo ira ter um comportamento esperado e pode ser visto na imagem a seguir:

#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;
}
//+------------------------------------------------------------------+


Isto não parece nada demais, já que o efeito foi exatamente como esperado, mas o MQL5 nos permite ir um pouco além disto, mas existem complicações quando se tenta fazer algo além do que o sistema foi inicialmente projetado para fazer. Então se modificarmos o código acima para algo mostrado abaixo, as coisas começam a ficar interessantes.

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;
}

As linhas em destaque foram adicionadas no código original, e quando executamos isto em um gráfico, teremos o seguinte resultado:


Mas ai você pensa, o que aconteceu ?!?! O que foi feito foi colocarmos um gráfico dentro de um outro gráfico, poderíamos colocar qualquer objeto, o MQL5 nos permite fazer isto, mas existe uma vantagem e uma desvantagem ao se fazer isto, vamos então adicionar mais uma mudança no código para verificar a vantagem em se fazer isto.

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;
}

E adicionando as linhas em destaque conseguimos obter o seguinte resultado:


Ou seja qualquer coisa colocada dentro de um objeto irá se manter dentro do objeto, isto é primordial para podermos usar janelas flutuantes, pois facilita muito a parte da lógica de controle, mas nem tudo são flores, o MetaTrader 5 não foi projetado para isto então temos um problema quando um objeto esta dentro de outro, e o principal é que não podemos enviar eventos para os objetos internos, para entender vamos adicionar mais algumas modificações no código. Agora o código final pode ser visto abaixo:

#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);
}
//+------------------------------------------------------------------+

E o resultado quando executamos ele na plataforma é visto abaixo:


Notem que mesmo clicando no objeto interno ainda teremos como resultado um clique no objeto externo, e é justamente isto que dificulta as coisas, mas um programador sempre tende a se tornar um especialista em contornar os problemas, resolvendo eles de forma a ter o resultado desejado, e é com este conhecimento visto até aqui é que iremos montar o nosso sistema de forma a criar um CHART TRADE em uma janela flutuante de forma que ele seja funcional e tenha uma aparência pessoal para cada um.

Existe uma ultima fase neste processo de planejamento, e apesar de não parecer fazer sentido para computadores modernos, devemos sim pensar nela, trata-se da otimização do tempo de processamento. A questão aqui é mais algo relacionado com numero de operações a serem efetuadas pelo processador do que necessariamente o tempo que ele irá gastar para processar tais informações. O sistema de janela flutuante que estou propondo contém 4 objetos que tem que ser movidos, independente do que você faça, e qualquer informação colocada na janela gráfica estará sujeita as modificações da própria janela. A questão é que um CHART TRADE mínimo irá aumentar a quantidade de objetos, e mesmo que isto não tenha custos computacionais relevantes, torna o código pouco agradável, dando a impressão que está mal otimizado. Então poderíamos simplesmente adicionar um sistema de controle e isto resolveria o problema, mas eu tenho uma proposta mais elegante, apesar de parecer mais trabalhosa, na verdade ela reduz o numero de objetos que precisamos manter e manipular.


Implantação

A primeira coisa que faremos é subdividir em mais etapas o criação da janela flutuante, isto para poder reutilizar ainda mais o código, ajustando apenas pequenas coisas quando necessário. então criamos 2 novas rotinas na classe objeto C_ChartFloating, elas podem ser vistas abaixo:

//+------------------------------------------------------------------+
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++;
}
//+------------------------------------------------------------------+

Com isto o novo código para adicionar a janela flutuante ficará conforme mostrado a seguir:

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;
}

Isto não afeta em nada o sistema já montado, mas nos permite fazer um melhor uso do mesmo, reparem nas linhas destacadas, agora vamos criar a rotina para usar nossa IDE, o inicio da rotina pode ser visto abaixo:

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;
}

Observem as linhas destacadas são as mesmas linhas encontradas no código anterior, ou seja estamos reutilizando, e precisaremos apenas ajustar as coisas que são diferentes. Agora podemos dizer ao nosso sistema que temos meios de controlar a IDE, então fazemos isto modificando a classe objeto C_TemplateChart, o fragmento abaixo mostra exatamente o que irá ser mudado na função, de forma que poderemos focar a partir deste momento em implementar a janela flutuante da IDE, já que todo o apoio necessário já estará funcionando corretamente.

void AddTemplate(void)
{

// .... Código da função ....

        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);
                }
        }
}

Vejam como o código vai  se ajustando de forma a tornar a coisa o mais maleável quanto for possível, isto evita que o sistema se torne um frankenstein conforme vamos modificando ele, devemos sempre ter isto em mente, evitar reescrever códigos, ou melhor dizendo, ficar re-testando diversas vezes a mesma coisa, procure sempre fazer com que o teste apenas uma única vez, feito isto, use e explore o máximo que for possível, antes de ter que fazer um novo teste, assim o seu sistema irá crescer com boas premissas e o código irá se manter sustentável e expansível ao longo do tempo.

Bom, se o sistema for posto para rodar já irá apresentar algo no gráfico, mas queremos que seja apresentado pelo menos a interface que desenhamos, então precisamos fazer uma modificação extra e o código ficará conformo pode ser visto logo abaixo:

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;
}

e o resultado quando executamos ele é visto na figura abaixo:


Tudo seria muito bom e bonito se houvesse a possibilidade de acessar os objetos que estão no template que carregamos na linha que esta destacada no código, mas não temos está possibilidade, mas aqui é que mora o grande detalhe, ao invés de criar todos os objetos como mostrei anteriormente como fazer, nos criamos apenas os objetos que devem ser manipulados ?!?! Isto nos pouparia muito tempo de processamento quando tivermos que mover a janela, mas ainda assim teremos um outro problemas, mas vamos resolver primeiro o problema de manipulação, permitindo assim que o sistema possa se tornar funcional. Bem, esta parte de certa forma já esta pronta, precisamos apenas ajeitar um pouco as coisas para que tudo funcione.

Vamos começar fazendo uma mudança na sequencia de herança entre as classes, o fato de não termos herança multipla nos força a fazer isto então a nova estrutura ficará assim:


mas não devem se preocupar com esta mudança, o fato de mudar a sequencia de herança, não mudará em quase nada o código, mas permitirá que o mesmo fique quase pronto. Os pontos que de fato sofreram mudanças podem ser visto abaixo, com as modificações sendo mostradas em destaque.

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);

//... Restante da rotina ....

}

Você deve estar pensando: Mas o que este maluco esta fazendo ?!?! O que estou fazendo é algo simples, lembre-se se não estivermos usando uma janela flutuante, o sistema já é capaz de manipular os eventos executados na nossa IDE, mas por conta da janela flutuante teremos que remontar a coisa. Isto é fato, mas também é fato que não precisamos fazer isto do zero, vamos modificar o código já existente de forma a adicionar a IDE no local correto, mas não com todos os elementos, iremos adicionar apenas os objetos que recebem eventos. E estas modificações nos permite fazer exatamente isto, saber se temos que criar todos ou apenas parte dos elementos.

Se você executar estas modificações já terá no gráfico as informações da IDE, só que os objetos da IDE estarão fora do lugar fazendo uma verdadeira confusão, já que os objetos não estão vinculados a janela flutuante, precisamos agora corrigir isto.

A primeira coisa a se fazer é conseguir os pontos onde a janela flutuante esta no gráfico, isto não é a tarefa mais trivial, pois bastará ter os pontos do objeto que representa nossa janela, mas já que estamos nos mantendo dentro das premissas de uma programação orientada a objetos teremos que fazer umas mudanças, mas a principal consequência é a criação de um código que pode ser visto abaixo

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

// ... Código da classe ...

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

// ... Código da função ...

                                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;
                                }
                        }
//+------------------------------------------------------------------+

// ... Código da classe ...

//+------------------------------------------------------------------+
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;
                        }
//+------------------------------------------------------------------+

//... Restante do código da classe 
}

Já que a solução é focada em apenas uma única janela que será o nosso CHART TRADER podemos fazer assim, não faz sentido complicar além deste ponto. Agora poderemos posicionar pelo menos inicialmente os objetos na janela de forma adequada. Isto é conseguido pela seguinte rotina.

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);
};

A rotina acima irá apenas posicionar inicialmente os objetos de forma adequada e o resultado pode ser visto abaixo:


Só que além de estarem corretamente posicionados inicialmente, os objetos já respondem aos eventos, mas ainda existem falhas no sistema, que precisam ser corrigidas. A primeira que vamos corrigir é visto abaixo:


Veja que a janela é minimizada, mas os objetos estão permanecendo na tela, isto se deve ao fato de os objetos não estarem de fato dentro da área da janela, eu já expliquei por que isto não poder ser feito. Mas temos que corrigir isto antes de continuar. Isto é fácil de ser feito, bastará modificar um pouco o código conforme mostrado abaixo:

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);
};

E o resultado é este:

Bem agora vamos resolver o problema da movimentação, este é igualmente simples de ser resolvido e o código que faz isto é visto no fragmento abaixo:

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;

//... Restante da rotina ...

O resultado final é este:

 


Conclusão

Notem como programação é algo lindo, começamos com um problema, e sem grandes mudanças no código, resolvermos um por um os problemas e no final temos o código funcionando e a criando o mínimo possível de código.


Observação importante: Caso você você venha a ver informações estranhas no fundo do Chart Trade, por conta da atualização 3228 que faz objetos com cor em clrNONE não mais transparentes, utilize o arquivo anexo do IDE que corrige o problema.

Arquivos anexados |
EA_1.09.zip (3617.03 KB)
IDE.tpl (10.13 KB)
Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados Desenvolvendo um EA de negociação do zero (Parte 10): Acessando indicadores personalizados
Como acessar Indicadores personalizados diretamente no EA ? Um EA de negociação, só será realmente bem explorado se for possível você usar indicadores personalizados nele, caso contrário ele será apenas um conjunto de códigos e instruções.
Desenvolvendo um EA de negociação do zero (Parte 08): Um salto conceitual (I) Desenvolvendo um EA de negociação do zero (Parte 08): Um salto conceitual (I)
Como implementar de forma o mais simples possível uma nova funcionalidade ? Aqui iremos dar um passo para trás para logo em seguida dar dois para frente.
Desenvolvendo um EA de negociação do zero (Parte 11): Sistema CROSS ORDER Desenvolvendo um EA de negociação do zero (Parte 11): Sistema CROSS ORDER
Criando um sistema cross order. Existem uma classe de ativos que dificulta muito a vida dos operadores, estes são os ativos de contrato futuro, e por que eles dificultam a vida do operador ?
Matemática na negociação: indices de Sharpe e de Sortino Matemática na negociação: indices de Sharpe e de Sortino
A rentabilidade é a medida mais óbvia que investidores e operadores novatos utilizam para analisar o desempenho da negociação. Já os traders profissionais empregam ferramentas mais robustas para analisar estratégias, entre elas os índices de Sharpe e de Sortino.