//+------------------------------------------------------------------+ //| AccountStatus.mqh | //| Copyright 2025, Niquel Mendoza. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Niquel Mendoza." #property link "https://www.mql5.com" #property strict #ifndef MQLARTICLES_RM_ACCOUNTSTATUS_MQH #define MQLARTICLES_RM_ACCOUNTSTATUS_MQH //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #include "RM_Functions.mqh" #include //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ #define ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_TP 1 #define ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_SL 2 #define ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_TP_SL (ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_TP|ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_SL) //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ double account_status_curr_profit; // Profit en flotante - profit in float bool account_status_positions_open; // Flag indicating if there are open positions in the account //+------------------------------------------------------------------+ //| Class for account management | //+------------------------------------------------------------------+ class CAccountStatus : public CManagerBase { private: //--- CTicketsInfo* open_positions; // CTicketsInfo type object for efficient ticket management bool init; // Flag indicating if it has been initialized correctly //--- double account_daily_profit; // Account profit (adding EAs and user etc, total) daily double account_weekly_profit; // Account profit (adding EAs and user etc, total) weekly double account_monthly_profit; // Account profit (adding EAs and user etc, total) monthly double account_gross_profit; // Account profit (adding EAs and user etc, total) total //--- datetime last_day_time; // Opening time of the current daily candle datetime last_weekly_time; // Opening time of the current weekly candle datetime last_monthly_time; // Opening time of the current monthly candle //--- ROrder last_order_struct; //--- Most recent structure of the opened or closed position ROnOpenClosePosition last_struct; // Variables that store a property of the last deal (when adding a deal, TRADE_TRANSACTION_DEAL_ADD) ulong last_deal_ticket; // Ticket ENUM_DEAL_TYPE last_deal_type; // Deal type // Variable that stores the last symbol for specific events (TRADE_TRANSACTION_DEAL_ADD, TRADE_TRANSACTION_ORDER_DELETE) string last_trans_symbol; // Symbol //--- Function to update profits void UpdateProfitInit(); void UpdateProfit(const double& new_profit); public: CAccountStatus(); ~CAccountStatus(); //--- Getter __forceinline Position GetPosition(ulong ticket) { return open_positions.GetByTicket(ticket); } // Get Position by ticket __forceinline int16_t GetPositionsTotal() const { return open_positions.Total(); } // Get total number of open positions __forceinline bool TheTicketExists(const ulong ticket) const { return open_positions.Exists(ticket); } // Check if ticket exists in positions array inline double AccountGrossProfit() const { return account_gross_profit; } inline double AccountDailyProfit() const { return account_daily_profit; } inline double AccountWeeklyProfit() const { return account_weekly_profit; } inline double AccountMonthlyProfit() const { return account_monthly_profit; } inline bool IsInitialized() const { return init; } inline string LastTransctionSymbol() const { return last_trans_symbol; } __forceinline ROnOpenClosePosition LastStructPosition() const { return last_struct; } __forceinline Position LastPosition() const { return last_struct.position; } //--- Events void OnTradeTransactionEvent(const MqlTradeTransaction& trans); // Should be executed in OnTradeTransaction void OnInitEvent(); // Should be executed in OnInit, at the end inline void OnNewDay(); // The function should be called every new day inline void OnNewWeek(); // The function should be called every new week inline void OnNewMonth(); // The function should be called every new month //--- Ticket logs inline void AddLogFlagTicket(uint8_t flags) { open_positions.AddLogFlags(flags); } inline void RemoveLogFlagTicket(uint8_t flags) { open_positions.RemoveLogFlags(flags); } //--- Info functions void PrintLastOrder(); void PrintLastPosition(); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccountStatus::CAccountStatus(void) : CManagerBase(true), account_daily_profit(0.00), account_weekly_profit(0.00), account_gross_profit(0.00), account_monthly_profit(0.00), init(false) { account_status_curr_profit = 0.00; account_status_positions_open = false; open_positions = NULL; open_positions = new CTicketsInfo(); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CAccountStatus::~CAccountStatus() { if(open_positions != NULL) delete open_positions; } //+------------------------------------------------------------------+ //| Function that executes every new day | //+------------------------------------------------------------------+ inline void CAccountStatus::OnNewDay(void) { this.last_day_time = iTime(_Symbol, PERIOD_D1, 0); this.account_daily_profit = 0.0; for(int i = 0; i < total; i++) items[i].OnNewDay(this.last_day_time); } //+------------------------------------------------------------------+ //| Function that executes every new month | //+------------------------------------------------------------------+ inline void CAccountStatus::OnNewMonth(void) { this.last_monthly_time = iTime(_Symbol, PERIOD_MN1, 0); this.account_monthly_profit = 0.0; for(int i = 0; i < total; i++) items[i].OnNewMonth(this.last_monthly_time); } //+------------------------------------------------------------------+ //| Function that executes weekly | //+------------------------------------------------------------------+ inline void CAccountStatus::OnNewWeek(void) { this.last_weekly_time = iTime(_Symbol, PERIOD_W1, 0); this.account_weekly_profit = 0.0; for(int i = 0; i < total; i++) items[i].OnNewWeek(this.last_weekly_time); } //+------------------------------------------------------------------+ //| Function that executes in OnInit | //+------------------------------------------------------------------+ void CAccountStatus::OnInitEvent(void) { //--- Initialize the profit collection variables this.last_day_time = iTime(_Symbol, PERIOD_D1, 0); this.last_weekly_time = iTime(_Symbol, PERIOD_W1, 0); this.last_monthly_time = iTime(_Symbol, PERIOD_MN1, 0); this.init = true; //--- Initialize the managers with the new profit UpdateProfitInit(); //--- ROnOpenClosePosition profit; profit.account_balance = AccountInfoDouble(ACCOUNT_BALANCE); profit.account_profit_diario = this.account_daily_profit; profit.account_profit_mensual = this.account_monthly_profit; profit.account_profit_semanal = this.account_weekly_profit; profit.account_profit_total = this.account_gross_profit; for(int i = 0; i < total; i++) items[i].OnNewProfit(profit); //--- Check if there are open trades const int pos_total = PositionsTotal(); //--- If there are open positions we add them if(pos_total > 0) { for(int i = pos_total - 1; i >= 0; i--) { const ulong position_ticket = PositionGetTicket(i); if(!PositionSelectByTicket(position_ticket)) continue; //--- last_trans_symbol = PositionGetString(POSITION_SYMBOL); //--- Position new_pos; new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); new_pos.ticket = position_ticket; new_pos.profit = GetTotalPositionProfitNoCurrent(position_ticket); new_pos.magic = (ulong)PositionGetInteger(POSITION_MAGIC); new_pos.tp = PositionGetDouble(POSITION_TP); new_pos.sl = PositionGetDouble(POSITION_SL); new_pos.open_time = (datetime)PositionGetInteger(POSITION_TIME); new_pos.open_price = PositionGetDouble(POSITION_PRICE_OPEN); new_pos.volume = PositionGetDouble(POSITION_VOLUME); open_positions.Add(new_pos); //--- profit.position = new_pos; for(int k = 0; k < total; k++) items[k].OnInitNewPos(profit); } } } //+------------------------------------------------------------------+ //| Function to update profits | //+------------------------------------------------------------------+ void CAccountStatus::UpdateProfit(const double &new_profit) { //--- if(!init) { LogFatalError("CAccountStatus.OnInit must be called before executing any CAccountStatus function", FUNCION_ACTUAL); Remover(); return; } //--- this.account_gross_profit += new_profit; this.account_daily_profit += new_profit; this.account_weekly_profit += new_profit; this.account_monthly_profit += new_profit; //--- if(IsInfoLogEnabled()) { FastLog(FUNCION_ACTUAL, INFO_TEXT, "New profits:"); Print("Daily profit: ", this.account_daily_profit); Print("Weekly profit: ", this.account_weekly_profit); Print("Monthly profit: ", this.account_monthly_profit); Print("Gross profit: ", this.account_gross_profit); } //--- for(int i = 0; i < total; i++) items[i].OnLossProfit(new_profit); } //+------------------------------------------------------------------+ void CAccountStatus::UpdateProfitInit() { //--- this.account_gross_profit = GetNetProfitSince(true, 0, D'1972.01.01 00:00'); this.account_daily_profit = GetNetProfitSince(true, 0, this.last_day_time); this.account_weekly_profit = GetNetProfitSince(true, 0, this.last_weekly_time); this.account_monthly_profit = GetNetProfitSince(true, 0, this.last_monthly_time); //--- if(IsInfoLogEnabled()) { FastLog(FUNCION_ACTUAL, INFO_TEXT, "New profits:"); Print("Daily profit: ", this.account_daily_profit); Print("Weekly profit: ", this.account_weekly_profit); Print("Monthly profit: ", this.account_monthly_profit); Print("Gross profit: ", this.account_gross_profit); } } //+------------------------------------------------------------------+ //| Function to execute in OnTick | //+------------------------------------------------------------------+ #define CAccountStatus_OnTickEvent account_status_curr_profit = AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE); //+------------------------------------------------------------------+ //| OnTradeTransaction event | //+------------------------------------------------------------------+ //--- Activa el evento de OnOrderAdd //#define CACCOUNT_STATUS_ACTIVE_ON_ORDER_ADD //--- Activa el evento de OnOrderUpdate //#define CACCOUNT_STATUS_ACTIVE_ON_ORDER_UPDATE //--- Activa el evento de OnOrderDelete //#define CACCOUNT_STATUS_ACTIVE_ON_ORDER_DELETE //--- Activa el evento de OnPositionModifed //#define CACCOUNT_STATUS_ACTIVE_ON_POSITION_MODIFIED //--- En los evenots de ordenes, su definicion activa que tamiben cuenten las ordenes de compra y venta //#define CACCOUNT_STATUS_COUNT_ORDERS_BUY_SELL //--- void CAccountStatus::OnTradeTransactionEvent(const MqlTradeTransaction &trans) { //--- switch(trans.type) { //--- case TRADE_TRANSACTION_ORDER_ADD: // 0 { #ifdef CACCOUNT_STATUS_ACTIVE_ON_ORDER_ADD #ifndef CACCOUNT_STATUS_COUNT_ORDERS_BUY_SELL if(!EsUnaOrderPendiente[trans.order_type]) return; #endif // CACCOUNT_STATUS_COUNT_ORDERS_BUY_SELL //--- if(OrderSelect(trans.order)) { //--- last_trans_symbol = trans.symbol; //--- last_order_struct.order_open_price = trans.price; last_order_struct.order_stop_loss = trans.price_sl; last_order_struct.order_take_profit = trans.price_tp; last_order_struct.order_magic = OrderGetInteger(ORDER_MAGIC); last_order_struct.order_ticket = trans.order; last_order_struct.order_time_expiration = trans.time_expiration; last_order_struct.order_reason = (ENUM_ORDER_REASON)OrderGetInteger(ORDER_REASON); last_order_struct.order_type = trans.order_type; last_order_struct.order_state = trans.order_state; last_order_struct.order_type_time = trans.time_type; //--- for(int i = 0; i < total; i++) items[i].OnOrderAdd(last_order_struct); } else { LogError(StringFormat("Error al seleccionar la orden %I64u", trans.order), FUNCION_ACTUAL); } #endif // CACCOUNT_STATUS_ACTIVE_ON_ORDER_ADD //--- break; } //--- case TRADE_TRANSACTION_ORDER_UPDATE: // 1 { #ifdef CACCOUNT_STATUS_ACTIVE_ON_ORDER_UPDATE #ifndef CACCOUNT_STATUS_COUNT_ORDERS_BUY_SELL if(!EsUnaOrderPendiente[trans.order_type]) return; #endif // CACCOUNT_STATUS_COUNT_ORDERS_BUY_SELL //--- if(OrderSelect(trans.order)) { //--- last_trans_symbol = trans.symbol; //--- last_order_struct.order_open_price = trans.price; last_order_struct.order_stop_loss = trans.price_sl; last_order_struct.order_take_profit = trans.price_tp; last_order_struct.order_magic = OrderGetInteger(ORDER_MAGIC); last_order_struct.order_ticket = trans.order; last_order_struct.order_time_expiration = trans.time_expiration; last_order_struct.order_reason = (ENUM_ORDER_REASON)OrderGetInteger(ORDER_REASON); last_order_struct.order_type = trans.order_type; last_order_struct.order_state = trans.order_state; last_order_struct.order_type_time = trans.time_type; //--- for(int i = 0; i < total; i++) items[i].OnOrderUpdate(last_order_struct); } else { LogError(StringFormat("Error al seleccionar la orden %I64u", trans.order), FUNCION_ACTUAL); } #endif // CACCOUNT_STATUS_ACTIVE_ON_ORDER_UPDATE //--- break; } //--- case TRADE_TRANSACTION_ORDER_DELETE: // 2 { //--- break; } //--- case TRADE_TRANSACTION_HISTORY_ADD: // 3 { #ifdef CACCOUNT_STATUS_ACTIVE_ON_ORDER_DELETE //--- if(EsUnaOrderPendiente[trans.order_type]) { if(HistoryOrderSelect(trans.order)) { // Se llama antes que historyadd //--- last_trans_symbol = trans.symbol; //--- last_order_struct.order_open_price = trans.price; last_order_struct.order_stop_loss = trans.price_sl; last_order_struct.order_take_profit = trans.price_tp; last_order_struct.order_magic = HistoryOrderGetInteger(trans.order, ORDER_MAGIC); last_order_struct.order_ticket = trans.order; last_order_struct.order_time_expiration = trans.time_expiration; last_order_struct.order_reason = (ENUM_ORDER_REASON)HistoryOrderGetInteger(trans.order, ORDER_REASON); last_order_struct.order_type = trans.order_type; last_order_struct.order_state = trans.order_state; last_order_struct.order_type_time = trans.time_type; //--- for(int i = 0; i < total; i++) items[i].OnOrderDelete(last_order_struct); } else { LogError(StringFormat("Error al seleccionar la orden %I64u", trans.order), FUNCION_ACTUAL); } } #endif // CACCOUNT_STATUS_ACTIVE_ON_ORDER_DELETE break; } //--- case TRADE_TRANSACTION_HISTORY_UPDATE: // 4 { break; } //--- case TRADE_TRANSACTION_HISTORY_DELETE: // 5 { break; } //--- case TRADE_TRANSACTION_DEAL_ADD: // 6 { //--- last_deal_type = trans.deal_type; last_deal_ticket = trans.deal; last_trans_symbol = trans.symbol; //--- if(last_deal_type == DEAL_TYPE_BUY || last_deal_type == DEAL_TYPE_SELL) { HistoryDealSelect(trans.deal); const ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(last_deal_ticket, DEAL_ENTRY); const ulong deal_magic = (ulong)HistoryDealGetInteger(last_deal_ticket, DEAL_MAGIC); const ulong position_ticket = trans.position; //--- const double deal_profit = HistoryDealGetDouble(last_deal_ticket, DEAL_PROFIT); // Get deal profit const double deal_commission = HistoryDealGetDouble(last_deal_ticket, DEAL_COMMISSION); // Get commission //--- if(PositionSelectByTicket(trans.position) && entry == DEAL_ENTRY_IN) { //--- LogInfo(StringFormat("Position %I64u opened with magic %I64u", position_ticket, deal_magic), FUNCION_ACTUAL); //--- Activate open positions flag account_status_positions_open = true; //--- Calculate the profit const double deal_net_profit = deal_profit + deal_commission; // Calculate net profit //--- Create a new position to add to the tickets Position new_pos; new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); new_pos.ticket = position_ticket; new_pos.magic = deal_magic; new_pos.profit = deal_net_profit; new_pos.open_time = (datetime)PositionGetInteger(POSITION_TIME); new_pos.open_price = trans.price; new_pos.sl = trans.price_sl; new_pos.tp = trans.price_tp; new_pos.volume = trans.volume; //--- UpdateProfit(deal_net_profit); //Update the profit //--- Add to positions array open_positions.Add(new_pos); //--- Set the new structure last_struct.position = new_pos; // Account last_struct.account_balance = AccountInfoDouble(ACCOUNT_BALANCE); last_struct.account_profit_diario = account_daily_profit; last_struct.account_profit_total = account_gross_profit; last_struct.account_profit_mensual = account_monthly_profit; last_struct.account_profit_semanal = account_weekly_profit; // Deal last_struct.deal_entry_type = entry; last_struct.deal_ticket = last_deal_ticket; last_struct.deal_profit = deal_net_profit; last_struct.deal_reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(last_deal_ticket, DEAL_REASON); // Extra last_struct.magic_number_closed = 0; //--- for(int i = 0; i < total; i++) items[i].OnOpenClosePosition(last_struct); //--- return; } if(entry != DEAL_ENTRY_OUT) break; //--- #define ACCOUNT_STATUS_MIN_VOL 0.00000001 //--- int16_t pos_idx; if(open_positions.GetByTicket(position_ticket, last_struct.position, pos_idx)) { // Print("Vol: ", DoubleToString(trans.volume)); //--- if((last_struct.position.volume - trans.volume) < ACCOUNT_STATUS_MIN_VOL) // Cierre completo { //--- open_positions.DeleteByIndex(pos_idx); //--- LogCaution(StringFormat("Position with ticket %I64u has been closed", position_ticket), FUNCION_ACTUAL); //--- Get the most recent profit const double deal_swap = HistoryDealGetDouble(last_deal_ticket, DEAL_SWAP); // Get swap fees const double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit //--- if(GetPositionsTotal() == 0) account_status_positions_open = false; //--- Update its profit last_struct.position.profit += deal_net_profit; //--- Update the general profit UpdateProfit(deal_net_profit); //--- Set the new structure // Ya se hace // Account last_struct.account_balance = AccountInfoDouble(ACCOUNT_BALANCE); last_struct.account_profit_diario = account_daily_profit; last_struct.account_profit_total = account_gross_profit; last_struct.account_profit_mensual = account_monthly_profit; last_struct.account_profit_semanal = account_weekly_profit; // Deal last_struct.deal_entry_type = entry; //market entry type (ENTRY_OUT) last_struct.deal_ticket = last_deal_ticket; //Closing deal ticket last_struct.deal_profit = deal_net_profit; //Net profit (excluding commissions) last_struct.deal_reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(last_deal_ticket, DEAL_REASON); // Extra last_struct.magic_number_closed = deal_magic; //Ticket with which the position was closed //--- Execute in the inheritors for(int i = 0; i < total; i++) items[i].OnOpenClosePosition(last_struct); //--- break; } else { //--- LogCaution(StringFormat("Possible partial closure of the position: %I64u", position_ticket), FUNCION_ACTUAL); //--- const double deal_swap = HistoryDealGetDouble(last_deal_ticket, DEAL_SWAP); // Get swap fees const double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit //--- open_positions.UpdatePositionPartial(deal_net_profit, trans.volume, pos_idx); //Update the profit of the position that partially closed //--- UpdateProfit(deal_net_profit); //--- break; } //--- break; } } else if(last_deal_type == DEAL_TYPE_BALANCE || last_deal_type == DEAL_TYPE_CHARGE) // Withdrawal or deposit { HistoryDealSelect(last_deal_ticket); const double deal_profit = HistoryDealGetDouble(last_deal_ticket, DEAL_PROFIT); // Get deal profit //--- if(fabs(deal_profit) < 0.0000000001) break; //--- for(int i = 0; i < total; i++) items[i].OnWithdrawalDeposit(deal_profit); //--- break; } //--- break; } //--- case TRADE_TRANSACTION_DEAL_UPDATE: // 7 { break; } //--- case TRADE_TRANSACTION_DEAL_DELETE: // 8 { break; } //--- case TRADE_TRANSACTION_POSITION: // 9 { #ifdef CACCOUNT_STATUS_ACTIVE_ON_POSITION_MODIFIED const ulong t = trans.position; if(open_positions.GetByTicket(t, last_struct.position)) { //--- last_trans_symbol = trans.symbol; //--- uint8_t f = 0; //--- Cambio tp const double new_tp = trans.price_tp; if(last_struct.position.tp != new_tp) { open_positions.UpdateTp(new_tp, t); last_struct.position.tp = new_tp; f |= ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_TP; } //--- Cambio sl const double new_sl = trans.price_sl; if(last_struct.position.sl != new_sl) { open_positions.UpdateSl(new_sl, t); last_struct.position.sl = new_sl; f |= ACCOUNTSTATUS_ON_POS_MODIFY_FLAG_CHANGE_SL; } //--- if(f) for(int i = 0; i < total; i++) items[i].OnPositionModify(last_struct.position, f); } #endif // CACCOUNT_STATUS_ACTIVE_ON_POSITION_MODIFIED break; } //--- case TRADE_TRANSACTION_REQUEST: // 10 { break; } //--- default: break; } } //+------------------------------------------------------------------+ //| Print last order values | //+------------------------------------------------------------------+ void CAccountStatus::PrintLastOrder(void) { Print("--- Last Order ---"); Print("Ticket: ", last_order_struct.order_ticket); Print("Type: ", EnumToString(last_order_struct.order_type)); Print("State: ", EnumToString(last_order_struct.order_state)); Print("Magic: ", last_order_struct.order_magic); Print("Open Price: ", last_order_struct.order_open_price); Print("Stop Loss: ", last_order_struct.order_stop_loss); Print("Take Profit: ", last_order_struct.order_take_profit); Print("Expiration: ", last_order_struct.order_time_expiration); Print("Time Type: ", EnumToString(last_order_struct.order_type_time)); Print("Reason: ", EnumToString(last_order_struct.order_reason)); } //+------------------------------------------------------------------+ //| Print last position values | //+------------------------------------------------------------------+ void CAccountStatus::PrintLastPosition(void) { Print("--- Last Position ---"); Print("Ticket: ", last_struct.position.ticket); Print("Type: ", EnumToString(last_struct.position.type)); Print("Magic: ", last_struct.position.magic); Print("Open Price: ", last_struct.position.open_price); Print("Open Time: ", last_struct.position.open_time); Print("Stop Loss: ", last_struct.position.sl); Print("Take Profit: ", last_struct.position.tp); Print("Profit: ", last_struct.position.profit); } /* ADVERTENCIA: Si se añaden items a esta clase, esta las eliminara por defecto, NO propagara sus logs.. solko funcioan como container WARNING: If items are added to this class, it will delete them by default, it will NOT propagate their logs. They only function as containers. */ CAccountStatus account_status; #endif // MQLARTICLES_RM_ACCOUNTSTATUS_MQH //+------------------------------------------------------------------+ //+------------------------------------------------------------------+