欢迎访问

FVG结构流动性模板_3

author emer | 57 人阅读 | 0 人评论 |

//+------------------------------------------------------------------+
//| FVGStructureLiquidityEA.mq4 |
//| Paper-trading EA that evaluates FVG + structure + liquidity |
//| sweeps with optional session filtering. |
//| The EA never sends real orders; it only simulates fills, issues |
//| alerts, and tracks virtual performance metrics. |
//+------------------------------------------------------------------+

property strict

input int SwingDepth = 3; // swing detection depth
input int LiquidityLookbackBars = 20; // bars to look back for liquidity sweep confirmation
input double FVGMinPips = 3.0; // minimum gap size (pips)
input int MaxActiveFVG = 15; // max simultaneous FVG slots tracked
input int FVGExpiryMinutes = 90; // gap expiry window
input double BufferPips = 2.0; // stop buffer beyond FVG boundary
input double RiskPercent = 1.0; // virtual risk per trade (%)
input double PartialTPRatio = 1.0; // R-multiple for partial TP
input double FinalTPRatio = 2.5; // R-multiple for final TP
input bool EnablePartialScaleOut = true; // move stop to break-even after partial TP

enum ENUM_SESSION_MODE
{
SESSION_ALL = 0,
SESSION_LONDON,
SESSION_NEWYORK,
SESSION_CUSTOM
};

input ENUM_SESSION_MODE SessionMode = SESSION_CUSTOM;
input string CustomSession1 = "08:00-11:30"; // HH:MM-HH:MM
input string CustomSession2 = "13:30-16:30"; // optional second window
input bool EnableCsvLogging = false;

//--- constants

define MAX_SWING_POINTS 50

define MAX_LIQUIDITY_EVENTS 40

define MAX_VIRTUAL_ORDERS 20

define MAX_FVG_SLOTS 30

struct SwingPoint
{
datetime time;
double price;
int type; // 1 = high, -1 = low
};

struct FVGSlot
{
datetime time;
datetime expiry;
double upper;
double lower;
int direction; // 1 = bullish, -1 = bearish
bool active;
double score;
};

struct LiquidityEvent
{
datetime time;
double level;
int direction; // 1 = swept downside liquidity (long bias), -1 = swept upside (short bias)
};

struct VirtualOrder
{
bool active;
datetime openTime;
double entryPrice;
double stopLoss;
double tpPartial;
double tpFinal;
int direction; // 1 buy, -1 sell
double lots;
double riskPips;
bool partialTaken;
double closedPnL;
};

//--- globals
SwingPoint g_swings[MAX_SWING_POINTS];
int g_swingCount = 0;

FVGSlot g_fvgs[MAX_FVG_SLOTS];
int g_fvgCount = 0;

LiquidityEvent g_liquidityEvents[MAX_LIQUIDITY_EVENTS];
int g_liquidityCount = 0;

VirtualOrder g_orders[MAX_VIRTUAL_ORDERS];
int g_orderCount = 0;

double g_equityCurve = 100000.0; // virtual equity baseline
int g_totalTrades = 0;
int g_winningTrades = 0;
int g_losingTrades = 0;
double g_totalPnL = 0.0;
double g_maxDrawdown = 0.0;

datetime g_lastProcessedBar = 0;

// session storage (minutes from 00:00)
int g_sessionStarts[4];
int g_sessionEnds[4];
int g_sessionWindows = 0;

int g_logHandle = INVALID_HANDLE;
int g_maxActiveFvgSlots = 0;

//--- function declarations
int OnInit();
void OnDeinit(const int reason);
void OnTick();

bool ProcessNewBar(int shift);
bool IsSessionAllowed(datetime t);

bool IsSwingHigh(int shift);
bool IsSwingLow(int shift);
void PushSwingPoint(datetime t, double price, int type);
int DetermineStructureBias();

void CheckLiquiditySweep(int shift);
bool LiquidityRecentlyConfirmed(int direction, int lookbackMinutes);

void DetectFVG(int shift);
void UpdateFVGStates(int shift);

void EvaluateEntries(int shift, int structureBias);
bool PriceTouchesFVG(const FVGSlot &slot, int shift);

void TriggerVirtualOrder(const FVGSlot &slot, int direction, datetime t);
void UpdateVirtualOrders();
void CloseVirtualOrder(int index, const string reason, double exitPrice, datetime t);

double ComputeVirtualLots(double stopPips);
double PipValuePerLot();
double GetPointPips();

