![Indicador de posiciones históricas en el gráfico como diagrama de sus ganancias/pérdidas](https://c.mql5.com/2/62/midjourney_image_13911_52_463_3_600x314.jpg)
Indicador de posiciones históricas en el gráfico como diagrama de sus ganancias/pérdidas
Contenido
- Introducción
- Clase de transacciones
- Clase de posiciones
- Clase de lista de posiciones históricas
- Indicador de diagrama de beneficios por posición
- Conclusión
Introducción
Posición... Una posición es la consecuencia de la ejecución (transacción) de una orden comercial enviada al servidor: Order --> Deal --> Position.
Siempre podemos obtener la lista de posiciones abiertas en nuestro programa usando las funciones PositionSelect() en el registro de posiciones por compensación, especificando el nombre del símbolo en el que está abierta la posición:
if(PositionSelect(symbol_name)) { /* Work with selected position data PositionGetDouble(); PositionGetInteger(); PositionGetString(); */ }
Para cuentas con representación independiente de posiciones (cobertura), primero tendremos que obtener el número de posiciones usando PositionsTotal(), luego, en un ciclo sobre el número de posiciones, deberemos obtener el ticket de la posición según su índice en la lista de posiciones abiertas usando PositionGetTicket(), y después seleccionar una posición según el ticket obtenido para trabajar con ella 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(); */ }
Aquí todo es sencillo y directo, pero la cosa cambia cuando necesitamos saber algo sobre una posición ya cerrada: no hay funciones para trabajar con posiciones históricas...
En este caso, deberemos recordar y saber que cada posición tiene su propio identificador único, y que este identificador se escribe en las transacciones que han afectado a la posición, provocando su apertura, modificación o cierre. Así, podemos obtener la lista de transacciones, y de ella la transacción y el identificador de la posición en la que dicha transacción ha estado involucrada, usando las funciones HistorySelect(), HistoryDealsTotal() y HistoryDealGetTicket() (HistoryDealSelect()).
En general, el identificador de la posición en la que se ha efectuado la transacción puede obtenerse del siguiente modo:
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(); */ } }
Es decir, teniendo una lista de transacciones, siempre podemos saber a qué posición pertenecía una transacción. Por ejemplo, podemos obtener un identificador de posición, y según las transacciones con este identificador, encontrar la transacción de entrada en el mercado (apertura de posición) y la transacción de salida del mercado (cierre de posición). Y según las propiedades de estas transacciones podemos averiguar la hora, el lote y otras propiedades necesarias de la posición histórica buscada.
Para buscar posiciones históricas cerradas, haremos lo siguiente: obtendremos una lista de transacciones históricas. En un ciclo a través de esta lista obtendremos cada transacción siguiente y comprobaremos el identificador de la posición en la que esta transacción estaba involucrada. Si esta posición no está ya en la lista, crearemos un objeto de posición, y si ya existe, utilizaremos una posición existente con este identificador. Luego añadiremos la transacción actual (si no está ya en la lista) a la lista de transacciones de la posición encontrada. Así, tras recorrer toda la lista de transacciones históricas, encontraremos todas las posiciones en las que han participado las transacciones, crearemos una lista de todas las posiciones históricas y en cada objeto de posición histórica, en su lista de transacciones, añadiremos todas las transacciones que han participado en esa posición.
Necesitaremos tres clases:
- La clase de transacción. Esta contiene las propiedades de transacción necesarias para identificar la posición y sus propiedades.
- La clase de posición. Esta contiene la lista de transacciones que han participado en ella durante la existencia de la posición y las propiedades inherentes a la misma.
- La clase de lista de posiciones históricas. Se trata de una lista de posiciones históricas encontradas con la posibilidad de seleccionar una posición según las propiedades especificadas.
Estas tres pequeñas clases le permitirán encontrar fácilmente todas las transacciones históricas, guardarlas en una lista y, a continuación, utilizar los datos de dichas posiciones en el indicador para dibujar un gráfico de beneficios/pérdidas de las posiciones para el símbolo seleccionado en la cuenta.
En la carpeta Indicators crearemos un nuevo archivo de indicador llamado PositionInfoIndicator. Especificaremos el dibujo del indicador con un búfer dibujado con estilo Filling en una ventana de gráfico aparte con colores de relleno Green y Red.
Se creará un modelo de indicador con un encabezado así:
//+------------------------------------------------------------------+ //| 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
Debajo del código, pondremos las clases que creemos.
Para crear listas de transacciones y posiciones, utilizaremos la clase de array dinámico de punteros a los ejemplares de la clase CObject y sus herederos de la Biblioteca Estándar.
Luego conectaremos el archivo de esta clase con el archivo creado:
//+------------------------------------------------------------------+ //| 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>
La clase contiene un método Search() para buscar los elementos iguales al patrón en un array clasificado:
//+------------------------------------------------------------------+ //| 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); }
El array deberá estar clasificado según alguna propiedad de los objetos cuyos punteros estén contenidos en él. El modo de clasificación se establecerá transmitiendo un valor entero a la variable m_sort_mode . El valor por defecto será 0. Un valor de -1 indicará que el array no está clasificado. Para clasificar según diferentes propiedades, deberemos establecer diferentes modos de clasificación fijando la variable m_sort_mode en valores de cero en adelante. Para ello, será cómodo utilizar enumeraciones que definan distintos modos de clasificación de listas. Estos modos se utilizarán en el método virtual para comparar dos objetos Compare(). Se define en el archivo de clase CObject y retorna el valor 0, lo cual significa que los objetos comparados resultan idénticos:
//--- method of comparing the objects virtual int Compare(const CObject *node,const int mode=0) const { return(0); }
Deberemos redefinir este método en sus clases, donde podremos organizar la comparación de dos objetos similares según sus diferentes propiedades (si m_sort_mode se establece en 0, los objetos se compararán según una propiedad, si m_sort_mode se establece en 1, según otra propiedad, si m_sort_mode se establece en 2, según una tercera propiedad, etc.).
Cada objeto de clase creado hoy tendrá su propio conjunto de propiedades. En función de estas propiedades, habrá que clasificar los objetos para la búsqueda. Por lo tanto, primero deberemos crear enumeraciones de propiedades para estos 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
Las propiedades correspondientes a las constantes de estas dos enumeraciones se contendrán en las clases de objetos de transacción y posición. Precisamente con estas propiedades será posible clasificar las listas para encontrar la igualdad de los objetos.
A continuación escribiremos los códigos de las clases creadas.
Clase de transacciones
Analicemos ahora la clase de transacción en su totalidad://+------------------------------------------------------------------+ //| 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){} };
En principio, resulta sencillo: en la sección privada, declararemos las variables de miembro de clase para almacenar las propiedades de la transacción. La sección pública implementará los métodos para establecer y retornar las propiedades de la transacción. El método virtual Compare() implementará una comparación en cada propiedad de una transacción, dependiendo de la propiedad que transmitamos al método como modo de comparación. Si el valor de la propiedad comprobada del objeto actual es superior al valor de la misma propiedad del objeto comparado, se retornará 1, si es inferior, se retornará -1, en caso contrario, si los valores son iguales, se retornará 0. En el constructor de la clase, se considera que la transacción ya ha sido seleccionada y sus propiedades se escribirán en las variables privadas correspondientes de la clase. Esto bastará para crear un objeto de transacción que almacene todas las propiedades necesarias de la transacción seleccionada de la lista de transacciones históricas del terminal. Para cada transacción se creará un objeto de esta clase, se extraerá el identificador de posición de las propiedades del objeto y se creará un objeto de la clase de posición histórica. Todas las transacciones pertenecientes a esta posición se introducirán en la lista de transacciones de la clase de posición. Así, el objeto posición histórica contendrá una lista de todas sus transacciones. Y ya de ellos podremos extraer la historia de la vida de esta posición.
Echemos ahora un vistazo a la clase de posición histórica.
Clase de posiciones
La clase de posición contendrá una lista con las transacciones de esta posición y los métodos auxiliares para calcular los valores, obtener la información sobre el tiempo de existencia de la posición y devolver dicha información:
//+------------------------------------------------------------------+ //| 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); } };
En la sección privada, se declararán las variables de miembro de clase para almacenar las propiedades de la posición y se escribirán los métodos auxiliares.
El método BarOpenTime() calculará y retornará la hora de apertura de la barra en un marco temporal dado según la hora transmitida al 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 ejemplo, ya conocemos una hora y ahora queremos conocer la hora de apertura de la barra del periodo del gráfico que nos interesa, dentro del cual se encuentra esta hora. El método retornará la hora de apertura de la barra calculada. De esta forma siempre podremos saber si una posición existía en un momento determinado en una barra determinada del 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()); }
o podremos hacer que una barra abra o cierre una posición:
//--- 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 los beneficios/pérdidas de una posición en la barra actual del gráfico, utilizaremos un principio de cálculo simplificado y aproximado: en un caso contaremos el número de puntos de beneficio relativos al precio de apertura de la posición y al precio de cierre de la barra actual. En el segundo caso, calcularemos el valor de estos puntos de beneficio/pérdida:
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 métodos no ofrecen una imagen completa del beneficio flotante a lo largo de la vida de la posición, pero eso no es necesario aquí. Aquí resulta más importante para nosotros mostrar el principio de obtención de los datos sobre una posición cerrada. Para lograr un cálculo preciso, necesitaremos datos mucho más variados sobre el estado del mercado en cada barra de la vida de la posición: será necesario obtener los ticks desde la hora de apertura hasta la hora de cierre de la barra y modelar el estado de la posición en este intervalo temporal basándonos en ellos. Y lo mismo ocurrirá con todas las barras. Por el momento no nos plateamos dicha tarea.
El identificador de la posición cuyo objeto será creado se transmitirá al constructor de la clase:
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); }
El identificador en el momento de la creación del objeto de clase ya será conocido pues lo obtendremos de las propiedades de la transacción. La transacción seleccionada, de la que hemos obtenido el identificador de posición, la añadiremos a nuestra lista de transacciones tras crear el objeto de la clase de posición. Todo esto se realizará en la clase de lista de posiciones históricas, que veremos a continuación.
Clase de lista de posiciones 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); } };
En la sección privada de la clase, declararemos una lista donde se colocarán los punteros a los objetos de las posiciones históricas. Los métodos públicos están diseñados para trabajar con esta lista hasta cierto punto.
Veamos las descripciones más de cerca.
El método CreatePositionList() creará una lista de posiciones 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; }
En un ciclo a través de la lista de transacciones históricas en el terminal obtendremos la siguiente transacción, tomaremos el identificador de posición de la misma y comprobaremos si existe un objeto de posición con este identificador en la lista de posiciones históricas. Si no existe tal posición en la lista, crearemos un nuevo objeto de posición y lo añadiremos a la lista. Si tal posición ya existe, obtendremos un puntero a este objeto de la lista. A continuación, añadiremos la transacción a la lista de transacciones de la posición histórica. Después, según el tipo de transacción, introduciremos las propiedades inherentes a la posición en el objeto posición y las registraremos en las propiedades de esta transacción.
Así, recorriendo todas las transacciones históricas, crearemos una lista de posiciones históricas, dentro de la cual se almacenarán en una lista las transacciones pertenecientes a cada posición. Al crear la lista de posiciones según la lista de transacciones, aquí no consideraremos el cambio en el volumen de las posiciones en su cierre parcial. De ser necesario, añadiremos esta funcionalidad. Por el momento, esto queda fuera del alcance del artículo, ya que estamos trabajando con un ejemplo de creación de una lista de posiciones históricas, no con una reproducción exacta de los cambios en las propiedades de las posiciones a lo largo de su tiempo de existencia.
La funcionalidad descrita la añadiremos al bloque de código marcado a color. O, si seguimos los principios de la programación orientada a objetos, haremos virtual el método CreatePositionList(), heredaremos de la clase de lista de posiciones históricas y realizaremos nuestra propia implementación de este método en nuestra propia clase. O es posible cambiar las propiedades de la posición por las propiedades de la transacción (el bloque de código marcado a color) en un método virtual protegido, y ya en la clase heredada redefinir este método protegido sin reescribir CreatePositionList() completamente.
El método GetPositionObjByIndex() retornará el puntero a un objeto de posición histórica de la lista según el índice transmitido al método.
Se retornará el puntero al objeto situado en la lista en el índice especificado, o NULL si no se encuentra ningún objeto.
El métodoGetPositionObjByID() retornará el puntero al objeto de posición histórica de la lista según el ID de posición transmitido al 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); }
La lista buscará el objeto cuyo identificador de posición sea igual al transmitido al método, y retornará el puntero al objeto encontrado, o NULL si no se encuentra ningún objeto.
El método DealAdd() añadirá una transacción a la lista de transacciones:
bool DealAdd(const long position_id,CDeal *deal) { CPosition *pos=this.GetPositionObjByID(position_id); return(pos!=NULL ? pos.DealAdd(deal) : false); }
El identificador de la posición a la que debe añadirse la transacción se transmitirá al método cuyo puntero también es transmitido al método. Si la transacción se añade correctamente a la lista de transacciones de la posición, el método retornará true, si se produce un error, retornará false.
El método IsPresentInTime() retornará una bandera para encontrar la posición especificada en el momento especificado:
bool IsPresentInTime(CPosition *pos,const datetime time) const { return pos.IsPresentInTime(time); }
Al método se le transmitirá el puntero a la posición a comprobar y el tiempo en el que se quiere saber si la posición existió o no. Si la posición existía en el momento especificado, el método retornará true, en caso contrario, retornará false.
El método ProfitRelativeClosePrice() retornará el beneficio de la posición relativo al precio de cierre.
double ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const { return pos.ProfitRelativeClosePrice(price_close,time,points); }
El método obtendrá el puntero a la posición cuyos datos deben obtenerse, el precio de cierre de la barra y la hora en relación con la cual deberá obtenerse el beneficio de la posición, así como la bandera que indica en qué unidades deberán obtenerse los datos: en cantidad de puntos o en el coste de este número de puntos.
El método PositionsTotal() retornará el número de posiciones históricas de la lista:
int PositionsTotal(void) const { return this.m_list_pos.Total(); }
El método Print() imprimirá las propiedades de las posiciones y sus transacciones en el diario de registro:
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(); } }
En el constructor de la clase, asignaremos a la lista de posiciones históricas la bandera de clasificación según el tiempo en milisegundos:
//--- Constructor CHistoryPosition(void) { this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); }
Además, se establecerán todas las clases para la aplicación del plan trazado.
Ahora vamos a escribir un código de indicador que creará una lista con todas las posiciones para el símbolo actual que han existido en la cuenta y dibujar un gráfico con sus beneficios/pérdidas en cada barra de datos históricos.
Indicador de diagrama de beneficios por posición
El estilo de dibujado del búfer indicador más apropiado para dibujar en forma de diagrama es el área coloreada entre los dos búferes de indicador, DRAW_FILLING.
El estilo DRAW_FILLING dibujará un área coloreada entre los valores de los dos búferes de indicador. De hecho, este estilo dibujará dos líneas y pintará el espacio entre ellas con uno de los dos colores especificados. Está pensado para crear indicadores que dibujen canales. Ninguno de los búferes podrá contener únicamente valores vacíos, ya que en este caso no se producirá renderización alguna.
Podremos especificar dos colores de rellenado:
- el primer color para las zonas en las que los valores del primer búfer de indicador son mayores que los valores del segundo búfer de indicador;
- un segundo color para las zonas en las que los valores del segundo búfer de indicador son mayores que los valores del primer búfer de indicador.
El color de rellenado puede establecerse usando directivas del compilador o dinámicamente utilizando la función PlotIndexSetInteger(). El cambio dinámico de las propiedades de la construcción gráfica permite "dar vida" a los indicadores para que cambien de aspecto en función de la situación actual.
El indicador se calculará para todas las barras para las que los valores de ambos búferes de indicador no son iguales a 0 ni iguales al valor vacío. Para especificar qué valor deberá considerarse "vacío", estableceremos este valor en la propiedad 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);
El dibujado de las barras que no participan en el cálculo del indicador dependerá de los valores de los búferes de indicador:
- Las barras cuyos valores de los dos búferes de indicador son iguales a 0 no participarán en el dibujado del indicador. Es decir, la zona con valores cero no se coloreará.
- Las barras cuyos valores de los búferes del indicador sean iguales al "valor vacío" participarán en el dibujado del indicador. La zona con valores vacíos se coloreará para conectar las zonas con valores significativos.
Debemos tener en cuenta que si el "valor vacío" es igual a cero, las barras que no participan en el cálculo del indicador también se colorearán.
El número de búferes necesarios para construir DRAW_FILLING es 2.
Vamos a añadir un parámetro de entrada para elegir en qué unidades se retornará el beneficio de la posición en la barra, declarar el puntero a un ejemplar de clase de lista de posiciones históricas y añadir un pequeño manejador 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); }
Luego escribiremos el manejador OnDeinit(), donde borraremos el ejemplar creado de la clase de lista de posiciones históricas y eliminaremos los comentarios en el 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(""); }
En el manejador OnCalculate(), en el primer tick, crearemos una lista con todas las posiciones históricas y más adelante en el ciclo principal, accederemos a la lista creada usando la función de cálculo de beneficios en la barra actual del ciclo del 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); }
Aquí se organizará el ciclo habitual de los indicadores, desde el inicio de los datos históricos hasta la fecha actual. La función de cálculo del beneficio de las posiciones históricas en la barra actual del ciclo se llamará para cada barra del 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; }
La función recibirá el precio (Close de la barra) en relación al cual deberemos recibir el número de puntos de beneficio de la posición, y el momento en el que se comprobará la existencia de la posición (Time open de la barra). Además, se sumará y se retornará el beneficio de todas las posiciones obtenidas de cada objeto de posiciones históricas.
Tras realizar la compilación, podremos ejecutar el indicador en un gráfico de símbolos en el que haya muchas posiciones abiertas, y este dibujará el gráfico de beneficio de todas las posiciones históricas:
Conclusión
Hoy hemos visto un ejemplo de cómo se puede reconstruir una posición a partir de las transacciones y hacer una lista de todas las posiciones históricas que ha habido en la cuenta. El ejemplo es simple y no abarca todos los aspectos relacionados con la recuperación precisa de una posición a partir de las transacciones, pero resulta suficiente para que usted mismo pueda complementarlo hasta la precisión deseada de recuperación de la posición.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/13911
![Aprendizaje automático y Data Science (Parte 17): ¿Crece el dinero en los árboles? Bosques aleatorios en el mercado Fórex](https://c.mql5.com/2/63/midjourney_image_13765_54_491__3-logo.png)
![Redes neuronales: así de sencillo (Parte 68): Optimización de políticas offline basada en preferencias](https://c.mql5.com/2/62/midjourney_image_13912_49_444__1-logo.png)
![Algoritmos de optimización de la población: Algoritmos de estrategias evolutivas (Evolution Strategies, (μ,λ)-ES y (μ+λ)-ES)](https://c.mql5.com/2/63/midjourney_image_13923_53_472__2-logo.png)
![Características del Wizard MQL5 que debe conocer (Parte 09): Combinación de clusterización de K-medias con ondas fractales](https://c.mql5.com/2/62/midjourney_image_13915_50_439__5-logo.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso