//+------------------------------------------------------------------+ //| 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 ACCOUNT_STATUS_BASES_MQH #define ACCOUNT_STATUS_BASES_MQH #include "RM_Functions.mqh" #include //+------------------------------------------------------------------+ //| 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 //--- Most recent structure of the opened or closed position ROnOpenClosePosition last_struct; //--- Function to update profits void UpdateProfit(double new_profit, bool flag_init); public: CAccountStatus(); //--- Getter inline Position GetPosition(ulong ticket) { return open_positions.GetByTicket(ticket); } // Get Position by ticket inline int16_t GetPositionsTotal() const { return (int16_t)this.open_positions.Size(); } // Get total number of open positions inline 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; } //--- 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); } }; //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| 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 UpdateProfit(0, true); 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--) { ulong position_ticket = PositionGetTicket(i); if(!PositionSelectByTicket(position_ticket)) continue; 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.first_tp = PositionGetDouble(POSITION_TP); new_pos.firt_sl = PositionGetDouble(POSITION_SL); new_pos.open_time = (datetime)PositionGetInteger(POSITION_TIME); new_pos.open_price = PositionGetDouble(POSITION_PRICE_OPEN); 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(double new_profit, bool flag_init) { if(!flag_init) { 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(!init) { LogFatalError("CAccountStatus.OnInit must be called before executing any CAccountStatus function", FUNCION_ACTUAL); Remover(); } } else { 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 \ if(account_status_positions_open) \ { \ account_status_curr_profit = AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE); \ } //+------------------------------------------------------------------+ //| OnTradeTransaction event | //+------------------------------------------------------------------+ //#define CACCOUNT_STATUS_ACTIVE_ORDER_EVENT //--- void CAccountStatus::OnTradeTransactionEvent(const MqlTradeTransaction &trans) { if(trans.type == TRADE_TRANSACTION_DEAL_ADD) { const ENUM_DEAL_TYPE deal_type = trans.deal_type; if(deal_type == DEAL_TYPE_BUY || deal_type == DEAL_TYPE_SELL) { HistoryDealSelect(trans.deal); const ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY); const ulong deal_magic = (ulong)HistoryDealGetInteger(trans.deal, DEAL_MAGIC); const bool is_select = PositionSelectByTicket(trans.position); //--- const double deal_profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT); // Get deal profit const double deal_commission = HistoryDealGetDouble(trans.deal, DEAL_COMMISSION); // Get commission //--- if(is_select && entry == DEAL_ENTRY_IN) { //--- LogInfo(StringFormat("Position %I64u opened with magic %I64u", trans.position, 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 = trans.position; new_pos.magic = deal_magic; new_pos.profit = deal_net_profit; new_pos.open_time = (datetime)PositionGetInteger(POSITION_TIME); new_pos.open_price = PositionGetDouble(POSITION_PRICE_OPEN); new_pos.firt_sl = PositionGetDouble(POSITION_SL); new_pos.first_tp = PositionGetDouble(POSITION_TP); //--- UpdateProfit(deal_net_profit, false); //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 = trans.deal; last_struct.deal_profit = deal_net_profit; last_struct.deal_reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal, 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) return; if(!is_select) { Position pos_delete; //Position that was deleted if(open_positions.DeleteByTicket(trans.position, pos_delete)) { LogCaution(StringFormat("Position with ticket %I64u has been closed", trans.position), FUNCION_ACTUAL); //--- Get the most recent profit const double deal_swap = HistoryDealGetDouble(trans.deal, 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 pos_delete.profit += deal_net_profit; //--- Update the general profit UpdateProfit(deal_net_profit, false); //--- Set the new structure last_struct.position = pos_delete; // 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 = trans.deal; //Closing deal ticket last_struct.deal_profit = deal_net_profit; //Net profit (excluding commissions) last_struct.deal_reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal, 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); } } else { LogCaution(StringFormat("Possible partial closure of the position: %I64u", trans.position), FUNCION_ACTUAL); //--- const double deal_swap = HistoryDealGetDouble(trans.deal, DEAL_SWAP); // Get swap fees const double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit //--- open_positions.UpdateProfit(deal_net_profit, trans.position); //Update the profit of the position that partially closed UpdateProfit(deal_net_profit, false); } } else if(deal_type == DEAL_TYPE_BALANCE || deal_type == DEAL_TYPE_CHARGE) // Withdrawal or deposit { HistoryDealSelect(trans.deal); const double deal_profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT); // Get deal profit if(fabs(deal_profit) < 0.0000000001) return; for(int i = 0; i < total; i++) items[i].OnWithdrawalDeposit(deal_profit); } return; } #ifdef CACCOUNT_STATUS_ACTIVE_ORDER_EVENT else if(trans.type == TRADE_TRANSACTION_ORDER_DELETE) { HistoryOrderSelect(trans.order); ROnOrderDelete new_order; new_order.order_ticket = trans.order; new_order.order_type = trans.order_type; new_order.order_state = trans.order_state; new_order.order_magic = HistoryOrderGetInteger(trans.order, ORDER_MAGIC); for(int i = 0; i < total; i++) items[i].OnOrderDelete(new_order); return; } #endif } /* 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 //+------------------------------------------------------------------+