Files
Bell-PriceActionWithEma-EA/Include/RsiMom/TradeJournal.mqh
T
2026-05-23 22:27:48 +07:00

412 lines
13 KiB
Plaintext

//+------------------------------------------------------------------+
//| TradeJournal.mqh — CSV lệnh đóng + tổng hợp tháng (RsiMomentumEA)|
//+------------------------------------------------------------------+
#ifndef RSI_MOM_TRADE_JOURNAL_MQH
#define RSI_MOM_TRADE_JOURNAL_MQH
struct RsiMomMonthRow
{
string ym;
int trades;
int wins;
int losses;
double netProfit;
double grossProfit;
double grossLoss;
int slCount;
int tpCount;
int otherCount;
};
int g_rsiMomJournalHandle = INVALID_HANDLE;
RsiMomMonthRow g_rsiMomMonths[];
int g_rsiMomMonthCount = 0;
string RsiMomJournal_TradesPath(const string sym, const ENUM_TIMEFRAMES tf)
{
return StringFormat("RsiMomEA\\journal_%s_%s.csv", sym, EnumToString(tf));
}
string RsiMomJournal_SummaryPath(const string sym, const ENUM_TIMEFRAMES tf)
{
return StringFormat("RsiMomEA\\summary_%s_%s.csv", sym, EnumToString(tf));
}
int RsiMomJournal_FindMonth(const string ym)
{
for(int i = 0; i < g_rsiMomMonthCount; i++)
{
if(g_rsiMomMonths[i].ym == ym)
return i;
}
return -1;
}
void RsiMomJournal_AddMonth(const string ym, const double profit, const string exitTag)
{
int idx = RsiMomJournal_FindMonth(ym);
if(idx < 0)
{
idx = g_rsiMomMonthCount;
g_rsiMomMonthCount++;
ArrayResize(g_rsiMomMonths, g_rsiMomMonthCount);
g_rsiMomMonths[idx].ym = ym;
g_rsiMomMonths[idx].trades = 0;
g_rsiMomMonths[idx].wins = 0;
g_rsiMomMonths[idx].losses = 0;
g_rsiMomMonths[idx].netProfit = 0.0;
g_rsiMomMonths[idx].grossProfit = 0.0;
g_rsiMomMonths[idx].grossLoss = 0.0;
g_rsiMomMonths[idx].slCount = 0;
g_rsiMomMonths[idx].tpCount = 0;
g_rsiMomMonths[idx].otherCount = 0;
}
RsiMomMonthRow r = g_rsiMomMonths[idx];
r.trades++;
r.netProfit += profit;
if(profit > 0.0)
{
r.wins++;
r.grossProfit += profit;
}
else if(profit < 0.0)
{
r.losses++;
r.grossLoss += profit;
}
if(exitTag == "SL")
r.slCount++;
else if(exitTag == "TP")
r.tpCount++;
else
r.otherCount++;
g_rsiMomMonths[idx] = r;
}
bool RsiMomJournal_DeleteIfExists(const string path)
{
if(!FileIsExist(path, FILE_COMMON))
return false;
return FileDelete(path, FILE_COMMON);
}
bool RsiMomJournal_ResetFiles(const string sym, const ENUM_TIMEFRAMES tf)
{
RsiMomJournal_CloseFiles();
RsiMomJournal_DeleteIfExists(RsiMomJournal_TradesPath(sym, tf));
RsiMomJournal_DeleteIfExists(RsiMomJournal_SummaryPath(sym, tf));
return true;
}
bool RsiMomJournal_OpenTrades(const string sym, const ENUM_TIMEFRAMES tf)
{
if(g_rsiMomJournalHandle != INVALID_HANDLE)
return true;
FolderCreate("RsiMomEA", FILE_COMMON);
const string path = RsiMomJournal_TradesPath(sym, tf);
const bool exists = FileIsExist(path, FILE_COMMON);
g_rsiMomJournalHandle = FileOpen(path, FILE_READ | FILE_WRITE | FILE_CSV | FILE_COMMON | FILE_ANSI, ',');
if(g_rsiMomJournalHandle == INVALID_HANDLE)
{
Print("[RsiMomEA] Journal: không mở file ", path, " err=", GetLastError());
return false;
}
if(!exists || FileSize(g_rsiMomJournalHandle) == 0)
{
FileWrite(g_rsiMomJournalHandle,
"close_time", "year_month", "profit", "dir", "exit",
"entry_time", "entry_price", "sl", "tp", "volume",
"risk_pts", "profit_r",
"rsi", "wma45", "ema200", "close_entry", "above_ema200",
"atr", "atr_ratio", "atr_exp_ok",
"session_ok", "spread_pts",
"symbol", "timeframe", "magic");
}
else
{
FileSeek(g_rsiMomJournalHandle, 0, SEEK_END);
}
return true;
}
void RsiMomJournal_CloseFiles()
{
if(g_rsiMomJournalHandle != INVALID_HANDLE)
{
FileClose(g_rsiMomJournalHandle);
g_rsiMomJournalHandle = INVALID_HANDLE;
}
}
bool RsiMomJournal_Copy1(const int handle, const int shift, double &v)
{
v = 0.0;
if(handle == INVALID_HANDLE || shift < 0)
return false;
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(handle, 0, shift, 1, buf) < 1)
return false;
v = buf[0];
return true;
}
bool RsiMomJournal_AtrRatioAt(const int hAtr, const int shift,
const int cmpBars, const double minRatio,
double &atrNow, double &ratio, bool &expOk)
{
atrNow = 0.0;
ratio = 0.0;
expOk = false;
double atrRef = 0.0;
if(!RsiMomJournal_Copy1(hAtr, shift, atrNow) || !RsiMomJournal_Copy1(hAtr, shift + cmpBars, atrRef))
return false;
if(atrRef <= 0.0)
return false;
ratio = atrNow / atrRef;
expOk = (ratio >= MathMax(1.0, minRatio) - 1e-8);
return true;
}
string RsiMomJournal_ExitTag(const long dealReason, const double profit)
{
if(dealReason == DEAL_REASON_SL)
return "SL";
if(dealReason == DEAL_REASON_TP)
return "TP";
if(profit >= 0.0)
return "WIN";
return "LOSS";
}
bool RsiMomJournal_FindEntryDeal(const ulong positionId,
ulong &entryDeal,
datetime &entryTime,
long &posType,
double &entryPrice,
double &sl,
double &tp,
double &volume)
{
entryDeal = 0;
entryTime = 0;
posType = 0;
entryPrice = sl = tp = volume = 0.0;
if(positionId == 0 || !HistorySelectByPosition(positionId))
return false;
const int total = HistoryDealsTotal();
for(int i = 0; i < total; i++)
{
const ulong d = HistoryDealGetTicket(i);
if(d == 0 || !HistoryDealSelect(d))
continue;
if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_IN)
continue;
entryDeal = d;
entryTime = (datetime)HistoryDealGetInteger(d, DEAL_TIME);
posType = HistoryDealGetInteger(d, DEAL_TYPE);
entryPrice = HistoryDealGetDouble(d, DEAL_PRICE);
volume = HistoryDealGetDouble(d, DEAL_VOLUME);
const ulong orderTicket = (ulong)HistoryDealGetInteger(d, DEAL_ORDER);
if(orderTicket > 0 && HistoryOrderSelect(orderTicket))
{
sl = HistoryOrderGetDouble(orderTicket, ORDER_SL);
tp = HistoryOrderGetDouble(orderTicket, ORDER_TP);
}
return true;
}
return false;
}
void RsiMomJournal_WriteSummary(const string sym, const ENUM_TIMEFRAMES tf)
{
if(g_rsiMomMonthCount <= 0)
return;
const string path = RsiMomJournal_SummaryPath(sym, tf);
const int h = FileOpen(path, FILE_WRITE | FILE_CSV | FILE_COMMON | FILE_ANSI, ',');
if(h == INVALID_HANDLE)
{
Print("[RsiMomEA] Summary CSV lỗi ", path, " err=", GetLastError());
return;
}
FileWrite(h,
"year_month", "trades", "wins", "losses", "winrate_pct",
"net_profit", "gross_profit", "gross_loss", "avg_profit",
"sl_count", "tp_count", "other_count");
for(int i = 0; i < g_rsiMomMonthCount; i++)
{
const RsiMomMonthRow r = g_rsiMomMonths[i];
double winrate = 0.0;
if(r.trades > 0)
winrate = 100.0 * (double)r.wins / (double)r.trades;
const double avg = (r.trades > 0) ? r.netProfit / (double)r.trades : 0.0;
FileWrite(h,
r.ym,
IntegerToString(r.trades),
IntegerToString(r.wins),
IntegerToString(r.losses),
DoubleToString(winrate, 2),
DoubleToString(r.netProfit, 2),
DoubleToString(r.grossProfit, 2),
DoubleToString(r.grossLoss, 2),
DoubleToString(avg, 2),
IntegerToString(r.slCount),
IntegerToString(r.tpCount),
IntegerToString(r.otherCount));
}
FileClose(h);
Print("[RsiMomEA] Summary CSV → ", path, " (FILE_COMMON, ", g_rsiMomMonthCount, " tháng)");
}
void RsiMomJournal_ResetMonths()
{
ArrayResize(g_rsiMomMonths, 0);
g_rsiMomMonthCount = 0;
}
void RsiMomJournal_OnDeinit(const string sym, const ENUM_TIMEFRAMES tf)
{
RsiMomJournal_WriteSummary(sym, tf);
RsiMomJournal_CloseFiles();
}
void RsiMomJournal_RecordClosedDeal(const ulong dealOut,
const string sym,
const ENUM_TIMEFRAMES tf,
const ulong magic,
const int hRsi,
const int hWma45,
const int hEma200,
const int hAtrRegime,
const int atrCmpBars,
const double atrMinRatio,
const int sessionOkAtEntry)
{
if(dealOut == 0 || !HistoryDealSelect(dealOut))
return;
if(HistoryDealGetString(dealOut, DEAL_SYMBOL) != sym)
return;
if((ulong)HistoryDealGetInteger(dealOut, DEAL_MAGIC) != magic)
return;
if(HistoryDealGetInteger(dealOut, DEAL_ENTRY) != DEAL_ENTRY_OUT)
return;
if(!RsiMomJournal_OpenTrades(sym, tf))
return;
const ulong posId = (ulong)HistoryDealGetInteger(dealOut, DEAL_POSITION_ID);
ulong entryDeal = 0;
datetime entryTime = 0;
long posType = 0;
double entryPrice = 0.0, sl = 0.0, tp = 0.0, volume = 0.0;
if(!RsiMomJournal_FindEntryDeal(posId, entryDeal, entryTime, posType,
entryPrice, sl, tp, volume))
{
Print("[RsiMomEA] Journal: không tìm entry pos=", posId);
return;
}
const int entryShift = iBarShift(sym, tf, entryTime, true);
if(entryShift < 1)
{
Print("[RsiMomEA] Journal: entry shift invalid ", TimeToString(entryTime));
return;
}
const int dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
double rsi = 0.0, wma = 0.0, ema200 = 0.0, atrNow = 0.0, atrRatio = 0.0;
bool atrExpOk = false;
RsiMomJournal_Copy1(hRsi, entryShift, rsi);
RsiMomJournal_Copy1(hWma45, entryShift, wma);
RsiMomJournal_Copy1(hEma200, entryShift, ema200);
RsiMomJournal_AtrRatioAt(hAtrRegime, entryShift, atrCmpBars, atrMinRatio,
atrNow, atrRatio, atrExpOk);
const double closeEntry = iClose(sym, tf, entryShift);
const int aboveEma = (ema200 > 0.0 && closeEntry > ema200) ? 1 : 0;
const int sessionOk = (sessionOkAtEntry != 0) ? 1 : 0;
const int spreadPts = (int)SymbolInfoInteger(sym, SYMBOL_SPREAD);
double riskPts = 0.0;
if(posType == DEAL_TYPE_BUY && sl > 0.0)
riskPts = (entryPrice - sl) / _Point;
else if(posType == DEAL_TYPE_SELL && sl > 0.0)
riskPts = (sl - entryPrice) / _Point;
if(riskPts < 0.0)
riskPts = 0.0;
const datetime closeTime = (datetime)HistoryDealGetInteger(dealOut, DEAL_TIME);
MqlDateTime dt;
TimeToStruct(closeTime, dt);
const string yearMonth = StringFormat("%04d-%02d", dt.year, dt.mon);
const double profit = HistoryDealGetDouble(dealOut, DEAL_PROFIT)
+ HistoryDealGetDouble(dealOut, DEAL_SWAP)
+ HistoryDealGetDouble(dealOut, DEAL_COMMISSION);
const long dealReason = HistoryDealGetInteger(dealOut, DEAL_REASON);
const string exitTag = RsiMomJournal_ExitTag(dealReason, profit);
const string dir = (posType == DEAL_TYPE_BUY) ? "BUY" : "SELL";
double profitR = 0.0;
if(riskPts > 0.0)
{
const double tickVal = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_VALUE);
const double tickSz = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
if(tickSz > 0.0 && tickVal > 0.0)
{
const double riskMoney = (riskPts * _Point / tickSz) * tickVal * volume;
if(riskMoney > 0.0)
profitR = profit / riskMoney;
}
}
RsiMomJournal_AddMonth(yearMonth, profit, exitTag);
FileWrite(g_rsiMomJournalHandle,
TimeToString(closeTime, TIME_DATE | TIME_MINUTES),
yearMonth,
DoubleToString(profit, 2),
dir,
exitTag,
TimeToString(entryTime, TIME_DATE | TIME_MINUTES),
DoubleToString(entryPrice, dig),
DoubleToString(sl, dig),
DoubleToString(tp, dig),
DoubleToString(volume, 2),
DoubleToString(riskPts, 1),
DoubleToString(profitR, 3),
DoubleToString(rsi, 2),
DoubleToString(wma, dig),
DoubleToString(ema200, dig),
DoubleToString(closeEntry, dig),
IntegerToString(aboveEma),
DoubleToString(atrNow, dig),
DoubleToString(atrRatio, 4),
IntegerToString(atrExpOk ? 1 : 0),
IntegerToString(sessionOk),
IntegerToString(spreadPts),
sym,
EnumToString(tf),
IntegerToString((int)magic));
FileFlush(g_rsiMomJournalHandle);
}
#endif