FIxxing bug only run 1 month data

This commit is contained in:
Bell
2025-12-03 20:52:43 +07:00
parent 82331ebdd8
commit 82d164dbc9
+169 -240
View File
@@ -1,231 +1,214 @@
//+------------------------------------------------------------------+
//| EA ICT-style: Daily Bias -> H1 FVG -> M5 pullback entries |
//| - Xác định Daily bias theo yêu cầu của bạn |
//| - Tìm FVG trên H1 thuận chiều với daily bias (phương pháp đơn giản)
//| - Khi giá hồi về H1 FVG, chuyển xuống M5: tìm M5 FVG + MSS (đơn giản)
//| - Đặt BuyLimit / SellLimit tại M5 FVG, SL tính theo đáy/đỉnh pullback
//| - Lot được tính sao cho rủi ro entry->SL = 1% equity (tùy biến RiskPerTrade)
//| - TP = entry + 3 * (entry - SL) (R:R = 1:3)
//| EA ICT-style: Daily Bias -> MidTf FVG -> LowTf pullback entries |
//+------------------------------------------------------------------+
#property strict
#include <Trade/Trade.mqh>
CTrade trade;
//--- Inputs
input double RiskPerTrade = 1.0; // % equity risk per trade (mặc định 1%)
input double RiskPerTrade = 1.0; // % equity risk per trade
input int MagicNumber = 33333;
input int H1_FVG_lookback = 200; // bars to scan for H1 FVG
input int M5_lookback = 200; // bars to scan for M5 FVG/MSS
input double MinADX = 10.0; // optional ADX filter (không bắt buộc)
input int Mid_TF_FVG_lookback = 200; // bars to scan for MidTf FVG
input int Low_TF_FVG_lookback = 200; // bars to scan for LowTf FVG/MSS
input double MinADX = 10.0;
input int ADXPeriod = 14;
input double MaxAcceptableSpread = 200; // points
input double RR = 3.0; // Risk:Reward multiplier (TP = RR * risk distance) // points
input double RR = 3.0; // Risk:Reward multiplier (TP = RR * risk distance)
//--- Timeframe configuration (configurable)
input ENUM_TIMEFRAMES TF_High = PERIOD_D1; // Higher timeframe used for bias
input ENUM_TIMEFRAMES TF_Mid = PERIOD_H1; // Mid timeframe for FVG
input ENUM_TIMEFRAMES TF_Low = PERIOD_M5; // Low timeframe for entries
//--- Internal structs
struct Zone { double top; double bottom; int from_index; int to_index; };
enum DailyBias { BIAS_UNKNOWN=0, BIAS_UP=1, BIAS_DOWN=-1 };
//--- Utility forward declarations
//--- Forward
DailyBias DetermineDailyBias();
int FindH1FVGs(DailyBias bias, Zone &foundZone);
int FindM5FVGAtZone(Zone &h1zone, DailyBias bias, Zone &m5zone);
bool DetectM5MSS(int &mssType, double &mssPrice); // returns 1 for bullish MSS (break to upside), -1 for bearish
int FindMidTfFvg(DailyBias bias, Zone &foundZone);
int FindLowTfFvg(Zone &midTfZone, DailyBias bias, Zone &lowTfZone);
bool DetectLowTfMSS(int &mssType, double &mssPrice);
double CalculateLotForRisk(double entryPrice, double stopPrice);
bool HasActiveOrders();
void PlaceLimitOrder(int side, double price, double sl, double tp, double lot);
//+------------------------------------------------------------------+
int OnInit()
{
Print("EA ICT-style initialized");
PrintFormat("EA init: TF_High=%d TF_Mid=%d TF_Low=%d", (int)TF_High, (int)TF_Mid, (int)TF_Low);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
void OnTick()
{
// chỉ xử lý trên M5 khi có bar mới
static datetime lastBarTime=0;
datetime t = iTime(_Symbol, PERIOD_M5, 0);
datetime t = iTime(_Symbol, TF_Low, 0);
if(t==lastBarTime) return;
lastBarTime = t;
// basic symbol params check
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(point <= 0) { Print("Invalid SYMBOL_POINT -> abort tick"); return; }
// spread check
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double spreadPts = (ask-bid)/point;
double spreadPts = (ask - bid)/point;
if(spreadPts > MaxAcceptableSpread) { PrintFormat("Spread too high: %.1f pts", spreadPts); return; }
// 1) xác định daily bias
// 1) bias on TF_High
DailyBias bias = DetermineDailyBias();
if(bias==BIAS_UNKNOWN) { Print("Daily bias unknown -> skip"); return; }
PrintFormat("Daily bias = %d", bias);
if(bias==BIAS_UNKNOWN) { Print("Bias unknown -> skip"); return; }
PrintFormat("Bias = %d", bias);
// 2) tìm H1 FVG thuận chiều với bias
Zone h1zone; bool foundH1 = (FindH1FVGs(bias, h1zone) > 0);
if(!foundH1) { Print("No H1 FVG found in bias direction"); return; }
PrintFormat("Found H1 FVG: top=%.5f bottom=%.5f from=%d to=%d", h1zone.top, h1zone.bottom, h1zone.from_index, h1zone.to_index);
// 2) Mid TF FVG
Zone midZone;
if(FindMidTfFvg(bias, midZone) == 0) { Print("No MidTf FVG -> skip"); return; }
PrintFormat("Mid zone: top=%.5f bottom=%.5f", midZone.top, midZone.bottom);
// 3) khi giá hiện tại đã từng (hoặc đang) thuộc H1 FVG region -> tìm M5 FVG inside that H1 zone
Zone m5zone; bool foundM5 = (FindM5FVGAtZone(h1zone,bias,m5zone) > 0);
if(!foundM5) { Print("No M5 FVG inside H1 FVG -> skip"); return; }
PrintFormat("Found M5 FVG: top=%.5f bottom=%.5f", m5zone.top, m5zone.bottom);
// 3) Low TF FVG inside mid zone
Zone lowZone;
if(FindLowTfFvg(midZone, bias, lowZone) == 0) { Print("No LowTf FVG -> skip"); return; }
PrintFormat("Low zone: top=%.5f bottom=%.5f", lowZone.top, lowZone.bottom);
// 4) xác nhận MSS trên M5 (đơn giản: break of structure recent swing)
// 4) MSS detect on low TF
int mssType=0; double mssPrice=0;
if(!DetectM5MSS(mssType,mssPrice)) { Print("No M5 MSS detected -> skip"); return; }
PrintFormat("M5 MSS type=%d price=%.5f", mssType, mssPrice);
if(!DetectLowTfMSS(mssType, mssPrice)) { Print("No LowTf MSS -> skip"); return; }
PrintFormat("LowTf MSS type=%d price=%.5f", mssType, mssPrice);
// 5) chuẩn bị entry: nếu bias up -> place buy limit at bottom of m5zone; if bias down -> sell limit at top
double entryPrice = (bias==BIAS_UP) ? m5zone.bottom : m5zone.top;
// 5) Entry at lowZone.bottom (buy) or top (sell)
double entry = (bias==BIAS_UP) ? lowZone.bottom : lowZone.top;
double sl=0, tp=0;
// compute SL: if buy -> SL = lowest low of the pullback swing on M5 (we approximate by minimum low in zone window)
double sl=0,tp=0;
// compute swing extreme on TF_Low with validation
if(bias==BIAS_UP)
{
// find lowest low in recent M5 bars inside/near the m5zone range
double swingLow = DBL_MAX;
for(int i=0;i<M5_lookback;i++) { double low = iLow(_Symbol,PERIOD_M5,i); if(low < swingLow) swingLow = low; }
sl = swingLow - 5*point; // small buffer
if(sl >= entryPrice) { Print("Computed SL >= entry -> skip"); return; }
double dist = entryPrice - sl;
tp = entryPrice + RR * dist; // R:R=1:3
for(int i=0;i<Low_TF_FVG_lookback;i++)
{
double lv = iLow(_Symbol, TF_Low, i);
if(lv <= 0) continue; // skip invalid bars
if(lv < swingLow) swingLow = lv;
}
if(swingLow==DBL_MAX) { Print("Not enough low TF bars -> skip"); return; }
sl = swingLow - 5*point;
if(sl >= entry) { Print("SL >= entry -> skip"); return; }
tp = entry + RR * (entry - sl);
}
else
{
double swingHigh = -DBL_MAX;
for(int i=0;i<M5_lookback;i++) { double high = iHigh(_Symbol,PERIOD_M5,i); if(high > swingHigh) swingHigh = high; }
for(int i=0;i<Low_TF_FVG_lookback;i++)
{
double hv = iHigh(_Symbol, TF_Low, i);
if(hv <= 0) continue;
if(hv > swingHigh) swingHigh = hv;
}
if(swingHigh==-DBL_MAX) { Print("Not enough low TF bars -> skip"); return; }
sl = swingHigh + 5*point;
if(sl <= entryPrice) { Print("Computed SL <= entry -> skip"); return; }
double dist = sl - entryPrice;
tp = entryPrice - RR * dist;
if(sl <= entry) { Print("SL <= entry -> skip"); return; }
tp = entry - RR * (sl - entry);
}
// 6) tính lot theo risk = RiskPerTrade% equity cho khoảng cách entry->SL
double lot = CalculateLotForRisk(entryPrice, sl);
if(lot <= 0) { Print("Calculated lot <=0 -> skip"); return; }
// 6) lot calc
double lot = CalculateLotForRisk(entry, sl);
if(lot <= 0) { Print("Lot <= 0 -> skip"); return; }
// 7) đặt pending limit
// 7) only one active for EA on symbol
int side = (bias==BIAS_UP) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
// Check if EA already has active position or pending order (only 1 at a time)
if(HasActiveOrders())
{
Print("Already have an active position or pending order for this EA -> skip placing another");
}
else
{
PlaceLimitOrder(side, entryPrice, sl, tp, lot);
}
if(HasActiveOrders()) { Print("Active order exists -> skip"); return; }
PlaceLimitOrder(side, entry, sl, tp, lot);
}
//+------------------------------------------------------------------+
DailyBias DetermineDailyBias()
{
// theo yêu cầu: xét 2 cây D1 đã đóng gần nhất (không tính nến hiện tại -> shift 1 và 2)
// nếu D[1].close > D[2].high -> up. nếu D[1].close < D[2].low -> down. nếu D[1] nằm trong D[2] thì bỏ qua D[1] và dùng D2 & D3
int idx1 = 1; // D[1]
int idx2 = 2; // D[2]
int tries = 0;
while(tries < 5)
int idx1=1, idx2=2, tries=0;
while(tries<5)
{
double close1 = iClose(_Symbol,PERIOD_D1,idx1);
double high2 = iHigh(_Symbol,PERIOD_D1,idx2);
double low2 = iLow(_Symbol,PERIOD_D1,idx2);
double close1 = iClose(_Symbol, TF_High, idx1);
double high2 = iHigh (_Symbol, TF_High, idx2);
double low2 = iLow (_Symbol, TF_High, idx2);
if(close1 > high2) return BIAS_UP;
if(close1 < low2) return BIAS_DOWN;
// close1 inside candle2 -> shift window down (use D2 & D3)
idx1++; idx2++; tries++;
// ensure there are bars
if(idx2 > 200) break;
if(idx2>500) break;
}
return BIAS_UNKNOWN;
}
//+------------------------------------------------------------------+
int FindH1FVGs(DailyBias bias, Zone &foundZone)
int FindMidTfFvg(DailyBias bias, Zone &foundZone)
{
// Phương pháp đơn giản:
// Tìm gap "Fair Value Gap" kiểu: giữa 2 candle (i and i+2) có khoảng trống
// Bullish FVG (hỗ trợ): low[i] > high[i+2] -> vùng FVG là (high[i+2], low[i])
// Bearish FVG (kháng cự): high[i] < low[i+2] -> vùng FVG là (high[i], low[i+2])
int limit = H1_FVG_lookback;
int limit = Mid_TF_FVG_lookback;
for(int i=1;i<limit-2;i++)
{
double high_i = iHigh(_Symbol,PERIOD_H1,i);
double low_i = iLow(_Symbol,PERIOD_H1,i);
double high_i2 = iHigh(_Symbol,PERIOD_H1,i+2);
double low_i2 = iLow(_Symbol,PERIOD_H1,i+2);
double high_i = iHigh(_Symbol, TF_Mid, i);
double low_i = iLow (_Symbol, TF_Mid, i);
double high_i2 = iHigh(_Symbol, TF_Mid, i+2);
double low_i2 = iLow (_Symbol, TF_Mid, i+2);
if(high_i<=0 || low_i<=0 || high_i2<=0 || low_i2<=0) continue;
if(bias==BIAS_UP)
{
// bullish FVG
if(low_i > high_i2 + SymbolInfoDouble(_Symbol,SYMBOL_POINT)*0.0) // allow equality
{
foundZone.top = low_i;
foundZone.bottom = high_i2;
foundZone.from_index = i+2;
foundZone.to_index = i;
return(1);
}
}
else if(bias==BIAS_DOWN)
{
if(high_i < low_i2 - SymbolInfoDouble(_Symbol,SYMBOL_POINT)*0.0)
{
foundZone.top = high_i2; // caution: for clarity we set top>bottom
foundZone.bottom = low_i;
// normalize so top>bottom
double t = MathMax(high_i, low_i2);
double b = MathMin(high_i, low_i2);
foundZone.top = t; foundZone.bottom = b;
foundZone.from_index = i+2;
foundZone.to_index = i;
return(1);
}
}
}
return(0);
}
//+------------------------------------------------------------------+
int FindM5FVGAtZone(Zone &h1zone, DailyBias bias, Zone &m5zone)
{
// Scan M5 recent bars. We look for small FVGs within the price range of H1 FVG
int limit = M5_lookback;
for(int i=1;i<limit-2;i++)
{
double high_i = iHigh(_Symbol,PERIOD_M5,i);
double low_i = iLow(_Symbol,PERIOD_M5,i);
double high_i2 = iHigh(_Symbol,PERIOD_M5,i+2);
double low_i2 = iLow(_Symbol,PERIOD_M5,i+2);
if(bias==BIAS_UP)
{
// bullish M5 FVG
if(low_i > high_i2)
{
double top = low_i;
double bottom = high_i2;
// check overlap with H1 zone
if(bottom <= h1zone.top && top >= h1zone.bottom)
{
m5zone.top = top; m5zone.bottom = bottom; return 1;
}
foundZone.top = low_i; foundZone.bottom = high_i2;
foundZone.from_index = i+2; foundZone.to_index = i;
return 1;
}
}
else if(bias==BIAS_DOWN)
{
if(high_i < low_i2)
{
double top = low_i2; double bottom = high_i;
if(bottom <= h1zone.top && top >= h1zone.bottom)
double t = MathMax(high_i, low_i2);
double b = MathMin(high_i, low_i2);
foundZone.top = t; foundZone.bottom = b;
foundZone.from_index = i+2; foundZone.to_index = i;
return 1;
}
}
}
return 0;
}
//+------------------------------------------------------------------+
int FindLowTfFvg(Zone &midTfZone, DailyBias bias, Zone &lowTfZone)
{
int limit = Low_TF_FVG_lookback;
for(int i=1;i<limit-2;i++)
{
double high_i = iHigh(_Symbol, TF_Low, i);
double low_i = iLow (_Symbol, TF_Low, i);
double high_i2 = iHigh(_Symbol, TF_Low, i+2);
double low_i2 = iLow (_Symbol, TF_Low, i+2);
if(high_i<=0 || low_i<=0 || high_i2<=0 || low_i2<=0) continue;
if(bias==BIAS_UP)
{
if(low_i > high_i2)
{
double top = low_i; double bottom = high_i2;
if(bottom <= midTfZone.top && top >= midTfZone.bottom)
{
m5zone.top = top; m5zone.bottom = bottom; return 1;
lowTfZone.top = top; lowTfZone.bottom = bottom; return 1;
}
}
}
else
{
if(high_i < low_i2)
{
double top = low_i2; double bottom = high_i;
if(bottom <= midTfZone.top && top >= midTfZone.bottom)
{
lowTfZone.top = top; lowTfZone.bottom = bottom; return 1;
}
}
}
@@ -234,55 +217,51 @@ int FindM5FVGAtZone(Zone &h1zone, DailyBias bias, Zone &m5zone)
}
//+------------------------------------------------------------------+
bool DetectM5MSS(int &mssType, double &mssPrice)
bool DetectLowTfMSS(int &mssType, double &mssPrice)
{
// Rất đơn giản: nếu price vừa break swing high -> bullish MSS (return 1)
// nếu price just break swing low -> bearish MSS (return -1)
// Implementation: compute last 3 swing highs and lows and check current candle
// get last swing high (local maxima) and swing low (local minima) in M5
double lastSwingHigh = -DBL_MAX; int idxHigh=-1;
double lastSwingLow = DBL_MAX; int idxLow=-1;
double lastSwingLow = DBL_MAX; int idxLow=-1;
int look = 50;
for(int i=2;i<look;i++)
{
double h = iHigh(_Symbol,PERIOD_M5,i);
double l = iLow(_Symbol,PERIOD_M5,i);
if(h > lastSwingHigh) { lastSwingHigh=h; idxHigh=i; }
if(l < lastSwingLow) { lastSwingLow=l; idxLow=i; }
double h = iHigh(_Symbol, TF_Low, i);
double l = iLow (_Symbol, TF_Low, i);
if(h>0 && h > lastSwingHigh) { lastSwingHigh=h; idxHigh=i; }
if(l>0 && l < lastSwingLow) { lastSwingLow=l; idxLow=i; }
}
double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
// bullish MSS detection: current price (ask) > lastSwingHigh
if(ask > lastSwingHigh)
{
mssType = 1; mssPrice = lastSwingHigh; return true;
}
if(bid < lastSwingLow)
{
mssType = -1; mssPrice = lastSwingLow; return true;
}
if(ask > lastSwingHigh) { mssType=1; mssPrice = lastSwingHigh; return true; }
if(bid < lastSwingLow) { mssType=-1; mssPrice = lastSwingLow; return true; }
return false;
}
//+------------------------------------------------------------------+
double CalculateLotForRisk(double entryPrice, double stopPrice)
{
// Tính lot sao cho khoảng cách entry->SL tương ứng RiskPerTrade% equity
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskMoney = equity * (RiskPerTrade/100.0);
double point = SymbolInfoDouble(_Symbol,SYMBOL_POINT);
double tickValue = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
// Fallback: if broker returns 0 for tickValue/tickSize, estimate valuePerPoint using contract size:
double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
if(tickValue<=0 || tickSize<=0)
{
// rough fallback: assume 1 lot -> contractSize * point movement value (this might be instrument-specific)
tickValue = contractSize; tickSize = 1.0;
}
if(point<=0 || tickValue<=0 || tickSize<=0) { Print("Invalid symbol params for lot calc"); return 0; }
double stopPoints = MathAbs(entryPrice - stopPrice)/point;
if(stopPoints <= 0) return 0;
double valuePerPoint = tickValue * (point / tickSize);
if(valuePerPoint <= 0) { Print("valuePerPoint invalid"); return 0; }
double rawLot = riskMoney / (stopPoints * valuePerPoint);
double minLot = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
@@ -300,10 +279,6 @@ double CalculateLotForRisk(double entryPrice, double stopPrice)
return lot;
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| HasActiveOrders: kiểm tra xem EA đã có position hoặc pending order |
//| - Trả về true nếu tồn tại position mở hoặc pending order cùng MagicNumber trên symbol
//+------------------------------------------------------------------+
bool HasActiveOrders()
{
@@ -332,15 +307,13 @@ bool HasActiveOrders()
return false;
}
//+------------------------------------------------------------------+
void PlaceLimitOrder(int side, double price, double sl, double tp, double lot)
{
// Create and send a pending limit order via MqlTradeRequest/OrderSend
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
MqlTradeRequest request; MqlTradeResult result;
ZeroMemory(request); ZeroMemory(result);
request.action = TRADE_ACTION_PENDING; // we're placing a pending order
request.action = TRADE_ACTION_PENDING;
request.symbol = _Symbol;
request.volume = lot;
request.price = price;
@@ -349,64 +322,35 @@ void PlaceLimitOrder(int side, double price, double sl, double tp, double lot)
request.deviation = 20;
request.magic = MagicNumber;
request.comment = (side==POSITION_TYPE_BUY) ? "ICT_BUY_LIMIT" : "ICT_SELL_LIMIT";
request.type_time = ORDER_TIME_GTC; // good-till-cancelled
request.type_filling = ORDER_FILLING_RETURN; // safe default filling
request.type_time = ORDER_TIME_GTC;
request.type_filling = ORDER_FILLING_RETURN;
request.type = (side==POSITION_TYPE_BUY) ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_SELL_LIMIT;
// set exact pending type
if(side == POSITION_TYPE_BUY)
request.type = ORDER_TYPE_BUY_LIMIT;
else
request.type = ORDER_TYPE_SELL_LIMIT;
// Send the order request to the server
if(!OrderSend(request,result))
{
// OrderSend can fail immediately (client-side) — print result for debugging
PrintFormat("OrderSend() failed: retcode=%d comment=%s", result.retcode, result.comment);
return;
}
// Check the server response (retcode)
if(result.retcode == TRADE_RETCODE_DONE || result.retcode == TRADE_RETCODE_PLACED)
{
ulong ticket = result.order;
PrintFormat("Placed pending order: ticket=%I64u side=%d price=%.5f lot=%.2f SL=%.5f TP=%.5f",
PrintFormat("Placed pending order ticket=%I64u side=%d price=%.5f lot=%.2f SL=%.5f TP=%.5f",
ticket, side, price, lot, sl, tp);
// Draw SL/TP and entry lines on chart for visual confirmation
string name_sl = StringFormat("ICT_SL_%I64u", ticket);
string name_tp = StringFormat("ICT_TP_%I64u", ticket);
string name_entry = StringFormat("ICT_ENTRY_%I64u", ticket);
// Create horizontal lines at SL, TP and entry price
if(ObjectFind(0, name_sl) == -1)
{
ObjectCreate(0, name_sl, OBJ_HLINE, 0, 0, sl);
ObjectSetDouble(0, name_sl, OBJPROP_PRICE, sl);
ObjectSetInteger(0, name_sl, OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, name_sl, OBJPROP_WIDTH, 1);
ObjectSetString(0, name_sl, OBJPROP_TEXT, "SL: " + DoubleToString(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
}
if(ObjectFind(0, name_tp) == -1)
{
ObjectCreate(0, name_tp, OBJ_HLINE, 0, 0, tp);
ObjectSetDouble(0, name_tp, OBJPROP_PRICE, tp);
ObjectSetInteger(0, name_tp, OBJPROP_COLOR, clrLime);
ObjectSetInteger(0, name_tp, OBJPROP_WIDTH, 1);
ObjectSetString(0, name_tp, OBJPROP_TEXT, "TP: " + DoubleToString(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
}
if(ObjectFind(0, name_entry) == -1)
{
ObjectCreate(0, name_entry, OBJ_HLINE, 0, 0, price);
ObjectSetDouble(0, name_entry, OBJPROP_PRICE, price);
ObjectSetInteger(0, name_entry, OBJPROP_COLOR, clrYellow);
ObjectSetInteger(0, name_entry, OBJPROP_STYLE, STYLE_DOT);
ObjectSetString(0, name_entry, OBJPROP_TEXT, "Entry: " + DoubleToString(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
}
// draw lines
string n_sl = StringFormat("ICT_SL_%I64u", ticket);
string n_tp = StringFormat("ICT_TP_%I64u", ticket);
string n_en = StringFormat("ICT_ENTRY_%I64u", ticket);
if(ObjectFind(0,n_sl) == -1) ObjectCreate(0,n_sl,OBJ_HLINE,0,0,sl);
if(ObjectFind(0,n_tp) == -1) ObjectCreate(0,n_tp,OBJ_HLINE,0,0,tp);
if(ObjectFind(0,n_en) == -1) ObjectCreate(0,n_en,OBJ_HLINE,0,0,price);
ObjectSetInteger(0,n_sl,OBJPROP_COLOR,clrRed);
ObjectSetInteger(0,n_tp,OBJPROP_COLOR,clrLime);
ObjectSetInteger(0,n_en,OBJPROP_COLOR,clrYellow);
ObjectSetString(0,n_sl,OBJPROP_TEXT,"SL: "+DoubleToString(sl,(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS)));
}
else
{
// Broker may reject or modify pending order; log retcode & comment
PrintFormat("OrderSend returned retcode=%d comment=%s", result.retcode, result.comment);
}
}
@@ -414,28 +358,13 @@ void PlaceLimitOrder(int side, double price, double sl, double tp, double lot)
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Xoá các đối tượng SL/TP/ENTRY còn lại trên chart khi EA đóng
int total = ObjectsTotal(0);
for(int i = total - 1; i >= 0; i--)
for(int i=total-1;i>=0;i--)
{
string name = ObjectName(0, i);
if(StringFind(name, "ICT_ENTRY_") == 0 ||
StringFind(name, "ICT_SL_") == 0 ||
StringFind(name, "ICT_TP_") == 0)
{
ObjectDelete(0, name);
}
string name = ObjectName(0,i);
if(StringFind(name,"ICT_ENTRY_")==0 || StringFind(name,"ICT_SL_")==0 || StringFind(name,"ICT_TP_")==0)
ObjectDelete(0,name);
}
Print("EA deinitialized — cleaned objects.");
Print("EA deinitialized - cleaned objects.");
}
//+------------------------------------------------------------------+
// NOTES / CAVEATS:
// - Đây là bản mẫu triển khai logic theo mô tả của bạn, nhưng có nhiều điểm được đơn giản hóa
// (phát hiện FVG và MSS là dạng heuristic đơn giản). Nên backtest kỹ và điều chỉnh
// - Bạn có thể muốn vẽ các zone (OBJ_RECTANGLE) để debug và quan sát H1/M5 FVG
// - Tinh chỉnh: lookback, cách xác định FVG, buffer SL, ADX filter, điều kiện trước khi đặt lệnh
// - EA hiện đặt 1 pending limit khi điều kiện thỏa. Nó không kiểm tra overlap với các pending/positions hiện tại
// - Hãy chạy trên demo/backtest trước khi dùng real