Indicador de posições históricas no gráfico em forma de diagrama de lucro/prejuízo
Conteúdo
- Introdução
- Classe de negociações
- Classe de posições
- Classe da lista de posições históricas
- Indicador de diagrama de lucro das posições
- Considerações finais
Introdução
Posição... A posição é o resultado de uma ordem de negociação executada no servidor: Order --> Deal --> Position.
Sempre podemos obter a lista de posições abertas no nosso programa usando a função PositionSelect() para contas de netting, especificando o nome do símbolo da posição aberta.
if(PositionSelect(symbol_name)) { /* Work with selected position data PositionGetDouble(); PositionGetInteger(); PositionGetString(); */ }
Para contas com posições independentes (hedge), primeiro obtemos o número de posições usando PositionsTotal(), depois, num laço, pegamos o ticket da posição pelo índice na lista de posições abertas usando PositionGetTicket(), e então selecionamos a posição pelo ticket usando PositionSelectByTicket().
int total=PositionsTotal(); for(int i=total-1;i>=0;i--) { ulong ticket=PositionGetTicket(i); if(ticket==0 || !PositionSelectByTicket(ticket)) continue; /* Work with selected position data PositionGetDouble(); PositionGetInteger(); PositionGetString(); */ }
Isso é simples e direto. Mas, quando queremos informações sobre uma posição já fechada, não há funções específicas para isso...
Precisamos lembrar que cada posição tem um identificador único. Esse identificador está nas negociações que abriram, modificaram ou fecharam a posição. Podemos pegar a lista de negociações e, a partir dela, usar HistorySelect(), HistoryDealsTotal() e HistoryDealGetTicket() (HistoryDealSelect()) para encontrar o identificador da posição.
Em geral, o identificador da posição na qual a transação estava envolvida pode ser obtido da seguinte forma:
if(HistorySelect(0,TimeCurrent())) { int total=HistoryDealsTotal(); for(int i=0;i<total;i++) { ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; long pos_id=HistoryDealGetInteger(ticket,DEAL_POSITION_ID); // ID of a position a deal participated in /* Work with selected deal data HistoryDealGetDouble(); HistoryDealGetInteger(); HistoryDealGetString(); */ } }
Assim, sabemos a qual posição cada negociação pertence. Por exemplo, podemos pegar o identificador da posição e, pelas negociações com esse identificador, encontrar a negociação de abertura e fechamento. Com as propriedades dessas negociações, podemos obter o tempo, o lote e outras informações necessárias sobre a posição histórica.
Para encontrar posições fechadas, obtemos a lista de negociações históricas. Num laço, pegamos cada negociação e verificamos o identificador da posição. Se essa posição ainda não estiver na lista, criamos um objeto de posição; se já estiver, usamos a posição existente com esse identificador. A negociação atual (se ainda não estiver na lista) é adicionada à lista de negociações da posição encontrada. Percorrendo toda a lista de negociações históricas, encontraremos todas as posições das que participaram negociações, criando uma lista de todas as posições históricas e, em cada posição, adicionaremos todas as negociações que participaram dela.
Precisaremos de três classes:
- Classe de negociação: Contém as propriedades da negociação necessárias para identificar a posição e suas propriedades.
- Classe de posição: Contém a lista de negociações e as propriedades associadas às posições.
- Classe da lista de posições históricas: Lista de posições históricas encontradas, permitindo selecionar uma posição pelas propriedades especificadas.
Essas três classes nos ajudarão a encontrar todas as negociações históricas, salvá-las em uma lista e, depois, usar esses dados no indicador para desenhar o diagrama de lucro/prejuízo das posições por símbolo selecionado na conta.
Na pasta Indicators, criaremos um novo arquivo chamado PositionInfoIndicator. Especificaremos a renderização do indicador com um buffer de estilo Filling em uma janela separada do gráfico, com cores de preenchimento Green e Red.
Será criado um template de indicador com o seguinte cabeçalho:
//+------------------------------------------------------------------+ //| PositionInfoIndicator.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 1 //--- plot Fill #property indicator_label1 "Profit;ZeroLine" #property indicator_type1 DRAW_FILLING #property indicator_color1 clrGreen,clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1
Abaixo do código, escreveremos as classes criadas.
Para criar listas de negociações e posições, usaremos uma classe de array dinâmico de ponteiros para instâncias da classe CObject e seus derivados da Biblioteca Padrão.
Incluiremos o arquivo dessa classe no arquivo criado:
//+------------------------------------------------------------------+ //| PositionInfoIndicator.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 1 //--- plot Fill #property indicator_label1 "Profit;ZeroLine" #property indicator_type1 DRAW_FILLING #property indicator_color1 clrGreen,clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- includes #include <Arrays\ArrayObj.mqh>
A classe contém o método Search() para buscar elementos iguais ao padrão em um array ordenado:
//+------------------------------------------------------------------+ //| Search of position of element in a sorted array | //+------------------------------------------------------------------+ int CArrayObj::Search(const CObject *element) const { int pos; //--- check if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) return(-1); //--- search pos=QuickSearch(element); if(m_data[pos].Compare(element,m_sort_mode)==0) return(pos); //--- not found return(-1); }
O array deve ser ordenado por alguma propriedade dos objetos cujos ponteiros estão contidos nele. O modo de ordenação é definido passando um valor inteiro para a variável m_sort_mode . Por padrão, usa-se o valor 0. O valor -1 significa que o array não está ordenado. Para ordenar por diferentes propriedades, é necessário definir vários modos de ordenação configurando valores de zero em diante na variável m_sort_mode. Para isso, é conveniente usar enumerações que definem diferentes modos de ordenação de listas. Esses modos são usados no método virtual de comparação de dois objetos Compare(). Ele é definido no arquivo da classe CObject e retorna o valor 0, que indica a identidade dos objetos comparados:
//--- method of comparing the objects virtual int Compare(const CObject *node,const int mode=0) const { return(0); }
Este método deve ser sobrescrito em suas próprias classes, onde se pode gerar a comparação de dois objetos do mesmo tipo por diferentes propriedades (com m_sort_mode igual a 0, os objetos são comparados por uma propriedade, com m_sort_mode igual a 1 — por outra propriedade, e assim por diante).
Cada objeto da classe criada hoje terá seu próprio conjunto de propriedades. Essas propriedades serão usadas para ordenar as listas na busca de igualdade de objetos. Por isso, precisamos primeiro criar enumerações das propriedades desses objetos:
//+------------------------------------------------------------------+ //| PositionInfoIndicator.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 1 //--- plot Fill #property indicator_label1 "Profit;ZeroLine" #property indicator_type1 DRAW_FILLING #property indicator_color1 clrGreen,clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- includes #include <Arrays\ArrayObj.mqh> //--- enums //--- Deal object sorting modes enum ENUM_DEAL_SORT_MODE { DEAL_SORT_MODE_TIME_MSC, // Deal execution time in milliseconds DEAL_SORT_MODE_TIME, // Deal execution time DEAL_SORT_MODE_TIKET, // Deal ticket DEAL_SORT_MODE_POS_ID, // Position ID DEAL_SORT_MODE_MAGIC, // Magic number for a deal DEAL_SORT_MODE_TYPE, // Deal type DEAL_SORT_MODE_ENTRY, // Deal entry - entry in, entry out, reverse DEAL_SORT_MODE_VOLUME, // Deal volume DEAL_SORT_MODE_PRICE, // Deal price DEAL_SORT_MODE_COMISSION, // Deal commission DEAL_SORT_MODE_SWAP, // Accumulated swap when closing DEAL_SORT_MODE_PROFIT, // Deal financial result DEAL_SORT_MODE_FEE, // Deal fee DEAL_SORT_MODE_SYMBOL, // Name of the symbol for which the deal is executed }; //--- Position object sorting modes enum ENUM_POS_SORT_MODE { POS_SORT_MODE_TIME_IN_MSC, // Open time in milliseconds POS_SORT_MODE_TIME_OUT_MSC,// Close time in milliseconds POS_SORT_MODE_TIME_IN, // Open time POS_SORT_MODE_TIME_OUT, // Close time POS_SORT_MODE_DEAL_IN, // Open deal ticket POS_SORT_MODE_DEAL_OUT, // Close deal ticket POS_SORT_MODE_ID, // Position ID POS_SORT_MODE_MAGIC, // Position magic POS_SORT_MODE_PRICE_IN, // Open price POS_SORT_MODE_PRICE_OUT, // Close price POS_SORT_MODE_VOLUME, // Position volume POS_SORT_MODE_SYMBOL, // Position symbol }; //--- classes
As propriedades correspondentes às constantes dessas duas enumerações estarão contidas nas classes de objetos de negociação e de posição. Essas propriedades serão usadas para ordenar as listas na busca de igualdade dos objetos.
Abaixo escreveremos os códigos das classes criadas.
Classe de negociações
Vamos ver a classe de negociação por completo://+------------------------------------------------------------------+ //| Deal class | //+------------------------------------------------------------------+ class CDeal : public CObject { private: long m_ticket; // Deal ticket long m_magic; // Magic number for a deal long m_position_id; // Position ID long m_time_msc; // Deal execution time in milliseconds datetime m_time; // Deal execution time ENUM_DEAL_TYPE m_type; // Deal type ENUM_DEAL_ENTRY m_entry; // Deal entry - entry in, entry out, reverse double m_volume; // Deal volume double m_price; // Deal price double m_comission; // Deal commission double m_swap; // Accumulated swap when closing double m_profit; // Deal financial result double m_fee; // Deal fee string m_symbol; // Name of the symbol for which the deal is executed //--- Return the deal direction description string EntryDescription(void) const { return(this.m_entry==DEAL_ENTRY_IN ? "Entry In" : this.m_entry==DEAL_ENTRY_OUT ? "Entry Out" : this.m_entry==DEAL_ENTRY_INOUT ? "Reverce" : "Close a position by an opposite one"); } //--- Return the deal type description string TypeDescription(void) const { switch(this.m_type) { case DEAL_TYPE_BUY : return "Buy"; case DEAL_TYPE_SELL : return "Sell"; case DEAL_TYPE_BALANCE : return "Balance"; case DEAL_TYPE_CREDIT : return "Credit"; case DEAL_TYPE_CHARGE : return "Additional charge"; case DEAL_TYPE_CORRECTION : return "Correction"; case DEAL_TYPE_BONUS : return "Bonus"; case DEAL_TYPE_COMMISSION : return "Additional commission"; case DEAL_TYPE_COMMISSION_DAILY : return "Daily commission"; case DEAL_TYPE_COMMISSION_MONTHLY : return "Monthly commission"; case DEAL_TYPE_COMMISSION_AGENT_DAILY : return "Daily agent commission"; case DEAL_TYPE_COMMISSION_AGENT_MONTHLY: return "Monthly agent commission"; case DEAL_TYPE_INTEREST : return "Interest rate"; case DEAL_TYPE_BUY_CANCELED : return "Canceled buy deal"; case DEAL_TYPE_SELL_CANCELED : return "Canceled sell deal"; case DEAL_DIVIDEND : return "Dividend operations"; case DEAL_DIVIDEND_FRANKED : return "Franked (non-taxable) dividend operations"; case DEAL_TAX : return "Tax charges"; default : return "Unknown: "+(string)this.m_type; } } //--- Return time with milliseconds string TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) { return ::TimeToString(time_msc/1000,flags)+"."+::IntegerToString(time_msc%1000,3,'0'); } public: //--- Methods for returning deal properties long Ticket(void) const { return this.m_ticket; } // Deal ticket long Magic(void) const { return this.m_magic; } // Magic number for a deal long PositionID(void) const { return this.m_position_id; } // Position ID long TimeMsc(void) const { return this.m_time_msc; } // Deal execution time in milliseconds datetime Time(void) const { return this.m_time; } // Deal execution time ENUM_DEAL_TYPE TypeDeal(void) const { return this.m_type; } // Deal type ENUM_DEAL_ENTRY Entry(void) const { return this.m_entry; } // Deal entry - entry in, entry out, reverse double Volume(void) const { return this.m_volume; } // Deal volume double Price(void) const { return this.m_price; } // Deal price double Comission(void) const { return this.m_comission; } // Deal commission double Swap(void) const { return this.m_swap; } // Accumulated swap when closing double Profit(void) const { return this.m_profit; } // Deal financial result double Fee(void) const { return this.m_fee; } // Deal fee string Symbol(void) const { return this.m_symbol; } // Name of the symbol, for which the deal is executed //--- Methods for setting deal properties void SetTicket(const long ticket) { this.m_ticket=ticket; } // Deal ticket void SetMagic(const long magic) { this.m_magic=magic; } // Magic number for a deal void SetPositionID(const long id) { this.m_position_id=id; } // Position ID void SetTimeMsc(const long time_msc) { this.m_time_msc=time_msc; } // Deal execution time in milliseconds void SetTime(const datetime time) { this.m_time=time; } // Deal execution time void SetType(const ENUM_DEAL_TYPE type) { this.m_type=type; } // Deal type void SetEntry(const ENUM_DEAL_ENTRY entry) { this.m_entry=entry; } // Deal entry - entry in, entry out, reverse void SetVolume(const double volume) { this.m_volume=volume; } // Deal volume void SetPrice(const double price) { this.m_price=price; } // Deal price void SetComission(const double comission) { this.m_comission=comission; } // Deal commission void SetSwap(const double swap) { this.m_swap=swap; } // Accumulated swap when closing void SetProfit(const double profit) { this.m_profit=profit; } // Deal financial result void SetFee(const double fee) { this.m_fee=fee; } // Deal fee void SetSymbol(const string symbol) { this.m_symbol=symbol; } // Name of the symbol, for which the deal is executed //--- Method for comparing two objects virtual int Compare(const CObject *node,const int mode=0) const { const CDeal *compared_obj=node; switch(mode) { case DEAL_SORT_MODE_TIME : return(this.Time()>compared_obj.Time() ? 1 : this.Time()<compared_obj.Time() ? -1 : 0); case DEAL_SORT_MODE_TIME_MSC : return(this.TimeMsc()>compared_obj.TimeMsc() ? 1 : this.TimeMsc()<compared_obj.TimeMsc() ? -1 : 0); case DEAL_SORT_MODE_TIKET : return(this.Ticket()>compared_obj.Ticket() ? 1 : this.Ticket()<compared_obj.Ticket() ? -1 : 0); case DEAL_SORT_MODE_MAGIC : return(this.Magic()>compared_obj.Magic() ? 1 : this.Magic()<compared_obj.Magic() ? -1 : 0); case DEAL_SORT_MODE_POS_ID : return(this.PositionID()>compared_obj.PositionID() ? 1 : this.PositionID()<compared_obj.PositionID() ? -1 : 0); case DEAL_SORT_MODE_TYPE : return(this.TypeDeal()>compared_obj.TypeDeal() ? 1 : this.TypeDeal()<compared_obj.TypeDeal() ? -1 : 0); case DEAL_SORT_MODE_ENTRY : return(this.Entry()>compared_obj.Entry() ? 1 : this.Entry()<compared_obj.Entry() ? -1 : 0); case DEAL_SORT_MODE_VOLUME : return(this.Volume()>compared_obj.Volume() ? 1 : this.Volume()<compared_obj.Volume() ? -1 : 0); case DEAL_SORT_MODE_PRICE : return(this.Price()>compared_obj.Price() ? 1 : this.Price()<compared_obj.Price() ? -1 : 0); case DEAL_SORT_MODE_COMISSION : return(this.Comission()>compared_obj.Comission() ? 1 : this.Comission()<compared_obj.Comission() ? -1 : 0); case DEAL_SORT_MODE_SWAP : return(this.Swap()>compared_obj.Swap() ? 1 : this.Swap()<compared_obj.Swap() ? -1 : 0); case DEAL_SORT_MODE_PROFIT : return(this.Profit()>compared_obj.Profit() ? 1 : this.Profit()<compared_obj.Profit() ? -1 : 0); case DEAL_SORT_MODE_FEE : return(this.Fee()>compared_obj.Fee() ? 1 : this.Fee()<compared_obj.Fee() ? -1 : 0); case DEAL_SORT_MODE_SYMBOL : return(this.Symbol()>compared_obj.Symbol() ? 1 : this.Symbol()<compared_obj.Symbol() ? -1 : 0); default : return(this.TimeMsc()>compared_obj.TimeMsc() ? 1 : this.TimeMsc()<compared_obj.TimeMsc() ? -1 : 0); } } //--- Print deal properties in the journal void Print(void) { ::PrintFormat(" Deal: %s type %s #%lld at %s",this.EntryDescription(),this.TypeDescription(),this.Ticket(),this.TimeMSCtoString(this.TimeMsc())); } //--- Constructor CDeal(const long deal_ticket) { this.m_ticket=deal_ticket; this.m_magic=::HistoryDealGetInteger(deal_ticket,DEAL_MAGIC); // Magic number for a deal this.m_position_id=::HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID); // Position ID this.m_time_msc=::HistoryDealGetInteger(deal_ticket,DEAL_TIME_MSC); // Deal execution time in milliseconds this.m_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME); // Deal execution time this.m_type=(ENUM_DEAL_TYPE)::HistoryDealGetInteger(deal_ticket,DEAL_TYPE); // Deal type this.m_entry=(ENUM_DEAL_ENTRY)::HistoryDealGetInteger(deal_ticket,DEAL_ENTRY); // Deal entry - entry in, entry out, reverse this.m_volume=::HistoryDealGetDouble(deal_ticket,DEAL_VOLUME); // Deal volume this.m_price=::HistoryDealGetDouble(deal_ticket,DEAL_PRICE); // Deal volume this.m_comission=::HistoryDealGetDouble(deal_ticket,DEAL_COMMISSION); // Deal commission this.m_swap=::HistoryDealGetDouble(deal_ticket,DEAL_SWAP); // Accumulated swap when closing this.m_profit=::HistoryDealGetDouble(deal_ticket,DEAL_PROFIT); // Deal financial result this.m_fee=::HistoryDealGetDouble(deal_ticket,DEAL_FEE); // Deal fee this.m_symbol=::HistoryDealGetString(deal_ticket,DEAL_SYMBOL); // Name of the symbol, for which the deal is executed } ~CDeal(void){} };
Na seção privada, são declaradas as variáveis-membro da classe para armazenar as propriedades da negociação. Na seção pública, são implementados métodos para definir e retornar as propriedades da negociação. O método virtual Compare() implementa a comparação por cada propriedade da negociação dependendo da propriedade passada ao método como modo de comparação. Se o valor da propriedade verificada do objeto atual for maior que o valor da mesma propriedade do objeto comparado, retorna 1; se menor, retorna -1; caso contrário, retorna 0, indicando igualdade de valores. No construtor da classe, assume-se que a negociação já foi selecionada e suas propriedades são registradas nas variáveis privadas correspondentes da classe. Isso é suficiente para criar um objeto de negociação que armazene todas as propriedades necessárias da negociação selecionada na lista de negociações históricas do terminal. Para cada negociação, será criado um objeto dessa classe; das propriedades do objeto será extraído o identificador da posição, e será criado um objeto da classe de posição histórica. Todas as negociações dessa posição serão adicionadas à lista de negociações da classe de posição. Assim, o objeto de posição histórica conterá a lista de todas as suas negociações. E, a partir delas, será possível extrair o histórico da vida dessa posição.
Vamos ver a classe de posição histórica.
Classe de posições
A classe de posição conterá a lista de negociações dessa posição e métodos auxiliares para calcular valores, obter e retornar informações sobre o tempo de existência da posição:
//+------------------------------------------------------------------+ //| Position class | //+------------------------------------------------------------------+ class CPosition : public CObject { private: CArrayObj m_list_deals; // List of position deals long m_position_id; // Position ID long m_time_in_msc; // Open time in milliseconds long m_time_out_msc; // Close time in milliseconds long m_magic; // Position magic datetime m_time_in; // Open time datetime m_time_out; // Close time ulong m_deal_in_ticket; // Open deal ticket ulong m_deal_out_ticket; // Close deal ticket double m_price_in; // Open price double m_price_out; // Close price double m_volume; // Position volume ENUM_POSITION_TYPE m_type; // Position type string m_symbol; // Position symbol int m_digits; // Symbol digits double m_point; // One symbol point value double m_contract_size; // Symbol trade contract size string m_currency_profit; // Symbol profit currency string m_account_currency; // Deposit currency //--- Return time with milliseconds string TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) { return ::TimeToString(time_msc/1000,flags)+"."+::IntegerToString(time_msc%1000,3,'0'); } //--- Calculate and return the open time of a known bar on a specified chart period by a specified time //--- (https://www.mql5.com/ru/forum/170952/page234#comment_50523898) datetime BarOpenTime(const ENUM_TIMEFRAMES timeframe,const datetime time) const { ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); //--- Calculate the bar open time on periods less than W1 if(period<PERIOD_W1) return time-time%::PeriodSeconds(period); //--- Calculate the bar open time on W1 if(period==PERIOD_W1) return time-(time+4*24*60*60)%::PeriodSeconds(period); //--- Calculate the bar open time on MN1 else { MqlDateTime dt; ::ResetLastError(); if(!::TimeToStruct(time,dt)) { ::PrintFormat("%s: TimeToStruct failed. Error %lu",__FUNCTION__,::GetLastError()); return 0; } return time-(time%(24*60*60))-(dt.day-1)*(24*60*60); } } //--- Return the symbol availability flag on the server. Add a symbol to MarketWatch window bool SymbolIsExist(const string symbol) const { bool custom=false; if(!::SymbolExist(symbol,custom)) return false; return ::SymbolSelect(symbol,true); } //--- Return the price of one point double GetOnePointPrice(const datetime time) const { if(time==0) return 0; //--- If the symbol's profit currency matches the account currency, return the contract size * Point of the symbol if(this.m_currency_profit==this.m_account_currency) return this.m_point*this.m_contract_size; //--- Otherwise, check for the presence of a symbol with the name "Account currency" + "Symbol profit currency" double array[1]; string reverse=this.m_account_currency+this.m_currency_profit; //--- If such a symbol exists and is added to the Market Watch if(this.SymbolIsExist(reverse)) { //--- If the closing price of the bar by 'time' is received, return the contract size * Point of the symbol divided by the Close price of the bar if(::CopyClose(reverse,PERIOD_CURRENT,time,1,array)==1 && array[0]>0) return this.m_point*this.m_contract_size/array[0]; //--- If failed to get the closing price of the bar by 'time', return zero else return 0; } //--- Check for the presence of a symbol with the name "Symbol profit currency" + "Account currency" string direct=this.m_currency_profit+this.m_account_currency; //--- If such a symbol exists and is added to the Market Watch if(this.SymbolIsExist(direct)) { //--- If the closing price of the bar by 'time' is received, return the contract size * Point of the symbol multiplied by the Close price of the bar if(::CopyClose(direct,PERIOD_CURRENT,time,1,array)==1) return this.m_point*this.m_contract_size*array[0]; } //--- Failed to get symbols whose profit currency does not match the account currency, neither reverse nor direct - return zero return 0; } public: //--- Methods for returning position properties long ID(void) const { return this.m_position_id; } // Position ID long Magic(void) const { return this.m_magic; } // Position magic long TimeInMsc(void) const { return this.m_time_in_msc; } // Open time long TimeOutMsc(void) const { return this.m_time_out_msc; } // Close time datetime TimeIn(void) const { return this.m_time_in; } // Open time datetime TimeOut(void) const { return this.m_time_out; } // Close time ulong DealIn(void) const { return this.m_deal_in_ticket; } // Open deal ticket ulong DealOut(void) const { return this.m_deal_out_ticket; } // Close deal ticket ENUM_POSITION_TYPE TypePosition(void) const { return this.m_type; } // Position type double PriceIn(void) const { return this.m_price_in; } // Open price double PriceOut(void) const { return this.m_price_out; } // Close price double Volume(void) const { return this.m_volume; } // Position volume string Symbol(void) const { return this.m_symbol; } // Position symbol //--- Methods for setting position properties void SetID(long id) { this.m_position_id=id; } // Position ID void SetMagic(long magic) { this.m_magic=magic; } // Position magic void SetTimeInMsc(long time_in_msc) { this.m_time_in_msc=time_in_msc; } // Open time void SetTimeOutMsc(long time_out_msc) { this.m_time_out_msc=time_out_msc; } // Close time void SetTimeIn(datetime time_in) { this.m_time_in=time_in; } // Open time void SetTimeOut(datetime time_out) { this.m_time_out=time_out; } // Close time void SetDealIn(ulong ticket_deal_in) { this.m_deal_in_ticket=ticket_deal_in; } // Open deal ticket void SetDealOut(ulong ticket_deal_out) { this.m_deal_out_ticket=ticket_deal_out; } // Close deal ticket void SetType(ENUM_POSITION_TYPE type) { this.m_type=type; } // Position type void SetPriceIn(double price_in) { this.m_price_in=price_in; } // Open price void SetPriceOut(double price_out) { this.m_price_out=price_out; } // Close price void SetVolume(double new_volume) { this.m_volume=new_volume; } // Position volume void SetSymbol(string symbol) // Position symbol { this.m_symbol=symbol; this.m_digits=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_DIGITS); this.m_point=::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT); this.m_contract_size=::SymbolInfoDouble(this.m_symbol,SYMBOL_TRADE_CONTRACT_SIZE); this.m_currency_profit=::SymbolInfoString(this.m_symbol,SYMBOL_CURRENCY_PROFIT); } //--- Add a deal to the list of deals bool DealAdd(CDeal *deal) { //--- Declare a variable of the result of adding a deal to the list bool res=false; //--- Set the flag of sorting by deal ticket for the list this.m_list_deals.Sort(DEAL_SORT_MODE_TIKET); //--- If a deal with such a ticket is not in the list - if(this.m_list_deals.Search(deal)==WRONG_VALUE) { //--- Set the flag of sorting by time in milliseconds for the list and //--- return the result of adding a deal to the list in order of sorting by time this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC); res=this.m_list_deals.InsertSort(deal); } //--- If the deal is already in the list, return 'false' else this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC); return res; } //--- Returns the start time of the bar (1) opening, (2) closing a position on the current chart period datetime BarTimeOpenPosition(void) const { return this.BarOpenTime(PERIOD_CURRENT,this.TimeIn()); } datetime BarTimeClosePosition(void) const { return this.BarOpenTime(PERIOD_CURRENT,this.TimeOut()); } //--- Return the flag of the existence of a position at the specified time bool IsPresentInTime(const datetime time) const { return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition()); } //--- Return the profit of the position in the number of points or in the value of the number of points relative to the close price double ProfitRelativeClosePrice(const double price_close,const datetime time,const bool points) const { //--- If there was no position at the specified time, return 0 if(!this.IsPresentInTime(time)) return 0; //--- Calculate the number of profit points depending on the position direction int pp=int((this.TypePosition()==POSITION_TYPE_BUY ? price_close-this.PriceIn() : this.PriceIn()-price_close)/this.m_point); //--- If the profit is in points, return the calculated number of points if(points) return pp; //--- Otherwise, return the calculated number of points multiplied by (cost of one point * position volume) return pp*this.GetOnePointPrice(time)*this.Volume(); } //--- Method for comparing two objects virtual int Compare(const CObject *node,const int mode=0) const { const CPosition *compared_obj=node; switch(mode) { case POS_SORT_MODE_TIME_IN_MSC : return(this.TimeInMsc()>compared_obj.TimeInMsc() ? 1 : this.TimeInMsc()<compared_obj.TimeInMsc() ? -1 : 0); case POS_SORT_MODE_TIME_OUT_MSC : return(this.TimeOutMsc()>compared_obj.TimeOutMsc() ? 1 : this.TimeOutMsc()<compared_obj.TimeOutMsc() ? -1 : 0); case POS_SORT_MODE_TIME_IN : return(this.TimeIn()>compared_obj.TimeIn() ? 1 : this.TimeIn()<compared_obj.TimeIn() ? -1 : 0); case POS_SORT_MODE_TIME_OUT : return(this.TimeOut()>compared_obj.TimeOut() ? 1 : this.TimeOut()<compared_obj.TimeOut() ? -1 : 0); case POS_SORT_MODE_DEAL_IN : return(this.DealIn()>compared_obj.DealIn() ? 1 : this.DealIn()<compared_obj.DealIn() ? -1 : 0); case POS_SORT_MODE_DEAL_OUT : return(this.DealOut()>compared_obj.DealOut() ? 1 : this.DealOut()<compared_obj.DealOut() ? -1 : 0); case POS_SORT_MODE_ID : return(this.ID()>compared_obj.ID() ? 1 : this.ID()<compared_obj.ID() ? -1 : 0); case POS_SORT_MODE_MAGIC : return(this.Magic()>compared_obj.Magic() ? 1 : this.Magic()<compared_obj.Magic() ? -1 : 0); case POS_SORT_MODE_SYMBOL : return(this.Symbol()>compared_obj.Symbol() ? 1 : this.Symbol()<compared_obj.Symbol() ? -1 : 0); case POS_SORT_MODE_PRICE_IN : return(this.PriceIn()>compared_obj.PriceIn() ? 1 : this.PriceIn()<compared_obj.PriceIn() ? -1 : 0); case POS_SORT_MODE_PRICE_OUT : return(this.PriceOut()>compared_obj.PriceOut() ? 1 : this.PriceOut()<compared_obj.PriceOut() ? -1 : 0); case POS_SORT_MODE_VOLUME : return(this.Volume()>compared_obj.Volume() ? 1 : this.Volume()<compared_obj.Volume() ? -1 : 0); default : return(this.TimeInMsc()>compared_obj.TimeInMsc() ? 1 : this.TimeInMsc()<compared_obj.TimeInMsc() ? -1 : 0); } } //--- Return a position type description string TypeDescription(void) const { return(this.m_type==POSITION_TYPE_BUY ? "Buy" : this.m_type==POSITION_TYPE_SELL ? "Sell" : "Unknown"); } //--- Print the properties of the position and its deals in the journal void Print(void) { //--- Display a header with a position description ::PrintFormat ( "Position %s %s #%lld, Magic %lld\n-Opened at %s at a price of %.*f\n-Closed at %s at a price of %.*f:", this.TypeDescription(),this.Symbol(),this.ID(),this.Magic(), this.TimeMSCtoString(this.TimeInMsc()), this.m_digits,this.PriceIn(), this.TimeMSCtoString(this.TimeOutMsc()),this.m_digits,this.PriceOut() ); //--- Display deal descriptions in a loop by all position deals for(int i=0;i<this.m_list_deals.Total();i++) { CDeal *deal=this.m_list_deals.At(i); if(deal==NULL) continue; deal.Print(); } } //--- Constructor CPosition(const long position_id) : m_time_in(0), m_time_in_msc(0),m_time_out(0),m_time_out_msc(0),m_deal_in_ticket(0),m_deal_out_ticket(0),m_type(WRONG_VALUE) { this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC); this.m_position_id=position_id; this.m_account_currency=::AccountInfoString(ACCOUNT_CURRENCY); } };
Na seção privada, são declaradas variáveis-membro da classe para armazenar as propriedades da posição, bem como métodos auxiliares.
O método BarOpenTime() calcula e retorna o tempo de abertura da barra no timeframe especificado com base em um tempo transmitido ao método:
datetime BarOpenTime(const ENUM_TIMEFRAMES timeframe,const datetime time) const { ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); //--- Calculate the bar open time on periods less than W1 if(period<PERIOD_W1) return time-time%::PeriodSeconds(period); //--- Calculate the bar open time on W1 if(period==PERIOD_W1) return time-(time+4*24*60*60)%::PeriodSeconds(period); //--- Calculate the bar open time on MN1 else { MqlDateTime dt; ::ResetLastError(); if(!::TimeToStruct(time,dt)) { ::PrintFormat("%s: TimeToStruct failed. Error %lu",__FUNCTION__,::GetLastError()); return 0; } return time-(time%(24*60*60))-(dt.day-1)*(24*60*60); } }
Por exemplo, sabemos um tempo específico e queremos agora saber o tempo de abertura da barra do período gráfico de interesse, dentro do qual está esse tempo. O método retornará o tempo de abertura da barra calculada. Assim, sempre podemos saber se a posição existia em determinado momento em uma barra específica do gráfico:
//--- Return the flag of the existence of a position at the specified time bool IsPresentInTime(const datetime time) const { return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition()); }
ou simplesmente podemos obter a barra de abertura ou fechamento da posição:
//--- Returns the start time of the bar (1) opening, (2) closing a position on the current chart period datetime BarTimeOpenPosition(void) const { return this.BarOpenTime(PERIOD_CURRENT,this.TimeIn()); } datetime BarTimeClosePosition(void) const { return this.BarOpenTime(PERIOD_CURRENT,this.TimeOut()); }
Para calcular o lucro/prejuízo da posição na barra atual do gráfico, usaremos um cálculo aproximado — contaremos o número de pontos de lucro em relação ao preço de abertura da posição e ao preço de fechamento da barra atual. No segundo caso, calcularemos o valor desses pontos de lucro/prejuízo:
double ProfitRelativeClosePrice(const double price_close,const datetime time,const bool points) const { //--- If there was no position at the specified time, return 0 if(!this.IsPresentInTime(time)) return 0; //--- Calculate the number of profit points depending on the position direction int pp=int((this.TypePosition()==POSITION_TYPE_BUY ? price_close-this.PriceIn() : this.PriceIn()-price_close)/this.m_point); //--- If the profit is in points, return the calculated number of points if(points) return pp; //--- Otherwise, return the calculated number of points multiplied by (cost of one point * position volume) return pp*this.GetOnePointPrice(time)*this.Volume(); }
Ambos os métodos não fornecerão uma imagem completa do lucro flutuante durante a vida útil da posição, mas isso não é necessário aqui. O mais importante é mostrar como obter dados sobre uma posição fechada. Para um cálculo preciso, precisaríamos de mais dados sobre o estado do mercado em cada barra da vida útil da posição — precisaríamos dos ticks desde a abertura até o fechamento da barra e, a partir deles, modelar o estado da posição nesse intervalo de tempo. E assim para cada barra. Atualmente, isso não é necessário.
O construtor da classe recebe o identificador da posição, cujo objeto será criado:
CPosition(const long position_id) : m_time_in(0), m_time_in_msc(0),m_time_out(0),m_time_out_msc(0),m_deal_in_ticket(0),m_deal_out_ticket(0),m_type(WRONG_VALUE) { this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC); this.m_position_id=position_id; this.m_account_currency=::AccountInfoString(ACCOUNT_CURRENCY); }
O identificador, no momento da criação do objeto da classe, já é conhecido — é obtido das propriedades da negociação. A negociação selecionada, da qual foi obtido o identificador da posição, após a criação do objeto da classe posição, é adicionada à sua lista de negociações. Tudo isso é feito na classe da lista de posições históricas, que veremos a seguir.
Classe da lista de posições históricas
//+------------------------------------------------------------------+ //| Historical position list class | //+------------------------------------------------------------------+ class CHistoryPosition { private: CArrayObj m_list_pos; // Historical position list public: //--- Create historical position list bool CreatePositionList(const string symbol=NULL); //--- Return a position object from the list by (1) index and (2) ID CPosition *GetPositionObjByIndex(const int index) { return this.m_list_pos.At(index); } CPosition *GetPositionObjByID(const long id) { //--- Create a temporary position object CPosition *tmp=new CPosition(id); //--- Set the list of positions to be sorted by position ID this.m_list_pos.Sort(POS_SORT_MODE_ID); //--- Get the index of the object in the list of positions with this ID int index=this.m_list_pos.Search(tmp); delete tmp; this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); return this.m_list_pos.At(index); } //--- Add a deal to the list of deals bool DealAdd(const long position_id,CDeal *deal) { CPosition *pos=this.GetPositionObjByID(position_id); return(pos!=NULL ? pos.DealAdd(deal) : false); } //--- Return the flag of the location of the specified position at the specified time bool IsPresentInTime(CPosition *pos,const datetime time) const { return pos.IsPresentInTime(time); } //--- Return the position profit relative to the close price double ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const { return pos.ProfitRelativeClosePrice(price_close,time,points); } //--- Return the number of historical positions int PositionsTotal(void) const { return this.m_list_pos.Total(); } //--- Print the properties of positions and their deals in the journal void Print(void) { for(int i=0;i<this.m_list_pos.Total();i++) { CPosition *pos=this.m_list_pos.At(i); if(pos==NULL) continue; pos.Print(); } } //--- Constructor CHistoryPosition(void) { this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); } };
Na seção privada da classe, é declarada a lista onde serão armazenados os ponteiros para os objetos das posições históricas. Os métodos públicos são, de alguma forma, destinados a trabalhar com essa lista.
Vamos analisá-los mais detalhadamente.
O método CreatePositionList() cria a lista de posições históricas:
//+------------------------------------------------------------------+ //| CHistoryPosition::Create historical position list | //+------------------------------------------------------------------+ bool CHistoryPosition::CreatePositionList(const string symbol=NULL) { //--- If failed to request the history of deals and orders, return 'false' if(!::HistorySelect(0,::TimeCurrent())) return false; //--- Declare a result variable and a pointer to the position object bool res=true; CPosition *pos=NULL; //--- In a loop based on the number of history deals int total=::HistoryDealsTotal(); for(int i=0;i<total;i++) { //--- get the ticket of the next deal in the list ulong ticket=::HistoryDealGetTicket(i); //--- If the deal ticket has not been received, or this is a balance deal, or if the deal symbol is specified, but the deal has been performed on another symbol, move on if(ticket==0 || ::HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE || (symbol!=NULL && symbol!="" && ::HistoryDealGetString(ticket,DEAL_SYMBOL)!=symbol)) continue; //--- Create a deal object and, if the object could not be created, add 'false' to the 'res' variable and move on CDeal *deal=new CDeal(ticket); if(deal==NULL) { res &=false; continue; } //--- Get the value of the position ID from the deal long pos_id=deal.PositionID(); //--- Get the pointer to a position object from the list pos=this.GetPositionObjByID(pos_id); //--- If such a position is not yet in the list, if(pos==NULL) { //--- create a new position object. If failed to do that, add 'false' to the 'res' variable, remove the deal object and move on pos=new CPosition(pos_id); if(pos==NULL) { res &=false; delete deal; continue; } //--- Set the sorting by time flag in milliseconds to the list of positions and add the position object to the corresponding place in the list this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); //--- If failed to add the position object to the list, add 'false' to the 'res' variable, remove the deal and position objects and move on if(!this.m_list_pos.InsertSort(pos)) { res &=false; delete deal; delete pos; continue; } } //--- If a position object is created and added to the list //--- If the deal object could not be added to the list of deals of the position object, add 'false' to the 'res' variable, remove the deal object and move on if(!pos.DealAdd(deal)) { res &=false; delete deal; continue; } //--- All is successful. //--- Set position properties depending on the deal type if(deal.Entry()==DEAL_ENTRY_IN) { pos.SetSymbol(deal.Symbol()); pos.SetDealIn(deal.Ticket()); pos.SetTimeIn(deal.Time()); pos.SetTimeInMsc(deal.TimeMsc()); ENUM_POSITION_TYPE type=ENUM_POSITION_TYPE(deal.TypeDeal()==DEAL_TYPE_BUY ? POSITION_TYPE_BUY : deal.TypeDeal()==DEAL_TYPE_SELL ? POSITION_TYPE_SELL : WRONG_VALUE); pos.SetType(type); pos.SetPriceIn(deal.Price()); pos.SetVolume(deal.Volume()); } if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY) { pos.SetDealOut(deal.Ticket()); pos.SetTimeOut(deal.Time()); pos.SetTimeOutMsc(deal.TimeMsc()); pos.SetPriceOut(deal.Price()); } if(deal.Entry()==DEAL_ENTRY_INOUT) { ENUM_POSITION_TYPE type=ENUM_POSITION_TYPE(deal.TypeDeal()==DEAL_TYPE_BUY ? POSITION_TYPE_BUY : deal.TypeDeal()==DEAL_TYPE_SELL ? POSITION_TYPE_SELL : WRONG_VALUE); pos.SetType(type); pos.SetVolume(deal.Volume()-pos.Volume()); } } //--- Return the result of creating and adding a position to the list return res; }
Num laço pela lista de negociações históricas no terminal, obtemos a negociação atual, pegamos dela o identificador da posição e verificamos se existe na lista de posições históricas um objeto de posição com esse identificador. Se ainda não houver tal posição na lista, criamos um novo objeto de posição e o adicionamos à lista. Se já houver tal posição, obtemos o ponteiro para esse objeto da lista. Em seguida, adicionamos a negociação à lista de negociações da posição histórica. Depois, com base no tipo de negociação, inserimos no objeto posição as propriedades associadas à posição e registradas nas propriedades dessa negociação.
Assim, percorrendo todas as negociações históricas, criamos uma lista de posições históricas, onde armazenamos, em uma lista, as negociações de cada posição. Ao criar a lista de posições pela lista de negociações, não levamos em conta a alteração no volume das posições em caso de fechamento parcial. Se necessário, tal funcionalidade deve ser adicionada. No momento, isso está fora do escopo deste artigo, pois estamos vendo um exemplo de criação de uma lista de posições históricas, não uma reprodução exata das mudanças nas propriedades das posições durante sua existência.
Adicionar a funcionalidade descrita deve ser no bloco de código marcado com cor. Ou, seguindo os princípios da POO, fazer o método CreatePositionList() virtual, herdá-lo da classe da lista de posições históricas e, na própria classe, fazer sua própria implementação desse método. Ou pode-se mover a alteração das propriedades da posição pelas propriedades da negociação (bloco de código marcado com cor) para um método virtual protegido e, na classe herdada, sobrescrever esse método protegido, sem reescrever completamente o CreatePositionList().
O método GetPositionObjByIndex() retorna um ponteiro para o objeto de posição histórica da lista pelo índice passado ao método.
Retorna um ponteiro para o objeto localizado na lista no índice especificado ou NULL, se o objeto não for encontrado.
O método GetPositionObjByID() retorna um ponteiro para o objeto de posição histórica da lista pelo identificador da posição passado ao método:
CPosition *GetPositionObjByID(const long id) { //--- Create a temporary position object CPosition *tmp=new CPosition(id); //--- Set the list of positions to be sorted by position ID this.m_list_pos.Sort(POS_SORT_MODE_ID); //--- Get the index of the object in the list of positions with this ID int index=this.m_list_pos.Search(tmp); //--- Remove the temporary object and set the sorting by time flag for the list in milliseconds. delete tmp; this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); //--- Return the pointer to the object in the list at the received index, or NULL if the index was not received return this.m_list_pos.At(index); }
Na lista, procura-se o objeto cujo identificador da posição é igual ao passado ao método, e retorna-se o ponteiro para o objeto encontrado, ou NULL, se o objeto não for encontrado.
O método DealAdd() adiciona uma negociação à lista de negociações:
bool DealAdd(const long position_id,CDeal *deal) { CPosition *pos=this.GetPositionObjByID(position_id); return(pos!=NULL ? pos.DealAdd(deal) : false); }
O método recebe o identificador da posição à qual a negociação deve ser adicionada, e o ponteiro para a negociação também é passado ao método. Ao adicionar com sucesso a negociação à lista de negociações da posição, o método retorna true; em caso de erro, false.
O método IsPresentInTime() retorna um indicador de que a posição especificada está presente no tempo especificado:
bool IsPresentInTime(CPosition *pos,const datetime time) const { return pos.IsPresentInTime(time); }
O método recebe o ponteiro para a posição a ser verificada e o tempo no qual se quer saber se a posição estava presente ou não. Se a posição estava presente no tempo especificado, o método retorna true; caso contrário, false.
O método ProfitRelativeClosePrice() retorna o lucro da posição em relação ao preço de fechamento.
double ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const { return pos.ProfitRelativeClosePrice(price_close,time,points); }
O método recebe o ponteiro para a posição cujos dados precisam ser obtidos, o preço de fechamento da barra e seu tempo, em relação aos quais o lucro da posição deve ser obtido, e um indicador que especifica em quais unidades os dados devem ser obtidos — em pontos ou no valor desses pontos.
O método PositionsTotal() retorna o número de posições históricas na lista:
int PositionsTotal(void) const { return this.m_list_pos.Total(); }
O método Print() imprime no log as propriedades das posições e suas negociações:
void Print(void) { //--- In the loop through the list of historical positions for(int i=0;i<this.m_list_pos.Total();i++) { //--- get the pointer to the position object and print the properties of the position and its deals CPosition *pos=this.m_list_pos.At(i); if(pos==NULL) continue; pos.Print(); } }
No construtor da classe, a lista de posições históricas é definida com o sinalizador de classificação por tempo em milissegundos:
//--- Constructor CHistoryPosition(void) { this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); }
Todas as classes para a implementação do plano proposto foram criadas.
Agora, escreveremos o código do indicador que criará a lista de todas as posições por símbolo atual, que já existiram na conta, e desenhará no gráfico um diagrama de seu lucro/prejuízo em cada barra dos dados históricos.
Indicador de diagrama de lucro das posições
O jeito mais adequado para desenhar o buffer do indicador em forma de diagramas é usar a área colorida entre dois buffers do indicador, DRAW_FILLING.
O estilo DRAW_FILLING cria uma área colorida entre os valores de dois buffers do indicador. Basicamente, ele desenha duas linhas e preenche o espaço entre elas com uma das duas cores especificadas. É perfeito para criar indicadores que desenham canais. Nenhum dos buffers pode ter apenas valores vazios, pois, nesse caso, a renderização não acontece.
Podem ser definidas duas cores de preenchimento:
- a primeira cor para as áreas onde os valores do primeiro buffer do indicador são maiores que os valores do segundo buffer do indicador;
- a segunda cor para as áreas onde os valores do segundo buffer do indicador são maiores que os valores do primeiro buffer do indicador.
A cor de preenchimento pode ser definida por diretivas do compilador ou dinamicamente usando a função PlotIndexSetInteger(). A mudança dinâmica das propriedades gráficas permite "dar vida" aos indicadores, para que mudem sua aparência dependendo da situação atual.
O indicador é calculado para todas as barras cujos valores de ambos os buffers do indicador não são iguais a 0 e não são valores vazios. Para indicar qual valor deve ser considerado "vazio", defina esse valor na propriedade PLOT_EMPTY_VALUE:
#define INDICATOR_EMPTY_VALUE -1.0 ... //--- the INDICATOR_EMPTY_VALUE (empty) value will not be used in the calculation PlotIndexSetDouble(plot_index_DRAW_FILLING,PLOT_EMPTY_VALUE,INDICATOR_EMPTY_VALUE);
A renderização nas barras que não participam do cálculo do indicador dependerá dos valores nos buffers do indicador:
- As barras cujos valores de ambos os buffers do indicador são iguais a 0 não participam da renderização do indicador. Ou seja, a área com valores zero não será preenchida.
- As barras cujos valores dos buffers do indicador são iguais ao "valor vazio" participam da renderização do indicador. A área com valores vazios será preenchida de forma a conectar áreas com valores significativos.
É importante notar que se o "valor vazio" for igual a zero, as barras que não participam do cálculo do indicador também serão preenchidas.
O número de buffers necessários para construir DRAW_FILLING é 2.
Adicionaremos um parâmetro de entrada para selecionar em quais unidades retornar o lucro da posição na barra, declararemos um ponteiro para a instância da classe da lista de posições históricas e adicionaremos um pouco no manipulador OnInit():
//+------------------------------------------------------------------+ //| Indicator | //+------------------------------------------------------------------+ //--- input parameters input bool InpProfitPoints = true; // Profit in Points //--- indicator buffers double BufferFilling1[]; double BufferFilling2[]; //--- global variables CHistoryPosition *history=NULL; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping SetIndexBuffer(0,BufferFilling1,INDICATOR_DATA); SetIndexBuffer(1,BufferFilling2,INDICATOR_DATA); //--- Set the indexing of buffer arrays as in a timeseries ArraySetAsSeries(BufferFilling1,true); ArraySetAsSeries(BufferFilling2,true); //--- Set Digits of the indicator data equal to 2 and one level equal to 0 IndicatorSetInteger(INDICATOR_DIGITS,2); IndicatorSetInteger(INDICATOR_LEVELS,1); IndicatorSetDouble(INDICATOR_LEVELVALUE,0); //--- Create a new historical data object history=new CHistoryPosition(); //--- Return the result of creating a historical data object return(history!=NULL ? INIT_SUCCEEDED : INIT_FAILED); }
Escreveremos o manipulador OnDeinit(), no qual excluiremos a instância criada da classe da lista de posições históricas e limparemos os comentários no gráfico:
//+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the object of historical positions and chart comments if(history!=NULL) delete history; Comment(""); }
No manipulador OnCalculate(), no primeiro tick criaremos a lista de todas as posições históricas e, no laço principal, acessaremos a lista criada usando a função de cálculo de lucro na barra atual do laço do indicador:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Set 'close' and 'time' array indexing as in timeseries ArraySetAsSeries(close,true); ArraySetAsSeries(time,true); //--- Flag of the position list successful creation static bool done=false; //--- If the position data object is created if(history!=NULL) { //--- If no position list was created yet if(!done) { //--- If the list of positions for the current symbol has been successfully created, if(history.CreatePositionList(Symbol())) { //--- print positions in the journal and set the flag for successful creation of a list of positions history.Print(); done=true; } } } //--- Number of bars required to calculate the indicator int limit=rates_total-prev_calculated; //--- If 'limit' exceeds 1, this means this is the first launch or a change in historical data if(limit>1) { //--- Set the number of bars for calculation equal to the entire available history and initialize the buffers with "empty" values limit=rates_total-1; ArrayInitialize(BufferFilling1,EMPTY_VALUE); ArrayInitialize(BufferFilling2,EMPTY_VALUE); } //--- In the loop by symbol history bars for(int i=limit;i>=0;i--) { //--- get the profit of positions present on the bar with the i loop index and write the resulting value to the first buffer double profit=Profit(close[i],time[i]); BufferFilling1[i]=profit; //--- Always write zero to the second buffer. Depending on whether the value in the first buffer is greater or less than zero, //--- the fill color between arrays 1 and 2 of the indicator buffer will change BufferFilling2[i]=0; } //--- return value of prev_calculated for the next call return(rates_total); }
Aqui é realizado um ciclo indicador comum desde o início dos dados históricos até a data atual. No ciclo, para cada barra, é chamada a função de cálculo de lucro das posições históricas na barra atual do ciclo:
//+-----------------------------------------------------------------------+ //| Return the profit of all positions from the list at the specified time| //+-----------------------------------------------------------------------+ double Profit(const double price,const datetime time) { //--- Variable for recording and returning the result of calculating profit on a bar double res=0; //--- In the loop by the list of historical positions for(int i=0;i<history.PositionsTotal();i++) { //--- get the pointer to the next position CPosition *pos=history.GetPositionObjByIndex(i); if(pos==NULL) continue; //--- add the value of calculating the profit of the current position, relative to the 'price' on the bar with 'time', to the result res+=pos.ProfitRelativeClosePrice(price,time,InpProfitPoints); } //--- Return the calculated amount of profit of all positions relative to the 'price' on the bar with 'time' return res; }
A função recebe o preço (fechamento da barra) em relação ao qual o número de pontos de lucro da posição deve ser obtido, e o tempo em que se verifica a existência da posição (tempo de abertura da barra). Em seguida, o lucro de todas as posições, obtido de cada objeto de posições históricas, é somado e retornado.
Após a compilação, o indicador pode ser executado no gráfico de um símbolo onde houve muitas posições abertas, e ele desenhará um diagrama de lucro de todas as posições históricas:
Considerações finais
Vimos um exemplo de como restaurar uma posição a partir de negociações e compilar uma lista de todas as posições históricas que já existiram na conta. O exemplo é simples e não abrange todos os aspectos da restauração precisa de posições a partir de negociações, mas é suficiente para que você possa complementar por conta própria até a precisão necessária na restauração das posições.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/13911
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso