#include #include #include #include #include enum deal_result { NO_VALUE=0, WIN=1, LOSS }; enum ENUM_HISTORY_SORT { HISTORY_SORT_OPENTIME, // Open time HISTORY_SORT_CLOSETIME // Close time }; class CHistory { private: CDealInfo m_deal; CFileSaving m_FileSave; CArrayDouble m_profit_data; CArrayDouble m_balance_data; CArrayDouble m_sharp_balance; CArrayDouble m_balance_line; double m_max_lot; double m_initial_deposit; double m_withdrawal; double m_volume; double m_swap; double m_commission; double m_profit; double m_gross_profit; double m_gross_loss; double m_profit_factor; double m_expected_payoff; double m_balance; double m_maxdraw_MDD; double m_maxdraw_percent; // double m_balance_min; // double m_balance_dd; // double m_balance_dd_percent; // double m_balance_dd_relative; // double m_balance_dd_relative_percent; // double m_balance_dd_absolute; double m_min_peak; double m_max_peak; long arr_Positions[]; long m_type; long m_magic; int m_deals; int m_trades; int m_profit_trades; double m_profit_trades_percent; int m_loss_trades; string m_symbol; string m_comment; string m_openTime_str; string m_closeTime_str; datetime m_openTime_dt; datetime m_closeTime_dt; string m_mqlName; // only to save file CSV datetime c_opTime; datetime c_clTime; string c_type; long c_magic; double c_volume; double c_opPrice; double c_sl; double c_tp; double c_clPrice; double c_commission; double c_swap; double c_profit; double c_profitPoint; string c_duration; string c_opComment; string c_clComment; public: CHistory(void); ~CHistory(void); bool ExportHistoryPositions(const string Cmment); double MaxLots() {return(m_max_lot);} // double InitialDeposit() {return(m_initial_deposit);} // double Withdrawal() {return(m_withdrawal);} double Profit() {return(m_profit);}; double GrossProfit() {return(m_gross_profit);} double GrossLoss() {return(m_gross_loss);} double MDD() {return(m_maxdraw_MDD);} double ProfitFactor() {return(m_profit_factor);} int Trades() {return(m_trades);} int ProfitTrades() {return(m_profit_trades);}; double ExpectedPayoff() {return(m_expected_payoff);} string OpenTime() {return(m_openTime_str);} string CloseTime() {return(m_closeTime_str);} double Percent(double value,double divider); int CountRowsInCSV(); bool AppendDataToCSV(); bool CheckIsFileBot() {return(FileIsExist(m_mqlName + " EA" + "\\" + AccountInfoString(ACCOUNT_COMPANY) +"\\"+ "Trade_Transaction.csv"));} string AnalyzePathFile() {return(m_mqlName + " EA" + "\\" + AccountInfoString(ACCOUNT_COMPANY) +"\\"+"Trade_Transaction.csv");} bool Calculate(const string Symbl,const long Mgc,datetime time_start,datetime time_end); bool HistorySelectByPositionProcess(const long position_id, const bool saveCsv); string GetExpertNameWithSum(); }; CHistory::CHistory(void) { m_mqlName = MQLInfoString(MQL_PROGRAM_NAME); c_magic = 1; } CHistory::~CHistory(void) { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CHistory::ExportHistoryPositions(const string Cmment) { datetime time_start = D'2024.12.24'; int handle= FileOpen(AnalyzePathFile(),FILE_WRITE|FILE_CSV|FILE_ANSI,',');// |FILE_ANSI,',' if(handle == INVALID_HANDLE) { ResetLastError(); string txt = TRANSLATION_FILED_TO_OPEN_FILE_WRITING + AnalyzePathFile() + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError()); m_FileSave.IniFileSaveRun(txt,false); m_FileSave.IniFileSave(__FUNCTION__+":: "+ txt, false); return false; } FileWrite(handle, "Open Time", "Symbol", "Magic Number", "Type", "Volume", "Open Price", "S/L", "T/P", "Close Price", "Close Time", "Commission", "Swap", "Profit", "Profit Points", "Duration", "Open Comment", "Close Comment"); ulong s_time=GetMicrosecondCount(); double Balanc=0; if(!HistorySelect(time_start,TimeCurrent())) { ResetLastError(); string txt = " ⚠ "+ TRANSLATION_HISTORY_GET_TRADE + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError()); m_FileSave.IniFileSave(__FUNCTION__+"::"+txt,false); m_FileSave.IniFileSaveRun(txt,false); return(false); } int deals = HistoryDealsTotal(); int sort_by = HISTORY_SORT_CLOSETIME; if(sort_by==HISTORY_SORT_OPENTIME) { for(int i = 0; i < deals && !IsStopped(); i++) { if(m_deal.SelectByIndex(i)) if(m_deal.Entry()==DEAL_ENTRY_IN) { if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL) { //--- save position ids to the array long position_id=m_deal.PositionId(); int arr_size=ArraySize(arr_Positions); ArrayResize(arr_Positions,arr_size+1,100); arr_Positions[arr_size]=position_id; } } } } //--- if(sort_by==HISTORY_SORT_CLOSETIME) { //--- define a hashset to collect position IDs (with no duplicates) CHashSethashset; //--- handle the case when a position has multiple deals out. for(int i = deals-1; i >= 0 && !IsStopped(); i--) if(m_deal.SelectByIndex(i)) { if(m_deal.Entry() == DEAL_ENTRY_IN && StringFind(m_deal.Comment(), Cmment) != -1) { if(m_deal.DealType() == DEAL_TYPE_BUY || m_deal.DealType() == DEAL_TYPE_SELL) { hashset.Add(m_deal.PositionId()); } } // if(m_deal.Entry()==DEAL_ENTRY_OUT || m_deal.Entry()==DEAL_ENTRY_OUT_BY) // if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL) // hashset.Add(m_deal.PositionId()); } //--- copy the elements from the set to a compatible one-dimensional array hashset.CopyTo(arr_Positions,0); //ArrayReverse(arr_Positions); ArraySetAsSeries(arr_Positions,true); } //--- now process the list of positions stored in the array int positions=ArraySize(arr_Positions); for(int i=0; i1) { //--- now process the list of received deals for the specified position deals=HistoryDealsTotal(); for(int j=0; j0.00001) continue; //--- Closed position is reconstructed StringTrimLeft(pos_close_comment); StringTrimRight(pos_close_comment); //--- sums Balanc+=pos_profit+pos_swap+pos_commission; //--- SymbolSelect(pos_symbol,true); int digits=(int)SymbolInfoInteger(pos_symbol,SYMBOL_DIGITS); double point=SymbolInfoDouble(pos_symbol,SYMBOL_POINT); FileWrite(handle, (string)pos_open_time, pos_symbol, pos_magic, (pos_type==DEAL_TYPE_BUY) ? "buy" : (pos_type==DEAL_TYPE_SELL) ? "sell" : "other", DoubleToString(pos_close_volume,2), DoubleToString(pos_open_price,digits), (pos_sl ? DoubleToString(pos_sl,digits) : ""), (pos_tp ? DoubleToString(pos_tp,digits) : ""), DoubleToString(pos_close_price,(deals==2 ? digits : digits+3)), (string)pos_close_time, DoubleToString(pos_commission,2), DoubleToString(pos_swap,2), DoubleToString(pos_profit,2), MathRound((pos_type == DEAL_TYPE_BUY ? pos_close_price - pos_open_price : pos_open_price - pos_close_price) / point), // // (sort_by == HISTORY_SORT_CLOSETIME ? DoubleToString(Balanc,2) : ""), // TimeElapsedToString(pos_close_time - pos_open_time), pos_open_comment, pos_close_comment ); } } FileClose(handle); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string TimeElapsedToString(const datetime pElapsedSeconds) { const long days = pElapsedSeconds / PeriodSeconds(PERIOD_D1); return((days ? (string)days + "d " : "") + TimeToString(pElapsedSeconds,TIME_SECONDS)); } bool CHistory::Calculate(const string Symbl, const long Mgc,datetime time_start,datetime time_end) { deal_result result = NO_VALUE; int sort_by = HISTORY_SORT_OPENTIME; datetime first_entry_time = 0; datetime overall_first_entry_time = 0; datetime open_times[]; m_profit=0; m_gross_profit=0; m_gross_loss=0; if(!HistorySelect(time_start,time_end)) { ResetLastError(); string txt = " ⚠ "+ TRANSLATION_HISTORY_GET_TRADE + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError()); m_FileSave.IniFileSave(__FUNCTION__+"::"+txt,false); m_FileSave.IniFileSaveRun(txt,false); return(false); } int deals_total = HistoryDealsTotal(); if(sort_by==HISTORY_SORT_OPENTIME) { for(int i = 0; i < deals_total && !IsStopped(); i++) { if(m_deal.SelectByIndex(i)) if(m_deal.Entry()==DEAL_ENTRY_IN) { if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL) { if(m_deal.Symbol() != Symbl) continue; if(m_deal.Magic() != Mgc) continue; //--- save position ids to the array long position_id=m_deal.PositionId(); int arr_size=ArraySize(arr_Positions); if(ArrayResize(arr_Positions,arr_size+1)==arr_size+1) arr_Positions[arr_size]=position_id; m_trades++; if (first_entry_time == 0 || m_deal.Time() < first_entry_time) {first_entry_time = m_deal.Time();} } } } } if(sort_by==HISTORY_SORT_CLOSETIME) { //--- define a hashset to collect position IDs (with no duplicates) CHashSethashset; //--- handle the case when a position has multiple deals out. for(int i = deals_total-1; i >= 0 && !IsStopped(); i--) if(m_deal.SelectByIndex(i)) if(m_deal.Entry()==DEAL_ENTRY_OUT || m_deal.Entry()==DEAL_ENTRY_OUT_BY) if(m_deal.DealType()==DEAL_TYPE_BUY || m_deal.DealType()==DEAL_TYPE_SELL) { if(m_deal.Symbol() != Symbl) continue; if(m_deal.Magic() != Mgc) continue; hashset.Add(m_deal.PositionId()); } //--- copy the elements from the set to a compatible one-dimensional array hashset.CopyTo(arr_Positions,0); //ArrayReverse(arr_Positions); ArraySetAsSeries(arr_Positions,true); } deals_total = ArraySize(arr_Positions); for(int i=0; i 0) { if (overall_first_entry_time == 0 || first_entry_time < overall_first_entry_time) { overall_first_entry_time = first_entry_time; m_openTime_dt = overall_first_entry_time; m_openTime_str = TimeToString(overall_first_entry_time, TIME_DATE); } } return true; } //+------------------------------------------------------------------+ //| Select history of orders and deals by position ID and | //| prints a list of orders and deals for the position in the journal| //+------------------------------------------------------------------+ bool CHistory::HistorySelectByPositionProcess(const long position_id, const bool saveCsv) { double profit=0, profit_close=0; m_profit_factor = 0; c_commission = 0.0; datetime last_exit_time = 0; datetime overall_last_exit_time = 0; if(!HistorySelectByPosition(position_id)) { ResetLastError(); string txt = " ⚠ "+ TRANSLATION_HISTORY_SELECTPOSITION_ERR + "(#"+IntegerToString(position_id,0)+") " + TRANSLATION_HISTORY_SELECTPOSITION_FAILED + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError()); m_FileSave.IniFileSave(__FUNCTION__+"::"+txt,false); m_FileSave.IniFileSaveRun(txt,false); return(false); } int orders_total=HistoryOrdersTotal(); for(int i=0; i= 0.0) {m_gross_profit += profit;} else {m_gross_loss += profit;} if(dealvolume > m_max_lot) {m_max_lot = dealvolume;} if (last_exit_time < dealtimeClose) {last_exit_time = dealtimeClose;} } if(deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_OUT_BY || deal_entry == DEAL_ENTRY_IN || deal_entry == DEAL_ENTRY_INOUT) { profit_close = NormalizeDouble(dealprofit + dealswap + dealcomision,2); m_balance += profit_close; if(profit >= 0.0) { m_profit_trades++; } } } if (last_exit_time > 0) { if (overall_last_exit_time == 0 || last_exit_time > overall_last_exit_time) { overall_last_exit_time = last_exit_time; m_closeTime_dt = overall_last_exit_time; m_closeTime_str = TimeToString(overall_last_exit_time, TIME_DATE); } } if (m_balance > max_balance) { max_balance = m_balance; } drawdown = NormalizeDouble(max_balance - m_balance, 2); if(drawdown > m_maxdraw_MDD) {m_maxdraw_MDD = drawdown;} // Print("balance ",m_balance," | ",max_balance," | ",drawdown," | ",m_maxdraw_MDD); m_profit = m_gross_profit+m_gross_loss; if(MathAbs(m_gross_loss) > FLT_EPSILON) {m_profit_factor = MathAbs(m_gross_profit/m_gross_loss);} if(m_trades > 0) { m_expected_payoff=m_profit/m_trades; m_profit_trades_percent = (double)m_profit_trades / m_trades * 100; } // Print("c_optime: ", TimeToString(c_opTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS), // " c_type: ", c_type, // " c_volume: ", c_volume, // " c_opPrice: ", c_opPrice, // " c_sl: ", c_sl, // " c_tp: ", c_tp, // " c_clPrice: ", c_clPrice,"\n", // " c_clTime: ", TimeToString(c_clTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS), // " commis: " ,c_commission, // " swap: ", c_swap, // " profit: ", c_profit,"\n", // " profit point: ", c_profitPoint, // " duration: ", c_duration, // " opcoment: ", c_opComment," cclcom ",c_clComment); if(saveCsv) {AppendDataToCSV();} return true; } double CHistory::Percent(double value,double divider) { if(MathAbs(value)<=FLT_EPSILON) return(0); return(100*value/divider); } bool CHistory::AppendDataToCSV() { int retryCount = 0; // Initialize the retry counter const int maxRetries = 3; // Maximum allowed retries while (retryCount < maxRetries) { int handle = FileOpen(AnalyzePathFile(), FILE_READ | FILE_WRITE | FILE_CSV|FILE_ANSI,','); // | FILE_ANSI, ',' if (handle != INVALID_HANDLE) { // File opened successfully, append data FileSeek(handle, 0, SEEK_END); FileWrite(handle, c_opTime, Symbol(), c_magic, c_type, c_volume, c_opPrice, c_sl, c_tp, c_clPrice, c_clTime, c_commission, c_swap, c_profit, c_profitPoint, c_duration, c_opComment, c_clComment); FileClose(handle); return true; // Successfully written to the file } else { // Handle the file open error retryCount++; ResetLastError(); string errorDetails = TRANSLATION_FILED_TO_OPEN_FILE_WRITING + AnalyzePathFile() + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError()); m_FileSave.IniFileSaveRun(errorDetails, false); m_FileSave.IniFileSave(__FUNCTION__ + ":: " + errorDetails, false); // Prepare warning message for the user string caption = TRANSLATION_FILED_TO_OPEN_FILE_WRITING2; string warning = StringFormat( "%s\n%s\n\n%s\n%s (%d/%d)%s%d%s", TRANSLATION_SAVE_ERR_MESSAG_FILE, AnalyzePathFile(), TRANSLATION_SAVE_ERR_MESSAG_FILE_OPEN, TRANSLATION_SAVE_ERR_MESSAG_ATTEMPT, retryCount, maxRetries, TRANSLATION_SAVE_ERR_MESSAG_WILL, maxRetries, TRANSLATION_SAVE_ERR_MESSAG_ATTEMPTS ); // Show the warning message int ret = MessageBox(warning, caption, MB_OK | MB_ICONWARNING); if (ret == IDOK) { ExportHistoryPositions("Break EA 651"); // GetExpertNameWithSum() // continue; // User clicked OK, retry file access } } } // Final warning after maximum retries exceeded string finalWarning = StringFormat( "%s\n%s\n\n%s%d%s\n%s", TRANSLATION_SAVE_ERR_MESSAG_FILE, AnalyzePathFile(), TRANSLATION_SAVE_ERR_MESSAG_AFFTER, maxRetries, TRANSLATION_SAVE_ERR_MESSAG_ATTEMPTS, TRANSLATION_SAVE_ERR_MESSAG_STOP ); MessageBox(finalWarning, "Error", MB_OK | MB_ICONERROR); ExpertRemove(); // Stop the expert after exceeding retries return false; } string CHistory::GetExpertNameWithSum() { string result; string expName = MQLInfoString(MQL_PROGRAM_NAME); int totalSum = 0; // Loop through each character in the expert name for (int i = 0; i < StringLen(expName); i++) { int charValue = StringGetCharacter(expName, i); totalSum += charValue; } result = expName +" EA_"+ IntegerToString(totalSum); return result; } string baseSymbol = ""; void GetMatchingSymbols(string &matchingSymbols[]) { string currentSymbol = Symbol(); // Get the current chart symbol if (baseSymbol == "") { baseSymbol = currentSymbol; // Set base symbol globally ArrayResize(matchingSymbols, 1); matchingSymbols[0] = currentSymbol; // Store the first matching symbol Print("Base symbol initialized: ", baseSymbol); return; // Return early with the base symbol } int minLength = MathMin(StringLen(baseSymbol), StringLen(currentSymbol)); int commonLength = 0; for (int i = 0; i < minLength; i++) { if (StringGetCharacter(baseSymbol, i) == StringGetCharacter(currentSymbol, i)) commonLength++; else break; } string baseCore = StringSubstr(baseSymbol, 0, commonLength); string currentCore = StringSubstr(currentSymbol, 0, commonLength); if (baseCore != currentCore) { baseSymbol = currentSymbol; ArrayResize(matchingSymbols, 1); matchingSymbols[0] = currentSymbol; Print("Symbol change detected: Previous Base = ", baseSymbol, ", New Core = ", currentCore); } else { ArrayResize(matchingSymbols, ArraySize(matchingSymbols) + 1); matchingSymbols[ArraySize(matchingSymbols) - 1] = currentSymbol; } } // Function to count rows in a CSV file int CHistory::CountRowsInCSV() { int row_count = 0; string filename = AnalyzePathFile(); int file_handle = FileOpen(filename, FILE_READ | FILE_CSV | FILE_ANSI); if (file_handle == INVALID_HANDLE) { ResetLastError(); string txt = " ⚠ "+ TRANSLATION_FILED_TO_OPEN_FILE_WRITING3 + AnalyzePathFile() + TRANSLATION_MESSAGE_ERROR + IntegerToString(GetLastError()); m_FileSave.IniFileSave(__FUNCTION__+"::"+txt,false); m_FileSave.IniFileSaveRun(txt,false); return(-1); } // Read the file line by line while (!FileIsEnding(file_handle)) // Loop until the end of the file { string firstColumn = FileReadString(file_handle); // Read the first column if (firstColumn != "") // If the first column is not empty, increment the row count { row_count++; } // Skip the rest of the columns in the current row while (!FileIsLineEnding(file_handle) && !FileIsEnding(file_handle)) { FileReadString(file_handle); // Read and discard the remaining columns } } FileClose(file_handle); // Close the file return row_count - 1; } //--- global variable double max_balance = -DBL_MAX; double drawdown = 0.0;