void AppendLiquidityEvent(datetime t, double level, int direction);

void ParseSessions();
bool ParseWindow(const string window, int &startMinutes, int &endMinutes);
int TimeToMinutes(datetime t);

void RenderStatusPanel();
void ResetObjects();
void LogEvent(const string &row);
string TrimString(string text);

//+------------------------------------------------------------------+
int OnInit()
{
ParseSessions();
ResetObjects();
g_maxActiveFvgSlots = MathMax(1, MathMin(MaxActiveFVG, MAX_FVG_SLOTS));

if(EnableCsvLogging)
{
string filePath = "FVG_PaperTradeLog.csv";
g_logHandle = FileOpen(filePath, FILE_CSV|FILE_WRITE|FILE_READ|FILE_SHARE_READ, ';');
if(g_logHandle != INVALID_HANDLE && FileSize(g_logHandle) == 0)
{
FileWrite(g_logHandle, "Timestamp", "Event", "Direction", "Entry", "Stop", "TP1", "TP2", "Lots", "PnL", "Equity");
}
}

return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ResetObjects();
if(g_logHandle != INVALID_HANDLE)
{
FileClose(g_logHandle);
g_logHandle = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
void OnTick()
{
datetime currentBarTime = iTime(_Symbol, _Period, 0);
if(currentBarTime == g_lastProcessedBar)
{
UpdateVirtualOrders();
return;
}

if(ProcessNewBar(1))
g_lastProcessedBar = currentBarTime;

UpdateVirtualOrders();
RenderStatusPanel();
}
//+------------------------------------------------------------------+
bool ProcessNewBar(int shift)
{
if(shift < SwingDepth + 2 || Bars(_Symbol, _Period) <= shift + SwingDepth + 2)
return false;

datetime barTime = iTime(_Symbol, _Period, shift);
bool sessionAllowed = IsSessionAllowed(barTime);

if(IsSwingHigh(shift))
PushSwingPoint(barTime, iHigh(_Symbol, _Period, shift), 1);
if(IsSwingLow(shift))
PushSwingPoint(barTime, iLow(_Symbol, _Period, shift), -1);

int structureBias = DetermineStructureBias();

CheckLiquiditySweep(shift);
DetectFVG(shift);
UpdateFVGStates(shift);

if(structureBias != 0 && sessionAllowed)
EvaluateEntries(shift, structureBias);

return true;
}
//+------------------------------------------------------------------+
bool IsSessionAllowed(datetime t)
{
if(SessionMode == SESSION_ALL)
return true;

int minutes = TimeToMinutes(t);

for(int i = 0; i < g_sessionWindows; ++i)
{
if(minutes >= g_sessionStarts[i] && minutes <= g_sessionEnds[i])
return true;
}
return false;
}
//+------------------------------------------------------------------+
bool IsSwingHigh(int shift)
{
if(shift - SwingDepth < 0)
return false;

double target = iHigh(_Symbol, _Period, shift);
for(int i = 1; i <= SwingDepth; ++i)
{
if(iHigh(_Symbol, _Period, shift + i) >= target)
return false;
if(iHigh(_Symbol, _Period, shift - i) >= target)
return false;
}
return true;
}
//+------------------------------------------------------------------+
bool IsSwingLow(int shift)
{
if(shift - SwingDepth < 0)
return false;

double target = iLow(_Symbol, _Period, shift);
for(int i = 1; i <= SwingDepth; ++i)
{
if(iLow(_Symbol, _Period, shift + i) <= target)
return false;
if(iLow(_Symbol, _Period, shift - i) <= target)
return false;
}
return true;
}
//+------------------------------------------------------------------+
void PushSwingPoint(datetime t, double price, int type)
{
SwingPoint point;
point.time = t;
point.price = price;
point.type = type;

if(g_swingCount < MAX_SWING_POINTS)
{
g_swings[g_swingCount++] = point;
}
else
{
for(int i = 1; i < MAX_SWING_POINTS; ++i)
g_swings[i - 1] = g_swings[i];
g_swings[MAX_SWING_POINTS - 1] = point;
g_swingCount = MAX_SWING_POINTS;
}
}
//+------------------------------------------------------------------+
int DetermineStructureBias()
{
double lastHigh = -1, prevHigh = -1;
double lastLow = -1, prevLow = -1;

for(int i = g_swingCount - 1; i >= 0; --i)
{
if(g_swings[i].type == 1)
{
if(lastHigh < 0)
lastHigh = g_swings[i].price;
else
{
prevHigh = g_swings[i].price;
break;
}
}
}

for(int j = g_swingCount - 1; j >= 0; --j)
{
if(g_swings[j].type == -1)
{
if(lastLow < 0)
lastLow = g_swings[j].price;
else
{
prevLow = g_swings[j].price;
break;
}
}
}

if(lastHigh < 0 || prevHigh < 0 || lastLow < 0 || prevLow < 0)
return 0;

if(lastHigh > prevHigh && lastLow > prevLow)
return 1;
if(lastHigh < prevHigh && lastLow < prevLow)
return -1;

return 0;
}
//+------------------------------------------------------------------+
void CheckLiquiditySweep(int shift)
{
if(g_swingCount < 2)
return;

SwingPoint lastSwing = g_swings[g_swingCount - 1];
double close = iClose(_Symbol, _Period, shift);
double high = iHigh(_Symbol, _Period, shift);
double low = iLow(_Symbol, _Period, shift);

if(lastSwing.type == 1)
{
// last swing is high -> look for sweep above
if(high > lastSwing.price && close < lastSwing.price)
AppendLiquidityEvent(iTime(_Symbol, _Period, shift), lastSwing.price, -1);
}
else if(lastSwing.type == -1)
{
if(low < lastSwing.price && close > lastSwing.price)
AppendLiquidityEvent(iTime(_Symbol, _Period, shift), lastSwing.price, 1);
}
}
//+------------------------------------------------------------------+
void AppendLiquidityEvent(datetime t, double level, int direction)
{
LiquidityEvent evt;
evt.time = t;
evt.level = level;
evt.direction = direction;

if(g_liquidityCount < MAX_LIQUIDITY_EVENTS)
{
g_liquidityEvents[g_liquidityCount++] = evt;
}
else
{
for(int i = 1; i < MAX_LIQUIDITY_EVENTS; ++i)
g_liquidityEvents[i - 1] = g_liquidityEvents[i];
g_liquidityEvents[MAX_LIQUIDITY_EVENTS - 1] = evt;
g_liquidityCount = MAX_LIQUIDITY_EVENTS;
}
}
//+------------------------------------------------------------------+
bool LiquidityRecentlyConfirmed(int direction, int lookbackMinutes)
{
datetime nowTime = TimeCurrent();
datetime earliest = nowTime - lookbackMinutes * 60;

for(int i = g_liquidityCount - 1; i >= 0; --i)
{
if(g_liquidityEvents[i].direction != direction)
continue;
if(g_liquidityEvents[i].time >= earliest)
return true;
if(g_liquidityEvents[i].time < earliest)
break;
}
return false;
}
//+------------------------------------------------------------------+
void DetectFVG(int shift)
{
double pip = GetPointPips();
double minGap = FVGMinPips * pip;

if(shift + 2 >= Bars(_Symbol, _Period))
return;

double highPrev1 = iHigh(_Symbol, _Period, shift + 1);
double highPrev2 = iHigh(_Symbol, _Period, shift + 2);
double lowPrev1 = iLow(_Symbol, _Period, shift + 1);
double lowPrev2 = iLow(_Symbol, _Period, shift + 2);

double lowCurr = iLow(_Symbol, _Period, shift);
double highCurr = iHigh(_Symbol, _Period, shift);

bool bullishGap = (lowCurr - highPrev1) > minGap && (lowCurr - highPrev2) > minGap;
bool bearishGap = (lowPrev1 - highCurr) > minGap && (lowPrev2 - highCurr) > minGap;

datetime barTime = iTime(_Symbol, _Period, shift);

if(bullishGap)
{
FVGSlot slot;
slot.time = barTime;
slot.expiry = barTime + FVGExpiryMinutes * 60;
slot.lower = highPrev1;
slot.upper = lowCurr;
slot.direction = 1;
slot.active = true;
slot.score = 1.0;

  if(g_fvgCount < g_maxActiveFvgSlots)
  {
     g_fvgs[g_fvgCount++] = slot;
  }
  else
  {
     for(int i = 1; i < g_maxActiveFvgSlots; ++i)
        g_fvgs[i - 1] = g_fvgs[i];
     g_fvgs[g_maxActiveFvgSlots - 1] = slot;
     g_fvgCount = g_maxActiveFvgSlots;
  }

}
else if(bearishGap)
{
FVGSlot slot;
slot.time = barTime;
slot.expiry = barTime + FVGExpiryMinutes * 60;
slot.upper = lowPrev1;
slot.lower = highCurr;
slot.direction = -1;
slot.active = true;
slot.score = 1.0;

  if(g_fvgCount < g_maxActiveFvgSlots)
  {
     g_fvgs[g_fvgCount++] = slot;
  }
  else
  {
     for(int j = 1; j < g_maxActiveFvgSlots; ++j)
        g_fvgs[j - 1] = g_fvgs[j];
     g_fvgs[g_maxActiveFvgSlots - 1] = slot;
     g_fvgCount = g_maxActiveFvgSlots;
  }

}
}
//+------------------------------------------------------------------+
void UpdateFVGStates(int shift)
{
double high = iHigh(_Symbol, _Period, shift);
double low = iLow(_Symbol, _Period, shift);
datetime barTime = iTime(_Symbol, _Period, shift);

for(int i = 0; i < g_fvgCount; ++i)
{
if(!g_fvgs[i].active)
continue;

  if(barTime >= g_fvgs[i].expiry)
  {
     g_fvgs[i].active = false;
     continue;
  }

  if(g_fvgs[i].direction == 1 && low <= g_fvgs[i].lower)
     g_fvgs[i].active = false;
  if(g_fvgs[i].direction == -1 && high >= g_fvgs[i].upper)
     g_fvgs[i].active = false;

}
}
//+------------------------------------------------------------------+
void EvaluateEntries(int shift, int structureBias)
{
for(int i = 0; i < g_fvgCount; ++i)
{
if(!g_fvgs[i].active)
continue;

  if(g_fvgs[i].direction != structureBias)
     continue;

  int direction = g_fvgs[i].direction;
  double periodSeconds = (double)PeriodSeconds(_Period);
  if(periodSeconds <= 0)
     periodSeconds = 60.0;
  int lookbackMinutes = (int)MathMax(1.0, LiquidityLookbackBars * periodSeconds / 60.0);
  if(!LiquidityRecentlyConfirmed(direction, lookbackMinutes))
     continue;

  if(PriceTouchesFVG(g_fvgs[i], shift))
  {
     TriggerVirtualOrder(g_fvgs[i], direction, iTime(_Symbol, _Period, shift));
     g_fvgs[i].active = false;
  }

}
}
//+------------------------------------------------------------------+
bool PriceTouchesFVG(const FVGSlot &slot, int shift)
{
double high = iHigh(_Symbol, _Period, shift);
double low = iLow(_Symbol, _Period, shift);

if(slot.direction == 1)
return (low <= slot.upper && high >= slot.lower);
else
return (high >= slot.lower && low <= slot.upper);
}
//+------------------------------------------------------------------+
void TriggerVirtualOrder(const FVGSlot &slot, int direction, datetime t)
{
double entryPrice = (slot.lower + slot.upper) 0.5;
double stopBuffer = BufferPips
GetPointPips();
double stopLoss;

if(direction == 1)
stopLoss = slot.lower - stopBuffer;
else
stopLoss = slot.upper + stopBuffer;

double riskPips = MathAbs(entryPrice - stopLoss) / GetPointPips();
if(riskPips <= 0.0)
return;

double lots = ComputeVirtualLots(riskPips);
if(lots <= 0.0)
return;

if(g_orderCount >= MAX_VIRTUAL_ORDERS)
{
for(int i = 1; i < MAX_VIRTUAL_ORDERS; ++i)
g_orders[i - 1] = g_orders[i];
g_orderCount = MAX_VIRTUAL_ORDERS - 1;
}

VirtualOrder order;
order.active = true;
order.openTime = t;
order.entryPrice = entryPrice;
order.stopLoss = stopLoss;
order.riskPips = riskPips;
order.direction = direction;
order.lots = lots;
order.partialTaken = false;
order.closedPnL = 0.0;

double riskPrice = riskPips GetPointPips();
if(direction == 1)
{
order.tpPartial = entryPrice + riskPrice
PartialTPRatio;
order.tpFinal = entryPrice + riskPrice FinalTPRatio;
}
else
{
order.tpPartial = entryPrice - riskPrice
PartialTPRatio;
order.tpFinal = entryPrice - riskPrice * FinalTPRatio;
}

g_orders[g_orderCount++] = order;
++g_totalTrades;

string msg = StringFormat("Paper trade opened (%s) at %.5f, stop %.5f, TP1 %.5f, TP2 %.5f, lots %.2f",
direction > 0 ? "LONG" : "SHORT",
order.entryPrice,
order.stopLoss,
order.tpPartial,
order.tpFinal,
order.lots);
Alert(msg);
Print(msg);

string logRow = StringFormat("%s;OPEN;%d;%.5f;%.5f;%.5f;%.5f;%.2f;0;%.2f",
TimeToString(t, TIME_DATE|TIME_SECONDS),
direction,
order.entryPrice,
order.stopLoss,
order.tpPartial,
order.tpFinal,
order.lots,
g_equityCurve);
LogEvent(logRow);
}
//+------------------------------------------------------------------+
void UpdateVirtualOrders()
{
if(g_orderCount == 0)
return;

double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
datetime nowTime = TimeCurrent();

for(int i = 0; i < g_orderCount; ++i)
{
if(!g_orders[i].active)
continue;

  double price = (g_orders[i].direction > 0) ? bid : ask;

  // check stop loss
  if((g_orders[i].direction > 0 && price <= g_orders[i].stopLoss) ||
     (g_orders[i].direction < 0 && price >= g_orders[i].stopLoss))
  {
     double pnl = -g_orders[i].riskPips * PipValuePerLot() * g_orders[i].lots;
     CloseVirtualOrder(i, "STOP", g_orders[i].stopLoss, nowTime);
     g_equityCurve += pnl;
     g_totalPnL += pnl;
     ++g_losingTrades;
     g_maxDrawdown = MathMin(g_maxDrawdown, g_totalPnL);
     continue;
  }

  // partial TP
  if(EnablePartialScaleOut && !g_orders[i].partialTaken)
  {
     if((g_orders[i].direction > 0 && price >= g_orders[i].tpPartial) ||
        (g_orders[i].direction < 0 && price <= g_orders[i].tpPartial))
     {
        g_orders[i].partialTaken = true;
        g_orders[i].stopLoss = g_orders[i].entryPrice;
        double pnl = g_orders[i].riskPips * PipValuePerLot() * g_orders[i].lots * PartialTPRatio;
        double partialReward = pnl * 0.5;
        g_equityCurve += partialReward;
        g_totalPnL += partialReward;

        string partialRow = StringFormat("%s;PARTIAL;%d;%.5f;%.5f;%.5f;%.5f;%.2f;%.2f;%.2f",
                                         TimeToString(nowTime, TIME_DATE|TIME_SECONDS),
                                         g_orders[i].direction,
                                         g_orders[i].entryPrice,
                                         g_orders[i].stopLoss,
                                         g_orders[i].tpPartial,
                                         g_orders[i].tpFinal,
                                         g_orders[i].lots,
                                         partialReward,
                                         g_equityCurve);
        LogEvent(partialRow);
     }
  }

  // final TP
  if((g_orders[i].direction > 0 && price >= g_orders[i].tpFinal) ||
     (g_orders[i].direction < 0 && price <= g_orders[i].tpFinal))
  {
     double reward = g_orders[i].riskPips * PipValuePerLot() * g_orders[i].lots * FinalTPRatio;
     CloseVirtualOrder(i, "TARGET", g_orders[i].tpFinal, nowTime);
     g_equityCurve += reward;
     g_totalPnL += reward;
     ++g_winningTrades;
  }

}
}
//+------------------------------------------------------------------+
void CloseVirtualOrder(int index, const string reason, double exitPrice, datetime t)
{
if(index < 0 || index >= g_orderCount)
return;

g_orders[index].active = false;
string msg = StringFormat("Paper trade closed (%s) at %.5f due to %s",
g_orders[index].direction > 0 ? "LONG" : "SHORT",
exitPrice,
reason);
Alert(msg);
Print(msg);

string logRow = StringFormat("%s;%s;%d;%.5f;%.5f;%.5f;%.5f;%.2f;%.2f;%.2f",
TimeToString(t, TIME_DATE|TIME_SECONDS),
reason,
g_orders[index].direction,
g_orders[index].entryPrice,
g_orders[index].stopLoss,
g_orders[index].tpPartial,
g_orders[index].tpFinal,
g_orders[index].lots,
g_totalPnL,
g_equityCurve);
LogEvent(logRow);
}
//+------------------------------------------------------------------+
double ComputeVirtualLots(double stopPips)
{
double accountEquity = AccountEquity();
if(accountEquity <= 0)
accountEquity = g_equityCurve;

double riskAmount = accountEquity * RiskPercent / 100.0;
double pipValue = PipValuePerLot();
if(stopPips <= 0 || pipValue <= 0)
return 0.0;

double lots = riskAmount / (stopPips pipValue);
lots = MathMax(NormalizeDouble(lots, 2), MarketInfo(_Symbol, MODE_MINLOT));
lots = MathMin(lots, MarketInfo(_Symbol, MODE_MAXLOT));
return lots;
}
//+------------------------------------------------------------------+
double PipValuePerLot()
{
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickSize == 0.0)
return 0.0;
double pipSize = GetPointPips();
return tickValue
(pipSize / tickSize);
}
//+------------------------------------------------------------------+
double GetPointPips()
{
int digits = (int)MarketInfo(_Symbol, MODE_DIGITS);
if(digits == 3 || digits == 5)
return 10 _Point;
return _Point;
}
//+------------------------------------------------------------------+
void ParseSessions()
{
g_sessionWindows = 0;
switch(SessionMode)
{
case SESSION_LONDON:
g_sessionStarts[0] = 8
60;
g_sessionEnds[0] = 11 60 + 30;
g_sessionWindows = 1;
break;
case SESSION_NEWYORK:
g_sessionStarts[0] = 13
60 + 30;
g_sessionEnds[0] = 16 * 60 + 30;
g_sessionWindows = 1;
break;
case SESSION_CUSTOM:
{
int startMinutes, endMinutes;
if(ParseWindow(CustomSession1, startMinutes, endMinutes))
{
g_sessionStarts[g_sessionWindows] = startMinutes;
g_sessionEnds[g_sessionWindows] = endMinutes;
++g_sessionWindows;
}
if(ParseWindow(CustomSession2, startMinutes, endMinutes) && g_sessionWindows < 4)
{
g_sessionStarts[g_sessionWindows] = startMinutes;
g_sessionEnds[g_sessionWindows] = endMinutes;
++g_sessionWindows;
}
break;
}
default:
g_sessionWindows = 0;
break;
}
}
//+------------------------------------------------------------------+
bool ParseWindow(const string window, int &startMinutes, int &endMinutes)
{
if(StringLen(window) < 5)
return false;

string parts[];
int count = StringSplit(window, '-', parts);
if(count != 2)
return false;

string startStr = TrimString(parts[0]);
string endStr = TrimString(parts[1]);

string startVals[];
string endVals[];
if(StringSplit(startStr, ':', startVals) != 2 || StringSplit(endStr, ':', endVals) != 2)
return false;

int startHour = (int)StringToInteger(startVals[0]);
int startMin = (int)StringToInteger(startVals[1]);
int endHour = (int)StringToInteger(endVals[0]);
int endMin = (int)StringToInteger(endVals[1]);

startMinutes = startHour 60 + startMin;
endMinutes = endHour
60 + endMin;
if(endMinutes < startMinutes)
endMinutes += 24 60;
return true;
}
//+------------------------------------------------------------------+
int TimeToMinutes(datetime t)
{
MqlDateTime md;
TimeToStruct(t, md);
return md.hour
60 + md.min;
}
//+------------------------------------------------------------------+
void RenderStatusPanel()
{
string name = "FVG_STATUS_PANEL";
if(ObjectFind(0, name) == -1)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 20);
}

