548 lines
16 KiB
Plaintext
548 lines
16 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| 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);
|
||
}
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|