This commit is contained in:
2025-12-30 04:31:39 +03:30
parent e62e1a13dd
commit 5850164aab
8 changed files with 2792 additions and 1 deletions
+547
View File
@@ -0,0 +1,547 @@
//+------------------------------------------------------------------+
//| XKI_MTF_EA.mq5
//| SaherElm IT Center - Strategy using XKI
//| Author: Hadi Khazaee Asl
//+------------------------------------------------------------------+
#property strict
//
// Imports ...
#include "../Libraries/x-saherelm.common.lib.mq5"
// Inputs
input string InpSymbol = "XAUUSDb";
input ENUM_TIMEFRAMES InpSignalTF = PERIOD_H1; // Signal timeframe (H1)
input int InpKiLength = 26; // KI length (XKI input)
input bool InpShowKI = true; // (XKI input)
input int InpCalcLastBars = 1500; // (XKI input)
input bool InpUseRSI = true; // Use RSI confirmation
input int InpRSIPeriod = 14;
input int InpRSIPrice = PRICE_CLOSE;
input bool InpUseH4Filter = true; // Confirm with H4
input bool InpUseD1Filter = false; // Confirm with D1
input bool InpRequireBothFilters = false; // If true, require BOTH H4 and D1 agreement; else either
input double InpFixedLot = 0.10; // Fixed lot size
input double InpRiskATRMult = 0.0; // Add ATR padding to stop (0=disabled)
input int InpATRPeriod = 14;
input double InpRR = 2.0; // Reward:Risk target
input bool InpTrailByKI = true; // Trail stop to KI
input int InpSlippagePoints = 20; // Max slippage in points
input int InpMaxSpreadPoints = 200; // Max allowed spread in points
input bool InpAvoidAsianSession = true; // Avoid 22:00–06:00 server time
input int InpAsianStartHour = 22;
input int InpAsianEndHour = 6;
input bool InpAvoidNewsWindow = false; // Placeholder flag
input int InpNewsWindowMinutes = 60; // Skip trades within +/- minutes
input int InpBarsLookbackSignal = 2; // Lookback bars for signal (e.g., last closed bar)
// Magic number and comments
input int InpMagic = 260126;
input string InpOrderComment = "XKI_MTF_EA";
// iCustom indicator path (relative to Indicators folder if compiled there)
string g_xkiIndicatorPath = "x-saherelm.x121.xki"; // the compiled name; adjust if different
// Buffers indices as per the indicator
#define KI_BUFFER_INDEX 0
#define KI_COLOR_IDX_BUFFER_INDEX 1
#define KI_STATE_BUFFER_INDEX 2
// States from indicator
#define BULLISH_STATE 1
#define NEUTURAL_STATE 0
#define BEARISH_STATE -1
// Globals
int g_digits;
double g_point;
MqlTick g_tick;
int g_atrHandleH1 = INVALID_HANDLE;
int g_atrHandleH4 = INVALID_HANDLE;
int g_atrHandleD1 = INVALID_HANDLE;
int g_rsiHandleH1 = INVALID_HANDLE;
int g_rsiHandleH4 = INVALID_HANDLE;
int g_rsiHandleD1 = INVALID_HANDLE;
//+------------------------------------------------------------------+
// OnInit
//+------------------------------------------------------------------+
int OnInit()
{
//
g_digits = (int)SymbolInfoInteger(InpSymbol, SYMBOL_DIGITS);
g_point = SymbolInfoDouble(InpSymbol, SYMBOL_POINT);
if (!SymbolInfoTick(InpSymbol, g_tick))
{
Print("Failed to get tick for symbol: ", InpSymbol);
return INIT_FAILED;
}
// Quick validation
if (InpKiLength < 5)
{
Print("Invalid KI length: ", InpKiLength);
return INIT_PARAMETERS_INCORRECT;
}
//
GetOrCreateATRHandle(InpSymbol, InpSignalTF, InpATRPeriod);
if (InpUseH4Filter)
GetOrCreateATRHandle(InpSymbol, PERIOD_H4, InpATRPeriod);
if (InpUseD1Filter)
GetOrCreateATRHandle(InpSymbol, PERIOD_D1, InpATRPeriod);
//
GetOrCreateRSIHandle(InpSymbol, InpSignalTF, InpRSIPeriod, InpRSIPrice);
if (InpUseH4Filter)
GetOrCreateRSIHandle(InpSymbol, PERIOD_H4, InpRSIPeriod, InpRSIPrice);
if (InpUseD1Filter)
GetOrCreateRSIHandle(InpSymbol, PERIOD_D1, InpRSIPeriod, InpRSIPrice);
//
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
// OnDeinit
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//
if (g_atrHandleH1 != INVALID_HANDLE)
{
IndicatorRelease(g_atrHandleH1);
g_atrHandleH1 = INVALID_HANDLE;
}
if (g_atrHandleH4 != INVALID_HANDLE)
{
IndicatorRelease(g_atrHandleH4);
g_atrHandleH4 = INVALID_HANDLE;
}
if (g_atrHandleD1 != INVALID_HANDLE)
{
IndicatorRelease(g_atrHandleD1);
g_atrHandleD1 = INVALID_HANDLE;
}
//
if (g_rsiHandleH1 != INVALID_HANDLE)
{
IndicatorRelease(g_rsiHandleH1);
g_rsiHandleH1 = INVALID_HANDLE;
}
if (g_rsiHandleH4 != INVALID_HANDLE)
{
IndicatorRelease(g_rsiHandleH4);
g_rsiHandleH4 = INVALID_HANDLE;
}
if (g_rsiHandleD1 != INVALID_HANDLE)
{
IndicatorRelease(g_rsiHandleD1);
g_rsiHandleD1 = INVALID_HANDLE;
}
}
//+------------------------------------------------------------------+
// OnTick
//+------------------------------------------------------------------+
void OnTick()
{
if (_Symbol != InpSymbol)
return;
// Basic trading safety
if (!SymbolInfoTick(InpSymbol, g_tick))
return;
double spreadPoints = GetSpread(InpSymbol);
if (spreadPoints > InpMaxSpreadPoints)
return;
if (InpAvoidAsianSession)
{
//
MqlDateTime now = GetCurrentTime();
int hr = now.hour;
if (IsHourInRange(hr, InpAsianStartHour, InpAsianEndHour))
return;
}
if (InpAvoidNewsWindow)
{
// Placeholder: implement your own calendar integration
// Skip trading in a window near news. Here we simply return.
return;
}
// Only process on new bar of signal timeframe
static datetime lastSignalBarTime = 0;
datetime currentSignalBarTime = GetLastClosedBarTime(InpSymbol, InpSignalTF);
if (currentSignalBarTime == 0 || currentSignalBarTime == lastSignalBarTime)
return;
lastSignalBarTime = currentSignalBarTime;
// Get signal state and KI on signal TF (last closed bar)
int signalBarShift = InpBarsLookbackSignal; // 1=last closed, 2=previous
int stateSignal;
double kiSignal, closeSignal;
if (!GetXKIStateAndKI(InpSymbol, InpSignalTF, signalBarShift, stateSignal, kiSignal))
return;
closeSignal = iClose(InpSymbol, InpSignalTF, signalBarShift);
// RSI confirmation
if (InpUseRSI)
{
double rsi = GetRSIValue(InpSymbol, InpSignalTF, InpRSIPeriod, InpRSIPrice, signalBarShift);
if (rsi == EMPTY_VALUE)
return;
if (stateSignal == BULLISH_STATE && rsi <= 50.0)
return;
if (stateSignal == BEARISH_STATE && rsi >= 50.0)
return;
}
// Higher timeframe agreement
bool agreesH4 = true, agreesD1 = true;
if (InpUseH4Filter)
agreesH4 = HigherTFAgrees(InpSymbol, PERIOD_H4, signalBarShift, stateSignal);
if (InpUseD1Filter)
agreesD1 = HigherTFAgrees(InpSymbol, PERIOD_D1, signalBarShift, stateSignal);
bool filterOK = true;
if (InpUseH4Filter || InpUseD1Filter)
{
if (InpRequireBothFilters)
filterOK = (agreesH4 && agreesD1);
else
filterOK = ((InpUseH4Filter && agreesH4) || (InpUseD1Filter && agreesD1));
}
if (!filterOK)
return;
// Neutral or invalid skip
if (stateSignal == NEUTURAL_STATE)
return;
// Ensure no open positions for this symbol/magic
if (HasOpenPosition(InpSymbol, InpMagic))
return;
// Prepare trade params
double atrPad = 0.0;
if (InpRiskATRMult > 0.0)
{
double atr = GetATRValue(InpSymbol, InpSignalTF, InpATRPeriod, signalBarShift);
if (atr != EMPTY_VALUE && atr > 0)
atrPad = atr * InpRiskATRMult;
}
double sl, tp;
bool isBuy = (stateSignal == BULLISH_STATE);
if (isBuy)
{
sl = kiSignal - atrPad;
tp = closeSignal + (closeSignal - sl) * InpRR;
PlaceOrder(InpSymbol, ORDER_TYPE_BUY, InpFixedLot, sl, tp);
}
else if (stateSignal == BEARISH_STATE)
{
sl = kiSignal + atrPad;
tp = closeSignal - (sl - closeSignal) * InpRR;
PlaceOrder(InpSymbol, ORDER_TYPE_SELL, InpFixedLot, sl, tp);
}
// After placement, trailing handled in OnTimer or OnTick below
if (InpTrailByKI)
TrailStopsByKI();
}
//+------------------------------------------------------------------+
// Utilities
//+------------------------------------------------------------------+
//
// Create or reuse an ATR handle for the given TF ...
int GetOrCreateATRHandle(
string symbol,
ENUM_TIMEFRAMES tf,
int period //
)
{
int h = g_atrHandleH1;
if (tf == PERIOD_H4)
h = g_atrHandleH4;
else if (tf == PERIOD_D1)
h = g_atrHandleD1;
if (h == INVALID_HANDLE)
{
h = iATR(symbol, tf, period);
if (h == INVALID_HANDLE)
Print("Failed to create ATR handle: ", symbol, " ", EnumToString(tf), " period=", period);
}
return h;
}
//
// Read ATR value for a specific bar shift (1=last closed bar) ...
double GetATRValue(
string symbol,
ENUM_TIMEFRAMES tf,
int period,
int shift //
)
{
int h = GetOrCreateATRHandle(symbol, tf, period);
if (h == INVALID_HANDLE)
return EMPTY_VALUE;
//
double buff[];
//
// Copy exactly one value at 'shift' ...
int copied = CopyBuffer(h, 0, shift, 1, buff);
if (copied != 1 || !MathIsValidNumber(buff[0]))
return EMPTY_VALUE;
//
return buff[0];
}
//
// Create or reuse an RSI handle for the given TF ...
int GetOrCreateRSIHandle(
string symbol,
ENUM_TIMEFRAMES tf,
int period,
int appliedPrice //
)
{
//
int h = g_rsiHandleH1;
if (tf == PERIOD_H4)
h = g_rsiHandleH4;
else if (tf == PERIOD_D1)
h = g_rsiHandleD1;
if (h == INVALID_HANDLE)
{
h = iRSI(symbol, tf, period, appliedPrice);
if (h == INVALID_HANDLE)
Print("Failed to create RSI handle: ", symbol, " ", EnumToString(tf), " period=", period, " price=", appliedPrice);
}
return h;
}
//
// Read RSI value for a specific bar shift (1=last closed bar) ..
double GetRSIValue(
string symbol,
ENUM_TIMEFRAMES tf,
int period,
int appliedPrice,
int shift //
)
{
int h = GetOrCreateRSIHandle(symbol, tf, period, appliedPrice);
if (h == INVALID_HANDLE)
return EMPTY_VALUE;
double buff[];
int copied = CopyBuffer(h, 0, shift, 1, buff);
if (copied != 1 || !MathIsValidNumber(buff[0]))
return EMPTY_VALUE;
return buff[0];
}
datetime GetLastClosedBarTime(string symbol, ENUM_TIMEFRAMES tf)
{
datetime times[];
if (CopyTime(symbol, tf, 0, 3, times) <= 1)
return 0;
// times[0] is current open bar time; last closed is times[1]
return times[1];
}
bool IsHourInRange(int hr, int startHr, int endHr)
{
// Handles wrap-around (e.g., 22 -> 6)
if (startHr <= endHr)
return (hr >= startHr && hr < endHr);
else
return (hr >= startHr || hr < endHr);
}
bool GetXKIStateAndKI(string symbol, ENUM_TIMEFRAMES tf, int shift, int &stateOut, double &kiOut)
{
// Prepare indicator handle for this TF
static int handleH1 = INVALID_HANDLE;
static int handleH4 = INVALID_HANDLE;
static int handleD1 = INVALID_HANDLE;
int handleRef = handleH1;
if (tf == PERIOD_H4)
handleRef = handleH4;
else if (tf == PERIOD_D1)
handleRef = handleD1;
if (handleRef == INVALID_HANDLE)
{
handleRef = iCustom(symbol, tf, g_xkiIndicatorPath,
InpKiLength, InpShowKI, InpCalcLastBars);
if (handleRef == INVALID_HANDLE)
{
Print("Failed to create XKI handle for TF: ", EnumToString(tf));
return false;
}
}
double kiBuff[];
double stateBuff[];
if (CopyBuffer(handleRef, KI_BUFFER_INDEX, shift, 1, kiBuff) != 1)
return false;
if (CopyBuffer(handleRef, KI_STATE_BUFFER_INDEX, shift, 1, stateBuff) != 1)
return false;
kiOut = kiBuff[0];
stateOut = (int)stateBuff[0];
if (!MathIsValidNumber(kiOut))
return false;
return true;
}
bool HigherTFAgrees(string symbol, ENUM_TIMEFRAMES tf, int shift, int signalState)
{
int stateHTF;
double kiHTF;
if (!GetXKIStateAndKI(symbol, tf, shift, stateHTF, kiHTF))
return false;
if (signalState == BULLISH_STATE)
return (stateHTF == BULLISH_STATE);
if (signalState == BEARISH_STATE)
return (stateHTF == BEARISH_STATE);
return false;
}
bool HasOpenPosition(string symbol, int magic)
{
for (int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if (ticket == 0)
continue;
if (PositionSelectByTicket(ticket))
{
string sym = PositionGetString(POSITION_SYMBOL);
long mg = (long)PositionGetInteger(POSITION_MAGIC);
if (sym == symbol && mg == magic)
return true;
}
}
return false;
}
void PlaceOrder(string symbol, ENUM_ORDER_TYPE type, double lots, double sl, double tp)
{
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
double price = 0.0;
if (!SymbolInfoTick(symbol, g_tick))
return;
if (type == ORDER_TYPE_BUY)
price = g_tick.ask;
else if (type == ORDER_TYPE_SELL)
price = g_tick.bid;
// Normalize SL/TP
sl = NormalizeDouble(sl, g_digits);
tp = NormalizeDouble(tp, g_digits);
price = NormalizeDouble(price, g_digits);
req.action = TRADE_ACTION_DEAL;
req.symbol = symbol;
req.volume = lots;
req.type = type;
req.price = price;
req.sl = sl;
req.tp = tp;
req.deviation = InpSlippagePoints;
req.magic = InpMagic;
req.comment = InpOrderComment;
if (!OrderSend(req, res))
Print("OrderSend failed: ", res.retcode);
}
void TrailStopsByKI()
{
// Trail per position using current signal TF KI
for (int i = 0; i < PositionsTotal(); i++)
{
ulong ticket = PositionGetTicket(i);
if (ticket == 0)
continue;
if (!PositionSelectByTicket(ticket))
continue;
string sym = PositionGetString(POSITION_SYMBOL);
long mg = (long)PositionGetInteger(POSITION_MAGIC);
if (sym != InpSymbol || mg != InpMagic)
continue;
ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double sl = PositionGetDouble(POSITION_SL);
double priceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
// Use last closed bar KI for stability
int shift = 1;
int state;
double ki;
if (!GetXKIStateAndKI(sym, InpSignalTF, shift, state, ki))
continue;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
double newSL = sl;
if (ptype == POSITION_TYPE_BUY)
{
double proposedSL = NormalizeDouble(ki, g_digits);
if (proposedSL > sl)
newSL = proposedSL; // only move up
}
else if (ptype == POSITION_TYPE_SELL)
{
double proposedSL = NormalizeDouble(ki, g_digits);
if (proposedSL < sl || sl == 0.0)
newSL = proposedSL; // only move down (or set if none)
}
if (newSL != sl && newSL != 0.0)
{
req.action = TRADE_ACTION_SLTP;
req.symbol = sym;
req.sl = newSL;
req.tp = PositionGetDouble(POSITION_TP);
req.magic = InpMagic;
if (!OrderSend(req, res))
Print("Trail SLTP failed: ", res.retcode);
}
}
}
//+------------------------------------------------------------------+
+708
View File
@@ -0,0 +1,708 @@
//+------------------------------------------------------------------+
//| TrendPullbackPro.mq5|
//| Trend-following pullback EA with risk & management |
//+------------------------------------------------------------------+
#property copyright "Hadi Strategy"
#property link ""
#property version "1.10"
#property strict
//--- Inputs: risk & money management
input double InpRiskPerTradePercent = 1.0; // Total risk per trade (% of balance)
input int InpMagicNumber = 123456; // Magic number
input double InpStopBufferPoints = 10; // Extra buffer beyond swing high/low (points)
//--- Inputs: indicators
input ENUM_TIMEFRAMES InpTF = PERIOD_CURRENT; // Signal timeframe
input int InpFastEMAPeriod = 20;
input int InpSlowEMAPeriod = 50;
input int InpRSIPeriod = 14;
input double InpRSILowZoneMin = 40; // Uptrend pullback zone min
input double InpRSILowZoneMax = 50; // Uptrend pullback zone max
input double InpRSIHighZoneMin = 50; // Downtrend pullback zone min
input double InpRSIHighZoneMax = 60; // Downtrend pullback zone max
//--- Inputs: partial TP and R multiples
input bool InpUsePartialTP = true;
input double InpPartialCloseAtR = 1.0; // R at which to take partial profit
input double InpPartialClosePercent = 50.0; // % of volume to close at partial TP
input double InpFinalTargetR = 3.0; // Final target R for runner
input bool InpUseBreakEven = true;
input double InpBreakEvenRMultiple = 1.0; // Move SL to BE at this R (runner)
//--- Inputs: trailing stop
input bool InpUseEMATrailing = true;
input double InpTrailStartR = 1.0; // Start trailing after this R
input double InpTrailOffsetPoints = 5; // Extra buffer beyond EMA (points)
//--- Inputs: spread & time filters
input bool InpUseSpreadFilter = true;
input double InpMaxSpreadPoints = 30; // Max allowed spread (in points)
input bool InpUseTimeFilter = false;
input int InpSessionStartHour = 8; // Trading session start (server time)
input int InpSessionEndHour = 22; // Trading session end (server time)
input bool InpAvoidRolloverHour = true;
input int InpRolloverHour = 23; // Avoid new entries at this hour
//--- constants
#define NO_TREND 0
#define UPTREND 1
#define DOWNTREND -1
#define PARTIAL_COMMENT "TrendPullbackEA_Partial"
#define RUNNER_COMMENT "TrendPullbackEA_Runner"
//--- Global indicator handles
int rsi_handle;
int ema_fast_handle;
int ema_slow_handle;
//--- Tick storage
MqlTick last_tick;
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
rsi_handle = iRSI(_Symbol, InpTF, InpRSIPeriod, PRICE_CLOSE);
if(rsi_handle == INVALID_HANDLE)
{
Print("Error creating RSI handle. Error: ", GetLastError());
return(INIT_FAILED);
}
ema_fast_handle = iMA(_Symbol, InpTF, InpFastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(ema_fast_handle == INVALID_HANDLE)
{
Print("Error creating fast EMA handle. Error: ", GetLastError());
return(INIT_FAILED);
}
ema_slow_handle = iMA(_Symbol, InpTF, InpSlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(ema_slow_handle == INVALID_HANDLE)
{
Print("Error creating slow EMA handle. Error: ", GetLastError());
return(INIT_FAILED);
}
SymbolInfoTick(_Symbol, last_tick);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(rsi_handle);
IndicatorRelease(ema_fast_handle);
IndicatorRelease(ema_slow_handle);
}
//+------------------------------------------------------------------+
//| OnTick |
//+------------------------------------------------------------------+
void OnTick()
{
if(!SymbolInfoTick(_Symbol, last_tick))
return;
// Run main logic once per bar on signal timeframe
static datetime last_bar_time = 0;
datetime cur_bar_time = iTime(_Symbol, InpTF, 0);
if(cur_bar_time == last_bar_time)
return;
last_bar_time = cur_bar_time;
ManageOpenPositions();
// Avoid new entries if no time/spread conditions
if(!CanOpenNewTrades())
return;
CheckOpenSignals();
}
//+------------------------------------------------------------------+
//| Check if we can open new trades (time & spread) |
//+------------------------------------------------------------------+
bool CanOpenNewTrades()
{
// Spread filter
if(InpUseSpreadFilter)
{
double spread_points = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID))
/ SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(spread_points > InpMaxSpreadPoints)
{
//Print("Spread too high: ", spread_points, " > ", InpMaxSpreadPoints);
return(false);
}
}
// Time filter
if(InpUseTimeFilter)
{
datetime now = TimeCurrent();
MqlDateTime dt;
TimeToStruct(now, dt);
// avoid rollover hour
if(InpAvoidRolloverHour && dt.hour == InpRolloverHour)
return(false);
// session window
bool inSession = (dt.hour >= InpSessionStartHour && dt.hour < InpSessionEndHour);
if(!inSession)
return(false);
}
return(true);
}
//+------------------------------------------------------------------+
//| Check for new entry signals |
//+------------------------------------------------------------------+
void CheckOpenSignals()
{
if(HasOpenPosition())
return;
int trend = GetTrendDirection();
if(trend == NO_TREND)
return;
double rsi_value;
if(!GetRSI(1, rsi_value))
return;
MqlRates rates[];
if(CopyRates(_Symbol, InpTF, 0, 3, rates) < 3)
return;
// rates[0] current, [1] closed 1, [2] closed 2
MqlRates candle = rates[1];
MqlRates prev = rates[2];
bool longSignal = false;
bool shortSignal = false;
if(trend == UPTREND)
{
if(rsi_value >= InpRSILowZoneMin && rsi_value <= InpRSILowZoneMax)
{
if(IsBullishPattern(candle, prev))
longSignal = true;
}
}
else if(trend == DOWNTREND)
{
if(rsi_value >= InpRSIHighZoneMin && rsi_value <= InpRSIHighZoneMax)
{
if(IsBearishPattern(candle, prev))
shortSignal = true;
}
}
if(longSignal)
OpenTrade(ORDER_TYPE_BUY, candle);
else if(shortSignal)
OpenTrade(ORDER_TYPE_SELL, candle);
}
//+------------------------------------------------------------------+
//| Manage open positions (partial TP, BE, trailing) |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
for(int i = PositionsTotal()-1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket))
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
long type = PositionGetInteger(POSITION_TYPE);
double volume = PositionGetDouble(POSITION_VOLUME);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double price_open = PositionGetDouble(POSITION_PRICE_OPEN);
string comment = PositionGetString(POSITION_COMMENT);
MqlTick tick;
SymbolInfoTick(_Symbol, tick);
double current_price = (type == POSITION_TYPE_BUY ? tick.bid : tick.ask);
double riskPoints;
if(type == POSITION_TYPE_BUY)
riskPoints = (price_open - sl) / point;
else
riskPoints = (sl - price_open) / point;
if(riskPoints <= 0)
continue;
double profitPoints;
if(type == POSITION_TYPE_BUY)
profitPoints = (current_price - price_open) / point;
else
profitPoints = (price_open - current_price) / point;
double rMultiple = profitPoints / riskPoints;
// 1) Partial TP logic for "partial" leg
if(InpUsePartialTP && comment == PARTIAL_COMMENT)
{
if(rMultiple >= InpPartialCloseAtR)
{
// Close this position fully (it's the partial leg)
ClosePosition(ticket);
continue;
}
}
// 2) Break-even & trailing for runner leg
if(comment == RUNNER_COMMENT)
{
// Break-even
if(InpUseBreakEven && rMultiple >= InpBreakEvenRMultiple)
{
if(type == POSITION_TYPE_BUY && sl < price_open)
ModifyPositionSL(ticket, price_open);
else if(type == POSITION_TYPE_SELL && sl > price_open)
ModifyPositionSL(ticket, price_open);
}
// EMA trailing after a certain R
if(InpUseEMATrailing && rMultiple >= InpTrailStartR)
{
double emaFast;
if(!GetEMA(ema_fast_handle, 1, emaFast))
continue;
double newSL;
double offset = InpTrailOffsetPoints * point;
if(type == POSITION_TYPE_BUY)
{
newSL = emaFast - offset;
if(newSL > sl && newSL < current_price) // only tighten SL
ModifyPositionSL(ticket, newSL);
}
else
{
newSL = emaFast + offset;
if(newSL < sl && newSL > current_price)
ModifyPositionSL(ticket, newSL);
}
}
// Optionally: ensure TP aligns with final target R
// If user wants to rely only on trailing, they can disable or adjust final R.
// We keep TP as set at entry.
}
}
}
//+------------------------------------------------------------------+
//| Get trend direction from EMAs |
//+------------------------------------------------------------------+
int GetTrendDirection()
{
double ema_fast[3], ema_slow[3];
if(CopyBuffer(ema_fast_handle, 0, 0, 3, ema_fast) < 3)
return NO_TREND;
if(CopyBuffer(ema_slow_handle, 0, 0, 3, ema_slow) < 3)
return NO_TREND;
double fast = ema_fast[1];
double slow = ema_slow[1];
if(fast > slow)
return UPTREND;
else if(fast < slow)
return DOWNTREND;
return NO_TREND;
}
//+------------------------------------------------------------------+
//| Get RSI value at bar index |
//+------------------------------------------------------------------+
bool GetRSI(int index, double &value)
{
double buffer[1];
int copied = CopyBuffer(rsi_handle, 0, index, 1, buffer);
if(copied != 1)
return false;
value = buffer[0];
return true;
}
//+------------------------------------------------------------------+
//| Get EMA value from handle |
//+------------------------------------------------------------------+
bool GetEMA(int handle, int index, double &value)
{
double buffer[1];
int copied = CopyBuffer(handle, 0, index, 1, buffer);
if(copied != 1)
return false;
value = buffer[0];
return true;
}
//+------------------------------------------------------------------+
//| Bullish pattern: engulfing + pin-bar-ish |
//+------------------------------------------------------------------+
bool IsBullishPattern(const MqlRates &c, const MqlRates &prev)
{
double bodyC = MathAbs(c.close - c.open);
double bodyP = MathAbs(prev.close - prev.open);
double rangeC = c.high - c.low;
if(rangeC <= 0)
return false;
// Basic bullish: close > open
if(c.close <= c.open)
return false;
// Engulfing body: current body >= previous body, and covers its open/close
bool engulfing = (bodyC >= bodyP && c.open <= prev.close && c.close >= prev.open);
// Pin-bar-ish: long lower wick
double lowerWick = MathMin(c.open, c.close) - c.low;
bool longLowerWick = (lowerWick >= rangeC * 0.4); // tweak
if(engulfing || longLowerWick)
return true;
return false;
}
//+------------------------------------------------------------------+
//| Bearish pattern: engulfing + pin-bar-ish |
//+------------------------------------------------------------------+
bool IsBearishPattern(const MqlRates &c, const MqlRates &prev)
{
double bodyC = MathAbs(c.close - c.open);
double bodyP = MathAbs(prev.close - prev.open);
double rangeC = c.high - c.low;
if(rangeC <= 0)
return false;
// Basic bearish: close < open
if(c.close >= c.open)
return false;
// Engulfing body
bool engulfing = (bodyC >= bodyP && c.open >= prev.close && c.close <= prev.open);
// Pin-bar-ish: long upper wick
double upperWick = c.high - MathMax(c.open, c.close);
bool longUpperWick = (upperWick >= rangeC * 0.4); // tweak
if(engulfing || longUpperWick)
return true;
return false;
}
//+------------------------------------------------------------------+
//| Check if there is already an open position |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
for(int i = PositionsTotal()-1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(!PositionSelectByTicket(ticket))
continue;
if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber)
continue;
if(PositionGetString(POSITION_SYMBOL) == _Symbol)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Calculate lot size based on total risk % |
//+------------------------------------------------------------------+
double CalculateTotalVolume(double stopLossPrice, ENUM_ORDER_TYPE type)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * InpRiskPerTradePercent / 100.0;
double entryPrice = (type == ORDER_TYPE_BUY ? last_tick.ask : last_tick.bid);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tick_size <= 0 || tick_value <= 0)
return(0.0);
double slDistance = (type == ORDER_TYPE_BUY ? entryPrice - stopLossPrice : stopLossPrice - entryPrice);
if(slDistance <= 0)
return(0.0);
double moneyPerPointPerLot = tick_value / tick_size * point;
double stopPoints = slDistance / point;
double volume = riskMoney / (stopPoints * moneyPerPointPerLot);
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
volume = MathFloor(volume / lotStep) * lotStep;
if(volume < minLot)
volume = 0.0;
if(volume > maxLot)
volume = maxLot;
return(volume);
}
//+------------------------------------------------------------------+
//| Open trade: split into partial + runner |
//+------------------------------------------------------------------+
void OpenTrade(ENUM_ORDER_TYPE type, const MqlRates &signalCandle)
{
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
return;
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double entryPrice = (type == ORDER_TYPE_BUY ? tick.ask : tick.bid);
double slPrice;
if(type == ORDER_TYPE_BUY)
slPrice = signalCandle.low - InpStopBufferPoints * point;
else
slPrice = signalCandle.high + InpStopBufferPoints * point;
if(slPrice <= 0)
return;
double totalVolume = CalculateTotalVolume(slPrice, type);
if(totalVolume <= 0)
{
Print("Calculated total volume <= 0, skipping trade.");
return;
}
double slDistance = (type == ORDER_TYPE_BUY ? entryPrice - slPrice : slPrice - entryPrice);
// final TP at FinalTargetR
double finalTPDistance = slDistance * InpFinalTargetR;
double runnerTP;
if(type == ORDER_TYPE_BUY)
runnerTP = entryPrice + finalTPDistance;
else
runnerTP = entryPrice - finalTPDistance;
// partial volume
double partialVolume = totalVolume;
double runnerVolume = 0.0;
if(InpUsePartialTP && InpPartialClosePercent > 0 && InpPartialClosePercent < 100)
{
partialVolume = totalVolume * (InpPartialClosePercent / 100.0);
runnerVolume = totalVolume - partialVolume;
}
else
{
// no partial: everything is runner
partialVolume = 0.0;
runnerVolume = totalVolume;
}
// normalized volumes
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
partialVolume = MathFloor(partialVolume / lotStep) * lotStep;
runnerVolume = MathFloor(runnerVolume / lotStep) * lotStep;
if(partialVolume < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
partialVolume = 0.0;
if(runnerVolume < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN))
runnerVolume = 0.0;
// Partial TP price at PartialCloseAtR
double partialTPPrice = 0.0;
if(InpUsePartialTP && partialVolume > 0 && InpPartialCloseAtR > 0)
{
double partialTPDist = slDistance * InpPartialCloseAtR;
if(type == ORDER_TYPE_BUY)
partialTPPrice = entryPrice + partialTPDist;
else
partialTPPrice = entryPrice - partialTPDist;
}
// 1) Open partial leg
if(partialVolume > 0.0)
{
SendOrder(type, partialVolume, slPrice, partialTPPrice, PARTIAL_COMMENT);
}
// 2) Open runner leg
if(runnerVolume > 0.0)
{
SendOrder(type, runnerVolume, slPrice, runnerTP, RUNNER_COMMENT);
}
}
//+------------------------------------------------------------------+
//| Send market order |
//+------------------------------------------------------------------+
bool SendOrder(ENUM_ORDER_TYPE type, double volume, double sl, double tp, string comment)
{
if(volume <= 0.0)
return(false);
MqlTick tick;
if(!SymbolInfoTick(_Symbol, tick))
return(false);
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.magic = InpMagicNumber;
request.symbol = _Symbol;
request.volume = volume;
request.type = type;
request.deviation = 20; // points
request.type_filling= ORDER_FILLING_FOK;
request.price = (type == ORDER_TYPE_BUY ? tick.ask : tick.bid);
request.sl = sl;
request.tp = tp;
request.comment = comment;
if(!OrderSend(request, result))
{
Print("OrderSend failed. Error: ", GetLastError());
return(false);
}
if(result.retcode != TRADE_RETCODE_DONE)
{
Print("OrderSend retcode: ", result.retcode);
return(false);
}
Print("Opened ", (type == ORDER_TYPE_BUY ? "BUY" : "SELL"),
" ticket #", result.order, " vol=", DoubleToString(volume,2),
" SL=", DoubleToString(sl,_Digits)," TP=", DoubleToString(tp,_Digits),
" comment=", comment);
return(true);
}
//+------------------------------------------------------------------+
//| Modify SL for position |
//+------------------------------------------------------------------+
bool ModifyPositionSL(ulong ticket, double newSL)
{
if(!PositionSelectByTicket(ticket))
return(false);
string sym = PositionGetString(POSITION_SYMBOL);
long type = PositionGetInteger(POSITION_TYPE);
double volume = PositionGetDouble(POSITION_VOLUME);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
double tp = PositionGetDouble(POSITION_TP);
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_SLTP;
request.magic = InpMagicNumber;
request.symbol = sym;
request.volume = volume;
request.type = (ENUM_ORDER_TYPE)type;
request.price = price;
request.sl = newSL;
request.tp = tp;
if(!OrderSend(request, result))
{
Print("ModifyPositionSL OrderSend failed. Error: ", GetLastError());
return(false);
}
if(result.retcode != TRADE_RETCODE_DONE)
{
Print("ModifyPositionSL retcode: ", result.retcode);
return(false);
}
Print("Modified SL for ticket #", ticket, " to ", DoubleToString(newSL,_Digits));
return(true);
}
//+------------------------------------------------------------------+
//| Close position |
//+------------------------------------------------------------------+
bool ClosePosition(ulong ticket)
{
if(!PositionSelectByTicket(ticket))
return(false);
string sym = PositionGetString(POSITION_SYMBOL);
long type = PositionGetInteger(POSITION_TYPE);
double volume = PositionGetDouble(POSITION_VOLUME);
double price = 0.0;
MqlTick tick;
if(!SymbolInfoTick(sym, tick))
return(false);
if(type == POSITION_TYPE_BUY)
price = tick.bid;
else
price = tick.ask;
MqlTradeRequest request;
MqlTradeResult result;
ZeroMemory(request);
ZeroMemory(result);
request.action = TRADE_ACTION_DEAL;
request.magic = InpMagicNumber;
request.symbol = sym;
request.volume = volume;
request.type = (type == POSITION_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY);
request.price = price;
request.deviation = 20;
request.type_filling= ORDER_FILLING_FOK;
request.comment = "Close";
if(!OrderSend(request, result))
{
Print("ClosePosition OrderSend failed. Error: ", GetLastError());
return(false);
}
if(result.retcode != TRADE_RETCODE_DONE)
{
Print("ClosePosition retcode: ", result.retcode);
return(false);
}
Print("Closed ticket #", ticket);
return(true);
}
//+------------------------------------------------------------------+
+525
View File
@@ -0,0 +1,525 @@
//+------------------------------------------------------------------+
//| XRR_Pullback.mq5 |
//| Author: Hadi (for XAUUSD,M15) |
//+------------------------------------------------------------------+
#property strict
#property version "0.1"
#property description "M15 pullback EA: Major zone (HH/LL), minor same-direction zone, pullback entry, SL under minor, TP at 2R, 1% risk."
//------------------------------- Inputs -----------------------------
input string Inp_Symbol = "XAUUSD";
input ENUM_TIMEFRAMES Inp_WorkTF = PERIOD_M15;
// Zone detection params
input int Inp_MajorLookbackBars = 400; // bars to scan for HH/LL major zone
input int Inp_MinorWindowBars = 80; // window to find first same-direction minor zone post-major
input int Inp_ZonePadPoints = 50; // padding around zones (points)
// Confirmation / filters
input bool Inp_UseHTFConfluence = true;
input ENUM_TIMEFRAMES Inp_HTF = PERIOD_H1; // confluence timeframe
input int Inp_HTFShiftBars = 3; // HTF confirmation window
input bool Inp_UseEngulfingConfirm = true;
input bool Inp_UseRSIDivergence = false;
input int Inp_RSI_Period = 14;
input bool Inp_UseVolumeSpike = false;
input double Inp_VolSpikeFactor = 1.5; // vs 20-bar average
// Risk management
input double Inp_RiskPercent = 1.0; // 1% per trade
input bool Inp_UseATRforSL = false;
input int Inp_ATR_Period = 14;
input double Inp_ATR_Mult = 1.5;
input double Inp_MinSLPoints = 200; // floor SL (points)
input double Inp_R_MultipleTP = 2.0; // fixed R:R=1:2
// Risk-free (optional)
input bool Inp_MoveToBEatTP1 = true;
input double Inp_TP1_R = 1.0; // move to BE at 1R
input double Inp_BE_OffsetPoints = 10; // lock-in small +offset
// Sessions
input bool Inp_UseSessionFilter = true;
input int Inp_LondonOpenHour = 8; // broker time
input int Inp_LondonCloseHour = 17;
input int Inp_NYOpenHour = 13;
input int Inp_NYCloseHour = 21;
input bool Inp_AllowNYOverlapOnly = false;
// Entry placement
input bool Inp_UseLimitAtZone = true; // limit order at minor zone
input int Inp_MaxSlippagePoints = 100;
input int Inp_MinRetestWickPoints = 30;
// Safety / engine
input int Inp_MinBarsLoaded = 600;
input int Inp_SignalCoolOffBars = 20; // avoid rapid re-entries
input bool Inp_OnePositionOnly = true;
//------------------------------- Structures -------------------------
struct Zone
{
bool isValid;
double priceLow;
double priceHigh;
int barIndex; // starting bar index of zone
bool isSupply; // true: resistance (HH), false: support (LL)
};
struct MinorZone
{
bool isValid;
double priceLow;
double priceHigh;
int barIndex;
bool isSupply; // same direction as major
};
datetime lastSignalTimeBuy = 0;
datetime lastSignalTimeSell = 0;
//
int atrHandler = INVALID_HANDLE;
//------------------------------- Helpers ----------------------------
int DigitsAdjust()
{
return (int)SymbolInfoInteger(Inp_Symbol, SYMBOL_DIGITS);
}
double PointAdjust()
{
return SymbolInfoDouble(Inp_Symbol, SYMBOL_POINT);
}
double TickValue()
{
double v;
SymbolInfoDouble(Inp_Symbol, SYMBOL_TRADE_TICK_VALUE, v);
return v;
}
double TickSize()
{
double s;
SymbolInfoDouble(Inp_Symbol, SYMBOL_TRADE_TICK_SIZE, s);
return s;
}
bool IsSessionAllowed()
{
if (!Inp_UseSessionFilter)
return true;
MqlDateTime t;
TimeToStruct(TimeCurrent(), t);
int hour = t.hour;
bool london = (hour >= Inp_LondonOpenHour && hour < Inp_LondonCloseHour);
bool ny = (hour >= Inp_NYOpenHour && hour < Inp_NYCloseHour);
if (Inp_AllowNYOverlapOnly)
return (london && ny);
return (london || ny);
}
bool EngulfingBearish(int shift)
{
double o0 = iOpen(Inp_Symbol, Inp_WorkTF, shift), c0 = iClose(Inp_Symbol, Inp_WorkTF, shift);
double o1 = iOpen(Inp_Symbol, Inp_WorkTF, shift + 1), c1 = iClose(Inp_Symbol, Inp_WorkTF, shift + 1);
return (c0 < o0 && c1 > o1 && o0 < c1 && c0 < o1 && (o0 - o1) > 0 && (c1 - c0) > 0 && (o1 < c0)); // rough engulfing
}
bool EngulfingBullish(int shift)
{
double o0 = iOpen(Inp_Symbol, Inp_WorkTF, shift), c0 = iClose(Inp_Symbol, Inp_WorkTF, shift);
double o1 = iOpen(Inp_Symbol, Inp_WorkTF, shift + 1), c1 = iClose(Inp_Symbol, Inp_WorkTF, shift + 1);
return (c0 > o0 && c1 < o1 && o0 > c1 && c0 > o1 && (o0 - o1) > 0 && (c0 - c1) > 0 && (o1 > c0)); // rough engulfing inverse
}
bool VolumeSpike(int shift)
{
if (!Inp_UseVolumeSpike)
return true;
double avg = 0;
int n = 20;
for (int i = shift + 1; i < shift + 1 + n; i++)
avg += (double)iVolume(Inp_Symbol, Inp_WorkTF, i);
avg /= n;
double v0 = (double)iVolume(Inp_Symbol, Inp_WorkTF, shift);
return (v0 >= avg * Inp_VolSpikeFactor);
}
bool HTFConfluence(bool wantSupply)
{
if (!Inp_UseHTFConfluence)
return true;
// Simple HTF direction: last HTF candle close vs open
double o = iOpen(Inp_Symbol, Inp_HTF, Inp_HTFShiftBars), c = iClose(Inp_Symbol, Inp_HTF, Inp_HTFShiftBars);
bool htfDown = (c < o), htfUp = (c > o);
return wantSupply ? htfDown : htfUp;
}
//------------------------------- Zone detection ---------------------
Zone FindMajorZone()
{
Zone z;
z.isValid = false;
z.isSupply = false;
z.priceLow = 0;
z.priceHigh = 0;
z.barIndex = -1;
int bars = iBars(Inp_Symbol, Inp_WorkTF);
if (bars < Inp_MinBarsLoaded)
return z;
// Highest high / lowest low over lookback
double hh = -DBL_MAX, ll = DBL_MAX;
int hhBar = -1, llBar = -1;
for (int i = 1; i <= Inp_MajorLookbackBars; i++)
{
double h = iHigh(Inp_Symbol, Inp_WorkTF, i), l = iLow(Inp_Symbol, Inp_WorkTF, i);
if (h > hh)
{
hh = h;
hhBar = i;
}
if (l < ll)
{
ll = l;
llBar = i;
}
}
// Decide which one is closest to current action: use most recent extreme
if (hhBar < llBar)
{
z.isSupply = true;
z.isValid = true;
z.barIndex = hhBar;
z.priceLow = hh - Inp_ZonePadPoints * PointAdjust();
z.priceHigh = hh + Inp_ZonePadPoints * PointAdjust();
}
else
{
z.isSupply = false;
z.isValid = true;
z.barIndex = llBar;
z.priceLow = ll - Inp_ZonePadPoints * PointAdjust();
z.priceHigh = ll + Inp_ZonePadPoints * PointAdjust();
}
return z;
}
MinorZone FindFirstMinorSameDirection(const Zone &major)
{
MinorZone mz;
mz.isValid = false;
mz.isSupply = major.isSupply;
mz.priceLow = 0;
mz.priceHigh = 0;
mz.barIndex = -1;
if (!major.isValid)
return mz;
// Scan forward from major's bar toward current for first reaction zone aligned with major
int start = major.barIndex - 1;
int end = MathMax(1, start - Inp_MinorWindowBars);
// Heuristic: supply minor = bearish impulse followed by base; demand minor = bullish impulse followed by base
for (int i = start; i >= end; i--)
{
double o = iOpen(Inp_Symbol, Inp_WorkTF, i), c = iClose(Inp_Symbol, Inp_WorkTF, i);
double h = iHigh(Inp_Symbol, Inp_WorkTF, i), l = iLow(Inp_Symbol, Inp_WorkTF, i);
bool bearImpulse = (c < o && (o - c) > (h - l) * 0.5);
bool bullImpulse = (c > o && (c - o) > (h - l) * 0.5);
if (major.isSupply && bearImpulse)
{
// Base zone from next candle tight range
double h1 = iHigh(Inp_Symbol, Inp_WorkTF, i - 1), l1 = iLow(Inp_Symbol, Inp_WorkTF, i - 1);
mz.isValid = true;
mz.barIndex = i - 1;
mz.priceLow = l1 - Inp_ZonePadPoints * PointAdjust();
mz.priceHigh = h1 + Inp_ZonePadPoints * PointAdjust();
break;
}
if (!major.isSupply && bullImpulse)
{
double h1 = iHigh(Inp_Symbol, Inp_WorkTF, i - 1), l1 = iLow(Inp_Symbol, Inp_WorkTF, i - 1);
mz.isValid = true;
mz.barIndex = i - 1;
mz.priceLow = l1 - Inp_ZonePadPoints * PointAdjust();
mz.priceHigh = h1 + Inp_ZonePadPoints * PointAdjust();
break;
}
}
return mz;
}
//------------------------------- Signal logic -----------------------
bool PriceRetestedZone(const MinorZone &mz, bool supply, int shift = 0)
{
if (!mz.isValid)
return false;
double high = iHigh(Inp_Symbol, Inp_WorkTF, shift), low = iLow(Inp_Symbol, Inp_WorkTF, shift);
// For supply: retest when high touches zone; for demand: retest when low touches zone
if (supply)
return (high >= mz.priceLow && high <= mz.priceHigh && (high - iClose(Inp_Symbol, Inp_WorkTF, shift)) * 1.0 / PointAdjust() >= Inp_MinRetestWickPoints);
else
return (low <= mz.priceHigh && low >= mz.priceLow && (iClose(Inp_Symbol, Inp_WorkTF, shift) - low) * 1.0 / PointAdjust() >= Inp_MinRetestWickPoints);
}
bool ConfirmAtZone(bool supply)
{
int shift = 0;
if (Inp_UseEngulfingConfirm)
{
if (supply)
{
if (!EngulfingBearish(shift))
return false;
}
else
{
if (!EngulfingBullish(shift))
return false;
}
}
if (!VolumeSpike(shift))
return false;
if (!HTFConfluence(supply))
return false;
// Optional: RSI divergence could be added here if enabled.
return true;
}
//------------------------------- Risk & orders -----------------------
double CalcPositionSize(double sl_points)
{
double riskMoney = AccountInfoDouble(ACCOUNT_BALANCE) * (Inp_RiskPercent / 100.0);
double tickValue = TickValue();
double tickSize = TickSize();
// Convert SL points to quoted ticks
double ticks = sl_points * PointAdjust() / tickSize;
if (ticks <= 0.0)
return 0.0;
double lots = riskMoney / (ticks * tickValue);
// Normalize to symbol step
double minLot = SymbolInfoDouble(Inp_Symbol, SYMBOL_VOLUME_MIN);
double lotStep = SymbolInfoDouble(Inp_Symbol, SYMBOL_VOLUME_STEP);
double maxLot = SymbolInfoDouble(Inp_Symbol, SYMBOL_VOLUME_MAX);
lots = MathFloor(lots / lotStep) * lotStep;
lots = MathMax(minLot, MathMin(lots, maxLot));
return lots;
}
bool PlaceEntry(const MinorZone &mz, bool supply)
{
double bid = SymbolInfoDouble(Inp_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(Inp_Symbol, SYMBOL_ASK);
double point = PointAdjust();
// SL beneath/above minor zone (or ATR-based)
double sl, entry, tp;
double sl_points;
if (supply)
{
entry = Inp_UseLimitAtZone ? mz.priceLow : bid; // sell near zone top/low
double baseSL = mz.priceHigh + Inp_ZonePadPoints * point;
if (Inp_UseATRforSL)
{
//
double atrs[];
CopyBuffer(atrHandler, MAIN_LINE, 0, 1, atrs);
double atr = atrs[0];
baseSL = MathMax(baseSL, entry + (atr * Inp_ATR_Mult));
}
sl = baseSL;
sl_points = (sl - entry) / point;
sl_points = MathMax(sl_points, Inp_MinSLPoints);
tp = entry - (sl_points * point * Inp_R_MultipleTP);
// Position sizing
double lots = CalcPositionSize(sl_points);
if (lots <= 0)
return false;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = Inp_Symbol;
req.type = ORDER_TYPE_SELL;
req.volume = lots;
req.price = Inp_UseLimitAtZone ? entry : bid;
req.sl = sl;
req.tp = tp;
req.deviation = Inp_MaxSlippagePoints;
bool ok = OrderSend(req, res);
if (ok)
{
lastSignalTimeSell = TimeCurrent();
}
return ok;
}
else
{
entry = Inp_UseLimitAtZone ? mz.priceHigh : ask; // buy near zone top/high
double baseSL = mz.priceLow - Inp_ZonePadPoints * point;
if (Inp_UseATRforSL)
{
//
double atrs[];
CopyBuffer(atrHandler, MAIN_LINE, 0, 1, atrs);
double atr = atrs[0];
baseSL = MathMin(baseSL, entry - (atr * Inp_ATR_Mult));
}
sl = baseSL;
sl_points = (entry - sl) / point;
sl_points = MathMax(sl_points, Inp_MinSLPoints);
tp = entry + (sl_points * point * Inp_R_MultipleTP);
double lots = CalcPositionSize(sl_points);
if (lots <= 0)
return false;
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_DEAL;
req.symbol = Inp_Symbol;
req.type = ORDER_TYPE_BUY;
req.volume = lots;
req.price = Inp_UseLimitAtZone ? entry : ask;
req.sl = sl;
req.tp = tp;
req.deviation = Inp_MaxSlippagePoints;
bool ok = OrderSend(req, res);
if (ok)
{
lastSignalTimeBuy = TimeCurrent();
}
return ok;
}
}
//------------------------------- Position management ----------------
void ManageOpenPositions()
{
int total = PositionsTotal();
double point = PointAdjust();
for (int i = 0; i < total; i++)
{
string sym = PositionGetSymbol(i);
if (sym != Inp_Symbol)
continue;
if (!PositionSelect(sym))
continue;
long type = (long)PositionGetInteger(POSITION_TYPE);
double price = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double current = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(sym, SYMBOL_BID) : SymbolInfoDouble(sym, SYMBOL_ASK);
// Risk-free move to BE at TP1 (= 1R)
if (Inp_MoveToBEatTP1 && tp > 0 && sl > 0)
{
double rDist = MathAbs(tp - price) / Inp_R_MultipleTP;
bool hitTP1 = (type == POSITION_TYPE_BUY ? (current >= price + rDist) : (current <= price - rDist));
if (hitTP1)
{
double newSL = (type == POSITION_TYPE_BUY ? price + Inp_BE_OffsetPoints * point : price - Inp_BE_OffsetPoints * point);
if ((type == POSITION_TYPE_BUY && newSL > sl) || (type == POSITION_TYPE_SELL && newSL < sl))
{
MqlTradeRequest req;
MqlTradeResult res;
ZeroMemory(req);
ZeroMemory(res);
req.action = TRADE_ACTION_SLTP;
req.symbol = sym;
req.position = (ulong)PositionGetInteger(POSITION_TICKET);
req.sl = newSL;
req.tp = tp;
OrderSend(req, res);
}
}
}
}
}
//------------------------------- Engine ------------------------------
bool CoolOffOk(bool supply)
{
datetime last = supply ? lastSignalTimeSell : lastSignalTimeBuy;
if (last == 0)
return true;
// Convert bars to seconds approximate using last M15 duration
int seconds = Inp_SignalCoolOffBars * 15 * 60;
return (TimeCurrent() - last) >= seconds;
}
void TrySignal()
{
if (!IsSessionAllowed())
return;
Zone major = FindMajorZone();
if (!major.isValid)
return;
MinorZone minor = FindFirstMinorSameDirection(major);
if (!minor.isValid)
return;
// Only act on retest + confirmation
bool retest = PriceRetestedZone(minor, major.isSupply, 0);
if (!retest)
return;
if (!ConfirmAtZone(major.isSupply))
return;
if (Inp_OnePositionOnly && PositionsTotal() > 0)
return;
if (!CoolOffOk(major.isSupply))
return;
PlaceEntry(minor, major.isSupply);
}
//------------------------------- Standard events --------------------
int OnInit()
{
//
if (Symbol() != Inp_Symbol)
{
SymbolSelect(Inp_Symbol, true);
}
//
atrHandler = iATR(Inp_Symbol, Inp_WorkTF, Inp_ATR_Period);
return (INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
}
void OnTick()
{
static datetime lastBarTime = 0;
MqlRates rates[];
int copied = CopyRates(Inp_Symbol, Inp_WorkTF, 0, Inp_MinBarsLoaded, rates);
if (copied < Inp_MinBarsLoaded)
return;
datetime curBarTime = rates[0].time;
if (curBarTime != lastBarTime)
{
// New bar on M15: evaluate signal
TrySignal();
lastBarTime = curBarTime;
}
// Manage open positions continuously
ManageOpenPositions();
}