string text = StringFormat("Paper Trades: %d (W:%d / L:%d)\nEquity: %.2f | PnL: %.2f | MaxDD: %.2f",
g_totalTrades,
g_winningTrades,
g_losingTrades,
g_equityCurve,
g_totalPnL,
g_maxDrawdown);
ObjectSetText(name, text, 10, "Consolas", clrWhite);
}
//+------------------------------------------------------------------+
void ResetObjects()
{
ObjectDelete(0, "FVG_STATUS_PANEL");
}
//+------------------------------------------------------------------+
void LogEvent(const string &row)
{
if(g_logHandle == INVALID_HANDLE)
return;
FileSeek(g_logHandle, 0, SEEK_END);
FileWriteString(g_logHandle, row);
FileWrite(g_logHandle, "\n");
FileFlush(g_logHandle);
}
//+------------------------------------------------------------------+
string TrimString(string text)
{
int len = StringLen(text);
while(len > 0 && StringGetChar(text, len - 1) <= ' ')
{
text = StringSubstr(text, 0, len - 1);
len = StringLen(text);
}
while(len > 0 && StringGetChar(text, 0) <= ' ')
{
text = StringSubstr(text, 1);
len = StringLen(text);
}
return text;
}
//+------------------------------------------------------------------+

还没有人打赏,支持一下