Đặt lệnh limit khi giá touch fvg
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "SimpleFVG/Config.mqh"
|
||||
#include "SimpleFVG/Trend.mqh"
|
||||
#include "SimpleFVG/FVG.mqh"
|
||||
#include "SimpleFVG/Trade.mqh"
|
||||
#include "SimpleFVG/Drawing.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -63,5 +64,6 @@ void OnTick()
|
||||
|
||||
TrendUpdate();
|
||||
FVGUpdate();
|
||||
ManageFVGTrades();
|
||||
DrawAll();
|
||||
}
|
||||
|
||||
@@ -6,30 +6,39 @@
|
||||
#define __SIMPLE_FVG_CONFIG_MQH__
|
||||
|
||||
//--- Step 0: Asset & Timeframe ---
|
||||
input string InpSymbol = ""; // Symbol (empty = current chart)
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; // Analysis Timeframe
|
||||
input string InpSymbol = "";
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1;
|
||||
|
||||
//--- Step 1: Trend (EMA) ---
|
||||
input int InpEMAFastPeriod = 34; // EMA Fast Period
|
||||
input int InpEMASlowPeriod = 89; // EMA Slow Period
|
||||
input int InpEMAFastPeriod = 34;
|
||||
input int InpEMASlowPeriod = 89;
|
||||
|
||||
//--- Step 2: FVG Detection ---
|
||||
input int InpFVGLookbackBars = 100; // FVG: Lookback range (bars)
|
||||
input int InpFVGMaxAgeBars = 50; // FVG: Max age before expiry (bars)
|
||||
input double InpFVGMinBodyPct = 50.0; // FVG: Min body % of impulse candle
|
||||
input double InpFVGMinSizePoints = 0; // FVG: Min gap size (points, 0=any)
|
||||
input int InpFVGLookbackBars = 100;
|
||||
input int InpFVGMaxAgeBars = 50;
|
||||
input double InpFVGMinBodyPct = 50.0;
|
||||
input double InpFVGMinSizePoints = 0;
|
||||
input double InpFVGTouchedPercent = 40.0;
|
||||
|
||||
//--- Step 3: Trading ---
|
||||
input bool InpTradeEnabled = true;
|
||||
input double InpRiskPercentPerR = 1.0;
|
||||
input double InpRRRatio = 2.2;
|
||||
input int InpMaxLimitOrders = 3;
|
||||
input int InpLimitMaxAgeBars = 24;
|
||||
input long InpEAMagic = 123456;
|
||||
|
||||
//--- Step 4: Drawing ---
|
||||
input color InpColorBullFVG = C'30,80,140'; // Color: Bullish FVG zone
|
||||
input color InpColorBearFVG = C'140,30,20'; // Color: Bearish FVG zone
|
||||
input color InpColorMitigatedFVG = C'60,60,60'; // Color: Mitigated FVG zone
|
||||
input color InpColorEMAFast = clrDodgerBlue; // Color: EMA Fast line
|
||||
input color InpColorEMASlow = clrOrangeRed; // Color: EMA Slow line
|
||||
input bool InpShowPanel = true; // Draw: Show info panel
|
||||
input bool InpShowFVGLabels = true; // Draw: Show FVG labels
|
||||
input color InpColorBullFVG = C'30,80,140';
|
||||
input color InpColorBearFVG = C'140,30,20';
|
||||
input color InpColorMitigatedFVG = C'60,60,60';
|
||||
input color InpColorEMAFast = clrDodgerBlue;
|
||||
input color InpColorEMASlow = clrOrangeRed;
|
||||
input bool InpShowPanel = true;
|
||||
input bool InpShowFVGLabels = true;
|
||||
|
||||
//--- Debug ---
|
||||
input bool InpDebugLog = true; // Debug: Enable logging
|
||||
input bool InpDebugLog = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Enums |
|
||||
@@ -47,6 +56,14 @@ enum ENUM_FVG_TYPE
|
||||
FVG_BEARISH = -1
|
||||
};
|
||||
|
||||
enum ENUM_FVG_STATUS
|
||||
{
|
||||
ACTIVE = 0, // Giá chưa quay lại test
|
||||
TOUCHED = 1, // Giá đã retest >= threshold %
|
||||
MITIGATED = 2, // Giá đâm xuyên qua vùng
|
||||
EXPIRED = 3 // Hết hạn, không còn vẽ/đếm
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Constants |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Config.mqh"
|
||||
#include "Trend.mqh"
|
||||
#include "FVG.mqh"
|
||||
#include "Trade.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Object-creation helpers |
|
||||
@@ -75,6 +76,81 @@ void DrawTextOnChart(string name, datetime t, double price,
|
||||
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Draw limit orders (TradingView-style lines) |
|
||||
//+------------------------------------------------------------------+
|
||||
void DrawLimitOrders()
|
||||
{
|
||||
ObjectsDeleteAll(0, EA_PREFIX + "ORD_");
|
||||
|
||||
string symbol = GetTradeSymbol();
|
||||
datetime currentBar = iTime(symbol, InpTimeframe, 0);
|
||||
|
||||
for(int i = 0; i < OrdersTotal(); i++)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!OrderSelect(ticket)) continue;
|
||||
|
||||
if((long)OrderGetInteger(ORDER_MAGIC) != InpEAMagic) continue;
|
||||
if((string)OrderGetString(ORDER_SYMBOL) != symbol) continue;
|
||||
|
||||
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||||
if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT)
|
||||
continue;
|
||||
|
||||
double entryPrice = OrderGetDouble(ORDER_PRICE_OPEN);
|
||||
double slPrice = OrderGetDouble(ORDER_SL);
|
||||
double tpPrice = OrderGetDouble(ORDER_TP);
|
||||
double volume = OrderGetDouble(ORDER_VOLUME_CURRENT);
|
||||
datetime setupTime = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
|
||||
|
||||
string side = (type == ORDER_TYPE_BUY_LIMIT) ? "BUY" : "SELL";
|
||||
color entryClr = (type == ORDER_TYPE_BUY_LIMIT) ? clrDeepSkyBlue : clrOrangeRed;
|
||||
color slClr = clrRed;
|
||||
color tpClr = clrLime;
|
||||
|
||||
string id = IntegerToString((int)ticket);
|
||||
|
||||
// Entry line
|
||||
DrawTrendLine(
|
||||
EA_PREFIX + "ORD_ENTRY_" + id,
|
||||
setupTime, entryPrice,
|
||||
currentBar, entryPrice,
|
||||
entryClr, STYLE_SOLID, 2
|
||||
);
|
||||
|
||||
// SL line
|
||||
DrawTrendLine(
|
||||
EA_PREFIX + "ORD_SL_" + id,
|
||||
setupTime, slPrice,
|
||||
currentBar, slPrice,
|
||||
slClr, STYLE_DASH, 1
|
||||
);
|
||||
|
||||
// TP line
|
||||
DrawTrendLine(
|
||||
EA_PREFIX + "ORD_TP_" + id,
|
||||
setupTime, tpPrice,
|
||||
currentBar, tpPrice,
|
||||
tpClr, STYLE_DASH, 1
|
||||
);
|
||||
|
||||
// Label near entry
|
||||
string labelText = StringFormat("%s LIMIT %.2f | Vol %.2f | RR %.1f",
|
||||
side, entryPrice, volume, InpRRRatio);
|
||||
|
||||
DrawTextOnChart(
|
||||
EA_PREFIX + "ORD_LBL_" + id,
|
||||
setupTime,
|
||||
entryPrice,
|
||||
labelText,
|
||||
entryClr,
|
||||
8,
|
||||
ANCHOR_LEFT_LOWER
|
||||
);
|
||||
}
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
//| Draw all active FVG zones as rectangles on the chart |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -87,14 +163,14 @@ void DrawFVGZones()
|
||||
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(!g_FVGZones[i].isActive)
|
||||
if(IsZoneExpired(g_FVGZones[i]))
|
||||
continue;
|
||||
|
||||
string zoneId = IntegerToString(i);
|
||||
|
||||
//--- Zone color depends on type and mitigation status ---
|
||||
//--- Zone color depends on type and FVG status ---
|
||||
color zoneColor;
|
||||
if(g_FVGZones[i].isMitigated)
|
||||
if(IsZoneMitigated(g_FVGZones[i]))
|
||||
zoneColor = InpColorMitigatedFVG;
|
||||
else if(g_FVGZones[i].type == FVG_BULLISH)
|
||||
zoneColor = InpColorBullFVG;
|
||||
@@ -111,7 +187,7 @@ void DrawFVGZones()
|
||||
|
||||
//--- Draw mid-line (50% of FVG) ---
|
||||
double midPrice = (g_FVGZones[i].upperEdge + g_FVGZones[i].lowerEdge) / 2.0;
|
||||
color midColor = g_FVGZones[i].isMitigated ? clrDimGray : clrGold;
|
||||
color midColor = IsZoneMitigated(g_FVGZones[i]) ? clrDimGray : clrGold;
|
||||
|
||||
DrawTrendLine(
|
||||
EA_PREFIX + "FVG_MID_" + zoneId,
|
||||
@@ -125,11 +201,17 @@ void DrawFVGZones()
|
||||
{
|
||||
string typeStr = (g_FVGZones[i].type == FVG_BULLISH) ? "BULL" : "BEAR";
|
||||
string arrow = (g_FVGZones[i].type == FVG_BULLISH) ? "▲" : "▼";
|
||||
string mitigStr = g_FVGZones[i].isMitigated ? " [MITIGATED]" : "";
|
||||
string stateStr = "";
|
||||
if(IsZoneTouched(g_FVGZones[i]))
|
||||
stateStr = " [TOUCHED]";
|
||||
else if(IsZoneMitigated(g_FVGZones[i]))
|
||||
stateStr = " [MITIGATED]";
|
||||
|
||||
color labelColor;
|
||||
if(g_FVGZones[i].isMitigated)
|
||||
if(IsZoneMitigated(g_FVGZones[i]))
|
||||
labelColor = clrDimGray;
|
||||
else if(IsZoneTouched(g_FVGZones[i]))
|
||||
labelColor = clrYellow;
|
||||
else if(g_FVGZones[i].type == FVG_BULLISH)
|
||||
labelColor = clrDeepSkyBlue;
|
||||
else
|
||||
@@ -142,7 +224,7 @@ void DrawFVGZones()
|
||||
StringFormat("%s %s [%.2f–%.2f]%s",
|
||||
arrow, typeStr,
|
||||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge,
|
||||
mitigStr),
|
||||
stateStr),
|
||||
labelColor, 7, ANCHOR_LEFT_LOWER
|
||||
);
|
||||
}
|
||||
@@ -190,6 +272,7 @@ void DrawAll()
|
||||
{
|
||||
DrawFVGZones();
|
||||
DrawInfoPanel();
|
||||
DrawLimitOrders();
|
||||
ChartRedraw(0);
|
||||
}
|
||||
|
||||
|
||||
+105
-39
@@ -22,13 +22,13 @@
|
||||
//+------------------------------------------------------------------+
|
||||
struct FVGZone
|
||||
{
|
||||
bool isActive;
|
||||
ENUM_FVG_TYPE type;
|
||||
double upperEdge;
|
||||
double lowerEdge;
|
||||
datetime createdTime;
|
||||
int ageInBars;
|
||||
bool isMitigated;
|
||||
ENUM_FVG_TYPE type;
|
||||
ENUM_FVG_STATUS status;
|
||||
double upperEdge;
|
||||
double lowerEdge;
|
||||
double slReferencePrice;
|
||||
datetime createdTime;
|
||||
int ageInBars;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -49,8 +49,34 @@ void FVGInit()
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if impulse candle has strong body (body/range >= threshold) |
|
||||
//| Helper: status helpers & impulse strength |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsZoneActive(const FVGZone &z)
|
||||
{
|
||||
return (z.status != EXPIRED);
|
||||
}
|
||||
|
||||
bool IsZoneLive(const FVGZone &z)
|
||||
{
|
||||
return (z.status == ACTIVE || z.status == TOUCHED);
|
||||
}
|
||||
|
||||
bool IsZoneMitigated(const FVGZone &z)
|
||||
{
|
||||
return (z.status == MITIGATED);
|
||||
}
|
||||
|
||||
bool IsZoneTouched(const FVGZone &z)
|
||||
{
|
||||
return (z.status == TOUCHED);
|
||||
}
|
||||
|
||||
bool IsZoneExpired(const FVGZone &z)
|
||||
{
|
||||
return (z.status == EXPIRED);
|
||||
}
|
||||
|
||||
// Impulse candle strength: body/range >= threshold
|
||||
bool IsImpulseCandleStrong(string symbol, ENUM_TIMEFRAMES tf, int shift)
|
||||
{
|
||||
double high = iHigh(symbol, tf, shift);
|
||||
@@ -69,13 +95,13 @@ bool IsImpulseCandleStrong(string symbol, ENUM_TIMEFRAMES tf, int shift)
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if this FVG timestamp already exists in our array |
|
||||
//| Check if this FVG timestamp already exists in our array |
|
||||
//+------------------------------------------------------------------+
|
||||
bool FVGAlreadyTracked(datetime fvgTime, ENUM_FVG_TYPE fvgType)
|
||||
{
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(g_FVGZones[i].isActive
|
||||
if(IsZoneActive(g_FVGZones[i])
|
||||
&& g_FVGZones[i].createdTime == fvgTime
|
||||
&& g_FVGZones[i].type == fvgType)
|
||||
return true;
|
||||
@@ -84,9 +110,13 @@ bool FVGAlreadyTracked(datetime fvgTime, ENUM_FVG_TYPE fvgType)
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Add a new FVG zone to the array |
|
||||
//| Add a new FVG zone to the array |
|
||||
//+------------------------------------------------------------------+
|
||||
bool AddFVGZone(ENUM_FVG_TYPE type, double upper, double lower, datetime time)
|
||||
bool AddFVGZone(ENUM_FVG_TYPE type,
|
||||
double upper,
|
||||
double lower,
|
||||
double slReference,
|
||||
datetime time)
|
||||
{
|
||||
if(g_FVGCount >= MAX_FVG_SLOTS)
|
||||
return false;
|
||||
@@ -99,13 +129,13 @@ bool AddFVGZone(ENUM_FVG_TYPE type, double upper, double lower, datetime time)
|
||||
}
|
||||
|
||||
ZeroMemory(g_FVGZones[g_FVGCount]);
|
||||
g_FVGZones[g_FVGCount].isActive = true;
|
||||
g_FVGZones[g_FVGCount].type = type;
|
||||
g_FVGZones[g_FVGCount].upperEdge = upper;
|
||||
g_FVGZones[g_FVGCount].lowerEdge = lower;
|
||||
g_FVGZones[g_FVGCount].createdTime = time;
|
||||
g_FVGZones[g_FVGCount].ageInBars = 0;
|
||||
g_FVGZones[g_FVGCount].isMitigated = false;
|
||||
g_FVGZones[g_FVGCount].type = type;
|
||||
g_FVGZones[g_FVGCount].status = ACTIVE;
|
||||
g_FVGZones[g_FVGCount].upperEdge = upper;
|
||||
g_FVGZones[g_FVGCount].lowerEdge = lower;
|
||||
g_FVGZones[g_FVGCount].slReferencePrice = slReference;
|
||||
g_FVGZones[g_FVGCount].createdTime = time;
|
||||
g_FVGZones[g_FVGCount].ageInBars = 0;
|
||||
g_FVGCount++;
|
||||
|
||||
if(InpDebugLog)
|
||||
@@ -148,7 +178,10 @@ void ScanForNewFVGs()
|
||||
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
|
||||
{
|
||||
if(!FVGAlreadyTracked(fvgTime, FVG_BULLISH))
|
||||
AddFVGZone(FVG_BULLISH, candleC_Low, candleA_High, fvgTime);
|
||||
{
|
||||
double slRef = iLow(symbol, InpTimeframe, shiftB); // bar2.low
|
||||
AddFVGZone(FVG_BULLISH, candleC_Low, candleA_High, slRef, fvgTime);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Bearish FVG: gap between C.High and A.Low ---
|
||||
@@ -157,7 +190,10 @@ void ScanForNewFVGs()
|
||||
&& IsImpulseCandleStrong(symbol, InpTimeframe, shiftB))
|
||||
{
|
||||
if(!FVGAlreadyTracked(fvgTime, FVG_BEARISH))
|
||||
AddFVGZone(FVG_BEARISH, candleA_Low, candleC_High, fvgTime);
|
||||
{
|
||||
double slRef = iHigh(symbol, InpTimeframe, shiftB); // bar2.high
|
||||
AddFVGZone(FVG_BEARISH, candleA_Low, candleC_High, slRef, fvgTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,28 +207,58 @@ void CheckMitigationStatus()
|
||||
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(!g_FVGZones[i].isActive || g_FVGZones[i].isMitigated)
|
||||
if(IsZoneMitigated(g_FVGZones[i]) || !IsZoneActive(g_FVGZones[i]))
|
||||
continue;
|
||||
|
||||
double lastLow = iLow (symbol, InpTimeframe, 1);
|
||||
double lastHigh = iHigh(symbol, InpTimeframe, 1);
|
||||
|
||||
// Bullish FVG mitigated: price drops through lower edge
|
||||
if(g_FVGZones[i].type == FVG_BULLISH && lastLow <= g_FVGZones[i].lowerEdge)
|
||||
double zoneHeight = g_FVGZones[i].upperEdge - g_FVGZones[i].lowerEdge;
|
||||
if(zoneHeight <= 0)
|
||||
continue;
|
||||
|
||||
//--- Bullish FVG: price comes down into the gap ---
|
||||
if(g_FVGZones[i].type == FVG_BULLISH)
|
||||
{
|
||||
g_FVGZones[i].isMitigated = true;
|
||||
if(InpDebugLog)
|
||||
PrintFormat("[FVG] MITIGATED BULL [%.5f – %.5f]",
|
||||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
||||
// Mitigated: price drops through lower edge
|
||||
if(lastLow <= g_FVGZones[i].lowerEdge)
|
||||
{
|
||||
g_FVGZones[i].status = MITIGATED;
|
||||
if(InpDebugLog)
|
||||
PrintFormat("[FVG] MITIGATED BULL [%.5f – %.5f]",
|
||||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Touched: price has filled at least X% of gap, but not fully
|
||||
double deepestPrice = MathMin(lastLow, g_FVGZones[i].upperEdge);
|
||||
deepestPrice = MathMax(deepestPrice, g_FVGZones[i].lowerEdge);
|
||||
double fillPct = (g_FVGZones[i].upperEdge - deepestPrice) / zoneHeight * 100.0;
|
||||
|
||||
if(g_FVGZones[i].status == ACTIVE && fillPct >= InpFVGTouchedPercent)
|
||||
g_FVGZones[i].status = TOUCHED;
|
||||
}
|
||||
|
||||
// Bearish FVG mitigated: price rises through upper edge
|
||||
if(g_FVGZones[i].type == FVG_BEARISH && lastHigh >= g_FVGZones[i].upperEdge)
|
||||
//--- Bearish FVG: price goes up into the gap ---
|
||||
if(g_FVGZones[i].type == FVG_BEARISH)
|
||||
{
|
||||
g_FVGZones[i].isMitigated = true;
|
||||
if(InpDebugLog)
|
||||
PrintFormat("[FVG] MITIGATED BEAR [%.5f – %.5f]",
|
||||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
||||
// Mitigated: price rises through upper edge
|
||||
if(lastHigh >= g_FVGZones[i].upperEdge)
|
||||
{
|
||||
g_FVGZones[i].status = MITIGATED;
|
||||
if(InpDebugLog)
|
||||
PrintFormat("[FVG] MITIGATED BEAR [%.5f – %.5f]",
|
||||
g_FVGZones[i].lowerEdge, g_FVGZones[i].upperEdge);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Touched: price has filled at least X% of gap, but not fully
|
||||
double highestPrice = MathMax(lastHigh, g_FVGZones[i].lowerEdge);
|
||||
highestPrice = MathMin(highestPrice, g_FVGZones[i].upperEdge);
|
||||
double fillPct = (highestPrice - g_FVGZones[i].lowerEdge) / zoneHeight * 100.0;
|
||||
|
||||
if(g_FVGZones[i].status == ACTIVE && fillPct >= InpFVGTouchedPercent)
|
||||
g_FVGZones[i].status = TOUCHED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,13 +270,13 @@ void ExpireOldFVGs()
|
||||
{
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(!g_FVGZones[i].isActive) continue;
|
||||
if(!IsZoneActive(g_FVGZones[i])) continue;
|
||||
|
||||
g_FVGZones[i].ageInBars++;
|
||||
|
||||
if(g_FVGZones[i].ageInBars > InpFVGMaxAgeBars)
|
||||
{
|
||||
g_FVGZones[i].isActive = false;
|
||||
g_FVGZones[i].status = EXPIRED;
|
||||
if(InpDebugLog)
|
||||
PrintFormat("[FVG] EXPIRED %s [%.5f – %.5f] age=%d bars",
|
||||
(g_FVGZones[i].type == FVG_BULLISH) ? "BULL" : "BEAR",
|
||||
@@ -228,7 +294,7 @@ void CompactFVGArray()
|
||||
int writeIndex = 0;
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(g_FVGZones[i].isActive)
|
||||
if(IsZoneActive(g_FVGZones[i]))
|
||||
{
|
||||
if(writeIndex != i)
|
||||
g_FVGZones[writeIndex] = g_FVGZones[i];
|
||||
@@ -257,7 +323,7 @@ int CountActiveFVGs(ENUM_FVG_TYPE type)
|
||||
int count = 0;
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(g_FVGZones[i].isActive && g_FVGZones[i].type == type)
|
||||
if(IsZoneActive(g_FVGZones[i]) && g_FVGZones[i].type == type)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
@@ -268,7 +334,7 @@ int CountMitigatedFVGs()
|
||||
int count = 0;
|
||||
for(int i = 0; i < g_FVGCount; i++)
|
||||
{
|
||||
if(g_FVGZones[i].isActive && g_FVGZones[i].isMitigated)
|
||||
if(IsZoneMitigated(g_FVGZones[i]))
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trade.mqh – Step 3: Trade management for FVG strategy |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef __SIMPLE_FVG_TRADE_MQH__
|
||||
#define __SIMPLE_FVG_TRADE_MQH__
|
||||
|
||||
#include "Config.mqh"
|
||||
#include "Trend.mqh"
|
||||
#include "FVG.mqh"
|
||||
|
||||
//--- Trading config nằm trong Config.mqh (Step 3)
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Internal helpers |
|
||||
//+------------------------------------------------------------------+
|
||||
int CountOurPositions()
|
||||
{
|
||||
int count = 0;
|
||||
for(int i = 0; i < PositionsTotal(); i++)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) == InpEAMagic &&
|
||||
(string)PositionGetString(POSITION_SYMBOL) == GetTradeSymbol())
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int CountOurLimitOrders()
|
||||
{
|
||||
int count = 0;
|
||||
for(int i = 0; i < OrdersTotal(); i++)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!OrderSelect(ticket)) continue;
|
||||
|
||||
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||||
long magic = (long)OrderGetInteger(ORDER_MAGIC);
|
||||
string symbol = (string)OrderGetString(ORDER_SYMBOL);
|
||||
|
||||
if(magic == InpEAMagic &&
|
||||
symbol == GetTradeSymbol() &&
|
||||
(type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void CancelAllOurLimitOrders()
|
||||
{
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!OrderSelect(ticket)) continue;
|
||||
|
||||
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||||
long magic = (long)OrderGetInteger(ORDER_MAGIC);
|
||||
string symbol = (string)OrderGetString(ORDER_SYMBOL);
|
||||
|
||||
if(magic == InpEAMagic &&
|
||||
symbol == GetTradeSymbol() &&
|
||||
(type == ORDER_TYPE_BUY_LIMIT || type == ORDER_TYPE_SELL_LIMIT))
|
||||
{
|
||||
MqlTradeRequest req;
|
||||
MqlTradeResult res;
|
||||
ZeroMemory(req);
|
||||
ZeroMemory(res);
|
||||
|
||||
req.action = TRADE_ACTION_REMOVE;
|
||||
req.order = ticket;
|
||||
|
||||
if(!OrderSend(req, res) && InpDebugLog)
|
||||
PrintFormat("[Trade] Failed to cancel order #%I64u, retcode=%d",
|
||||
ticket, res.retcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HasLimitOrderAtPrice(ENUM_ORDER_TYPE orderType, double entryPrice)
|
||||
{
|
||||
double eps = 2 * _Point;
|
||||
for(int i = 0; i < OrdersTotal(); i++)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!OrderSelect(ticket)) continue;
|
||||
|
||||
if((long)OrderGetInteger(ORDER_MAGIC) != InpEAMagic) continue;
|
||||
if((string)OrderGetString(ORDER_SYMBOL) != GetTradeSymbol()) continue;
|
||||
|
||||
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||||
if(type != orderType) continue;
|
||||
|
||||
double price = OrderGetDouble(ORDER_PRICE_OPEN);
|
||||
if(MathAbs(price - entryPrice) <= eps)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hủy các limit order quá "già" (tính theo số bar trên InpTimeframe)
|
||||
void CancelStaleLimitOrders()
|
||||
{
|
||||
if(InpLimitMaxAgeBars <= 0)
|
||||
return;
|
||||
|
||||
datetime now = TimeCurrent();
|
||||
if(now == 0)
|
||||
return;
|
||||
|
||||
int tfSeconds = PeriodSeconds(InpTimeframe);
|
||||
if(tfSeconds <= 0)
|
||||
return;
|
||||
|
||||
int maxAgeSeconds = InpLimitMaxAgeBars * tfSeconds;
|
||||
|
||||
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = OrderGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!OrderSelect(ticket)) continue;
|
||||
|
||||
ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
|
||||
long magic = (long)OrderGetInteger(ORDER_MAGIC);
|
||||
string symbol = (string)OrderGetString(ORDER_SYMBOL);
|
||||
|
||||
if(magic != InpEAMagic) continue;
|
||||
if(symbol != GetTradeSymbol()) continue;
|
||||
if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT)
|
||||
continue;
|
||||
|
||||
datetime setupTime = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
|
||||
int ageSeconds = int(now - setupTime);
|
||||
if(ageSeconds < maxAgeSeconds)
|
||||
continue;
|
||||
|
||||
MqlTradeRequest req;
|
||||
MqlTradeResult res;
|
||||
ZeroMemory(req);
|
||||
ZeroMemory(res);
|
||||
|
||||
req.action = TRADE_ACTION_REMOVE;
|
||||
req.order = ticket;
|
||||
|
||||
if(!OrderSend(req, res) && InpDebugLog)
|
||||
PrintFormat("[Trade] Failed to cancel stale order #%I64u, ageBars=%d, retcode=%d",
|
||||
ticket, ageSeconds / tfSeconds, res.retcode);
|
||||
}
|
||||
}
|
||||
|
||||
// Tính khối lượng lot sao cho 1R = InpRiskPercentPerR % balance
|
||||
double CalculateRiskLotSize(double entryPrice, double slPrice)
|
||||
{
|
||||
string symbol = GetTradeSymbol();
|
||||
|
||||
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||||
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||||
double volMin = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double volMax = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double volStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
|
||||
double priceDiff = MathAbs(entryPrice - slPrice);
|
||||
if(balance <= 0 || tickSize <= 0 || tickValue <= 0 || priceDiff <= 0)
|
||||
return 0.0;
|
||||
|
||||
double riskMoney = balance * InpRiskPercentPerR / 100.0;
|
||||
if(riskMoney <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
double ticks = priceDiff / tickSize;
|
||||
double costPerLot = ticks * tickValue; // tiền lỗ nếu 1 lot hit SL
|
||||
if(costPerLot <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
double rawVolume = riskMoney / costPerLot;
|
||||
|
||||
// Làm tròn đến 2 chữ số thập phân
|
||||
double rounded2 = MathFloor(rawVolume * 100.0 + 0.5) / 100.0;
|
||||
|
||||
// Canh theo step volume
|
||||
if(volStep > 0.0)
|
||||
rounded2 = MathFloor(rounded2 / volStep) * volStep;
|
||||
|
||||
// Giới hạn theo min/max
|
||||
if(rounded2 < volMin)
|
||||
rounded2 = volMin;
|
||||
if(rounded2 > volMax)
|
||||
rounded2 = volMax;
|
||||
|
||||
return rounded2;
|
||||
}
|
||||
|
||||
bool PlaceLimitForZone(const FVGZone &zone)
|
||||
{
|
||||
string symbol = GetTradeSymbol();
|
||||
|
||||
double entryPrice;
|
||||
double slPrice;
|
||||
double tpPrice;
|
||||
ENUM_ORDER_TYPE orderType;
|
||||
|
||||
double zoneHeight = zone.upperEdge - zone.lowerEdge;
|
||||
if(zoneHeight <= 0.0)
|
||||
return false;
|
||||
|
||||
double touchRatio = InpFVGTouchedPercent / 100.0;
|
||||
|
||||
if(zone.type == FVG_BULLISH)
|
||||
{
|
||||
// Entry: 40% từ đỉnh vùng xuống
|
||||
entryPrice = zone.upperEdge - zoneHeight * touchRatio;
|
||||
slPrice = zone.slReferencePrice;
|
||||
|
||||
double risk = entryPrice - slPrice;
|
||||
if(risk <= 0) return false;
|
||||
tpPrice = entryPrice + risk * InpRRRatio;
|
||||
orderType = ORDER_TYPE_BUY_LIMIT;
|
||||
}
|
||||
else // FVG_BEARISH
|
||||
{
|
||||
// Entry: 40% từ đáy vùng lên
|
||||
entryPrice = zone.lowerEdge + zoneHeight * touchRatio;
|
||||
slPrice = zone.slReferencePrice;
|
||||
|
||||
double risk = slPrice - entryPrice;
|
||||
if(risk <= 0) return false;
|
||||
tpPrice = entryPrice - risk * InpRRRatio;
|
||||
orderType = ORDER_TYPE_SELL_LIMIT;
|
||||
}
|
||||
|
||||
// Không tạo lệnh nếu đã có limit trùng giá/type
|
||||
if(HasLimitOrderAtPrice(orderType, entryPrice))
|
||||
return false;
|
||||
|
||||
double volume = CalculateRiskLotSize(entryPrice, slPrice);
|
||||
if(volume <= 0.0)
|
||||
return false;
|
||||
|
||||
MqlTradeRequest req;
|
||||
MqlTradeResult res;
|
||||
ZeroMemory(req);
|
||||
ZeroMemory(res);
|
||||
|
||||
req.action = TRADE_ACTION_PENDING;
|
||||
req.symbol = symbol;
|
||||
req.magic = InpEAMagic;
|
||||
req.type = orderType;
|
||||
req.volume = volume;
|
||||
req.price = entryPrice;
|
||||
req.sl = slPrice;
|
||||
req.tp = tpPrice;
|
||||
req.type_filling = ORDER_FILLING_RETURN;
|
||||
|
||||
req.deviation = 10;
|
||||
req.comment = "SimpleFVG";
|
||||
|
||||
if(!OrderSend(req, res))
|
||||
return false;
|
||||
|
||||
return (res.retcode == TRADE_RETCODE_DONE || res.retcode == TRADE_RETCODE_PLACED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Public: main trade manager |
|
||||
//+------------------------------------------------------------------+
|
||||
void ManageFVGTrades()
|
||||
{
|
||||
if(!InpTradeEnabled)
|
||||
return;
|
||||
|
||||
// Hủy các lệnh limit đã quá số bar cho phép
|
||||
CancelStaleLimitOrders();
|
||||
|
||||
// Nếu đã có vị thế, hủy toàn bộ limit còn lại
|
||||
if(CountOurPositions() > 0)
|
||||
{
|
||||
CancelAllOurLimitOrders();
|
||||
return;
|
||||
}
|
||||
|
||||
int currentLimits = CountOurLimitOrders();
|
||||
if(currentLimits >= InpMaxLimitOrders)
|
||||
return;
|
||||
|
||||
// Chỉ đặt lệnh limit khi giá đã chạm FVG (TOUCHED), không đặt khi mới ACTIVE
|
||||
ENUM_TREND_DIRECTION trend = g_CurrentTrend;
|
||||
|
||||
for(int i = g_FVGCount - 1; i >= 0 && currentLimits < InpMaxLimitOrders; i--)
|
||||
{
|
||||
FVGZone zone = g_FVGZones[i];
|
||||
if(!IsZoneTouched(zone))
|
||||
continue;
|
||||
|
||||
if(zone.type == FVG_BULLISH && trend != TREND_BULLISH)
|
||||
continue;
|
||||
if(zone.type == FVG_BEARISH && trend != TREND_BEARISH)
|
||||
continue;
|
||||
|
||||
if(PlaceLimitForZone(zone))
|
||||
currentLimits++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user