last ...
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -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);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user