Files
2026-05-27 03:18:59 +07:00

219 lines
7.8 KiB
Plaintext

//+------------------------------------------------------------------+
//| Stats.mqh — thống kê lệnh MSS từ HistoryDeals (theo magic) |
//+------------------------------------------------------------------+
//| Quét HistoryDeals filter symbol + magic = InpMssMagic. Compute: |
//| total / tpCount / slCount / otherCount |
//| winCount / lossCount / sumR / netProfit |
//| |
//| v1.183 — chỉ TP/SL được đếm vào total/win/loss/sumR/tp/sl. Partial|
//| close / manual / EA-close → otherCount (chỉ log, không vào panel).|
//| netProfit cộng dồn TẤT CẢ deals (kể cả partial — P&L thực). |
//| AvgR = sumR / (tpCount + slCount) — đồng nhất với cách đếm. |
//| |
//| 2-pass scan (v1.142 fix): |
//| Pass 1: chỉ collect ticket + reason + net (global selection còn |
//| nguyên, không gọi HistorySelectByPosition) |
//| Pass 2: tính R cho từng ticket (mỗi call thay selection) |
//| |
//| R computation (ComputeR): |
//| R = (closePrice - entryPrice) / (entryPrice - slPrice) for BUY |
//| = (entryPrice - closePrice) / (slPrice - entryPrice) for SELL|
//| entryPrice + slPrice lấy từ DEAL_ENTRY_IN + ORDER_SL của |
//| position. |
//| |
//| Globals owned: |
//| g_ictMssStats — IctMssStats struct |
//| g_ictMssStatsLastScan — TS scan gần nhất (debounce) |
//| |
//| Public API: |
//| void IctMssStats_Recompute(sym, magic) |
//| double IctMssStats_WinratePct() / IctMssStats_AvgR() |
//| string IctMssStats_LineCounts() / IctMssStats_LinePerf() |
//+------------------------------------------------------------------+
#ifndef ICT2026_STATS_MQH
#define ICT2026_STATS_MQH
#include <ICT2026/Config.mqh>
struct IctMssStats
{
int total;
int tpCount;
int slCount;
int otherCount; // manual / expert / mobile / web
int winCount; // profit > 0
int lossCount; // profit < 0
double sumR; // tổng R đạt được (đã quy chuẩn theo risk lúc vào lệnh)
double netProfit;
void Clear()
{
total = tpCount = slCount = otherCount = 0;
winCount = lossCount = 0;
sumR = 0.0;
netProfit = 0.0;
}
};
IctMssStats g_ictMssStats;
static datetime g_ictMssStatsLastScan = 0;
double IctMssStats_ComputeR(const ulong outDeal)
{
const ulong posId = (ulong)HistoryDealGetInteger(outDeal, DEAL_POSITION_ID);
const double closePrice = HistoryDealGetDouble(outDeal, DEAL_PRICE);
if(posId == 0 || closePrice <= 0.0)
return 0.0;
if(!HistorySelectByPosition(posId))
return 0.0;
double entryPrice = 0.0;
double slPrice = 0.0;
bool isBuy = false;
const int n = HistoryDealsTotal();
for(int i = 0; i < n; i++)
{
const ulong d = HistoryDealGetTicket(i);
if(d == 0)
continue;
if((ulong)HistoryDealGetInteger(d, DEAL_POSITION_ID) != posId)
continue;
if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_IN)
continue;
entryPrice = HistoryDealGetDouble(d, DEAL_PRICE);
isBuy = (HistoryDealGetInteger(d, DEAL_TYPE) == DEAL_TYPE_BUY);
const ulong orderTicket = (ulong)HistoryDealGetInteger(d, DEAL_ORDER);
if(orderTicket > 0 && HistoryOrderSelect(orderTicket))
slPrice = HistoryOrderGetDouble(orderTicket, ORDER_SL);
break;
}
if(entryPrice <= 0.0 || slPrice <= 0.0)
return 0.0;
const double risk = isBuy ? (entryPrice - slPrice) : (slPrice - entryPrice);
if(risk <= _Point)
return 0.0;
const double move = isBuy ? (closePrice - entryPrice) : (entryPrice - closePrice);
return move / risk;
}
void IctMssStats_Recompute(const string sym, const ulong magic)
{
g_ictMssStats.Clear();
if(!HistorySelect(0, TimeCurrent()))
return;
// Pass 1: thu thập ticket + dữ liệu cơ bản trong khi global history selection còn nguyên.
// Lưu ý: nếu gọi HistorySelectByPosition (trong ComputeR) ở đây → selection bị thay,
// các index sau sẽ đọc từ history của 1 position → đếm thiếu lệnh.
ulong outTickets[];
long reasons[];
double nets[];
int cached = 0;
const int n = HistoryDealsTotal();
ArrayResize(outTickets, n);
ArrayResize(reasons, n);
ArrayResize(nets, n);
for(int i = 0; i < n; i++)
{
const ulong d = HistoryDealGetTicket(i);
if(d == 0)
continue;
if(HistoryDealGetString(d, DEAL_SYMBOL) != sym)
continue;
if((ulong)HistoryDealGetInteger(d, DEAL_MAGIC) != magic)
continue;
if(HistoryDealGetInteger(d, DEAL_ENTRY) != DEAL_ENTRY_OUT)
continue;
outTickets[cached] = d;
reasons[cached] = HistoryDealGetInteger(d, DEAL_REASON);
nets[cached] = HistoryDealGetDouble(d, DEAL_PROFIT)
+ HistoryDealGetDouble(d, DEAL_SWAP)
+ HistoryDealGetDouble(d, DEAL_COMMISSION);
cached++;
}
// Pass 2: chỉ tính TP/SL vào stats. Partial close / manual close
// không phải kết quả lệnh ⇒ skip total/tp/sl/win/loss/sumR.
// netProfit vẫn cộng dồn TẤT CẢ deals để phản ánh đúng P&L thực tế.
for(int k = 0; k < cached; k++)
{
g_ictMssStats.netProfit += nets[k];
const ENUM_DEAL_REASON rs = (ENUM_DEAL_REASON)reasons[k];
const bool isTp = (rs == DEAL_REASON_TP);
const bool isSl = (rs == DEAL_REASON_SL);
if(!isTp && !isSl)
{
g_ictMssStats.otherCount++;
continue;
}
const double r = IctMssStats_ComputeR(outTickets[k]);
g_ictMssStats.total++;
g_ictMssStats.sumR += r;
if(isTp)
g_ictMssStats.tpCount++;
else
g_ictMssStats.slCount++;
if(nets[k] > 0.0)
g_ictMssStats.winCount++;
else if(nets[k] < 0.0)
g_ictMssStats.lossCount++;
}
g_ictMssStatsLastScan = TimeCurrent();
if(InpMssLogJournal)
PrintFormat("[ICT2026/Stats] Recompute %s magic=%I64u | total=%d TP=%d SL=%d (skip khác=%d) | net=%.2f sumR=%.2f",
sym, magic,
g_ictMssStats.total, g_ictMssStats.tpCount,
g_ictMssStats.slCount, g_ictMssStats.otherCount,
g_ictMssStats.netProfit, g_ictMssStats.sumR);
}
double IctMssStats_WinratePct()
{
const int decided = g_ictMssStats.tpCount + g_ictMssStats.slCount;
if(decided == 0)
return 0.0;
return 100.0 * (double)g_ictMssStats.tpCount / (double)decided;
}
double IctMssStats_AvgR()
{
const int decided = g_ictMssStats.tpCount + g_ictMssStats.slCount;
if(decided <= 0)
return 0.0;
return g_ictMssStats.sumR / (double)decided;
}
string IctMssStats_LineCounts()
{
return StringFormat("Stats: %d lệnh | TP %d | SL %d",
g_ictMssStats.total,
g_ictMssStats.tpCount,
g_ictMssStats.slCount);
}
string IctMssStats_LinePerf()
{
const double wr = IctMssStats_WinratePct();
const double ar = IctMssStats_AvgR();
return StringFormat("WR %.1f%% | Ravg %+.2fR | Net %+.2f",
wr, ar, g_ictMssStats.netProfit);
}
#endif