Update
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input group "=== Market ==="
|
||||
input string InpSymbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
||||
input double InpLots = 0.01;
|
||||
input int InpSlippagePoints = 30;
|
||||
input int InpMagic = 930101;
|
||||
input int InpMaxPositions = 6;
|
||||
input bool InpDebugLogs = true;
|
||||
|
||||
input group "=== EMA Trend State ==="
|
||||
input int InpEmaPeriod = 200;
|
||||
input int InpTrendLookbackBars = 12;
|
||||
input double InpTrendMinPoints = 120; // total EMA delta over lookback
|
||||
input double InpFlatMaxPoints = 40; // dead-flat band over lookback
|
||||
|
||||
input group "=== RSI Entries ==="
|
||||
input int InpRsiPeriod = 14;
|
||||
input double InpRsiDipLevel = 35.0; // buy dip in uptrend
|
||||
input double InpRsiSurgeLevel = 65.0; // sell surge in downtrend
|
||||
input bool InpUseCrossSignal = true; // true=cross, false=state-based
|
||||
|
||||
input group "=== Risk ==="
|
||||
input bool InpUseHardSLTP = false;
|
||||
input double InpSLPoints = 2500;
|
||||
input double InpTPPoints = 4500;
|
||||
|
||||
enum TrendState
|
||||
{
|
||||
TREND_FLAT = 0,
|
||||
TREND_UP = 1,
|
||||
TREND_DOWN = -1
|
||||
};
|
||||
|
||||
CTrade trade;
|
||||
datetime g_lastBarTime = 0;
|
||||
|
||||
void DebugLog(const string msg)
|
||||
{
|
||||
if(InpDebugLogs)
|
||||
Print("[EMARSIWarm] ", msg);
|
||||
}
|
||||
|
||||
bool IsNewBar(const string symbol, ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
datetime t = iTime(symbol, tf, 0);
|
||||
if(t <= 0 || t == g_lastBarTime)
|
||||
return false;
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
double GetIndicatorValue(const int handle, const int bufferIdx, const int shift)
|
||||
{
|
||||
if(handle == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
double v[1];
|
||||
if(CopyBuffer(handle, bufferIdx, shift, 1, v) <= 0)
|
||||
return 0.0;
|
||||
return v[0];
|
||||
}
|
||||
|
||||
double GetEma(const string symbol, ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int h = iMA(symbol, tf, period, 0, MODE_EMA, PRICE_CLOSE);
|
||||
double val = GetIndicatorValue(h, 0, shift);
|
||||
if(h != INVALID_HANDLE)
|
||||
IndicatorRelease(h);
|
||||
return val;
|
||||
}
|
||||
|
||||
double GetRsi(const string symbol, ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int h = iRSI(symbol, tf, period, PRICE_CLOSE);
|
||||
double val = GetIndicatorValue(h, 0, shift);
|
||||
if(h != INVALID_HANDLE)
|
||||
IndicatorRelease(h);
|
||||
return val;
|
||||
}
|
||||
|
||||
TrendState GetTrendState()
|
||||
{
|
||||
double emaNow = GetEma(InpSymbol, InpTimeframe, InpEmaPeriod, 1);
|
||||
double emaPast = GetEma(InpSymbol, InpTimeframe, InpEmaPeriod, 1 + InpTrendLookbackBars);
|
||||
if(emaNow == 0.0 || emaPast == 0.0)
|
||||
return TREND_FLAT;
|
||||
|
||||
double deltaPts = (emaNow - emaPast) / _Point;
|
||||
if(MathAbs(deltaPts) <= InpFlatMaxPoints)
|
||||
return TREND_FLAT;
|
||||
if(deltaPts >= InpTrendMinPoints)
|
||||
return TREND_UP;
|
||||
if(deltaPts <= -InpTrendMinPoints)
|
||||
return TREND_DOWN;
|
||||
return TREND_FLAT;
|
||||
}
|
||||
|
||||
int CountPositionsByMagic(const string symbol, const int magic)
|
||||
{
|
||||
int count = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(int)PositionGetInteger(POSITION_MAGIC) == magic)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
string TrendStateToString(const TrendState s)
|
||||
{
|
||||
if(s == TREND_UP) return "UP";
|
||||
if(s == TREND_DOWN) return "DOWN";
|
||||
return "FLAT";
|
||||
}
|
||||
|
||||
void CloseAllByMagic(const string symbol, const int magic)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(int)PositionGetInteger(POSITION_MAGIC) == magic)
|
||||
trade.PositionClose(t);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeSLTP(const bool isBuy, const double entry, double &sl, double &tp)
|
||||
{
|
||||
if(!InpUseHardSLTP)
|
||||
{
|
||||
sl = 0.0;
|
||||
tp = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(isBuy)
|
||||
{
|
||||
sl = entry - InpSLPoints * _Point;
|
||||
tp = entry + InpTPPoints * _Point;
|
||||
}
|
||||
else
|
||||
{
|
||||
sl = entry + InpSLPoints * _Point;
|
||||
tp = entry - InpTPPoints * _Point;
|
||||
}
|
||||
}
|
||||
|
||||
bool BuySignal()
|
||||
{
|
||||
double r1 = GetRsi(InpSymbol, InpTimeframe, InpRsiPeriod, 1);
|
||||
double r2 = GetRsi(InpSymbol, InpTimeframe, InpRsiPeriod, 2);
|
||||
if(r1 == 0.0 || r2 == 0.0)
|
||||
return false;
|
||||
|
||||
if(InpUseCrossSignal)
|
||||
return (r2 > InpRsiDipLevel && r1 <= InpRsiDipLevel); // fresh dip
|
||||
return (r1 <= InpRsiDipLevel);
|
||||
}
|
||||
|
||||
bool SellSignal()
|
||||
{
|
||||
double r1 = GetRsi(InpSymbol, InpTimeframe, InpRsiPeriod, 1);
|
||||
double r2 = GetRsi(InpSymbol, InpTimeframe, InpRsiPeriod, 2);
|
||||
if(r1 == 0.0 || r2 == 0.0)
|
||||
return false;
|
||||
|
||||
if(InpUseCrossSignal)
|
||||
return (r2 < InpRsiSurgeLevel && r1 >= InpRsiSurgeLevel); // fresh surge
|
||||
return (r1 >= InpRsiSurgeLevel);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(_Symbol != InpSymbol)
|
||||
{
|
||||
static datetime lastMismatchLog = 0;
|
||||
datetime nowBar = iTime(_Symbol, PERIOD_M1, 0);
|
||||
if(nowBar != lastMismatchLog)
|
||||
{
|
||||
lastMismatchLog = nowBar;
|
||||
DebugLog(StringFormat("Skipped: chart symbol=%s but InpSymbol=%s. Attach EA to %s chart or set InpSymbol=%s.",
|
||||
_Symbol, InpSymbol, InpSymbol, _Symbol));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!IsNewBar(InpSymbol, InpTimeframe))
|
||||
return;
|
||||
|
||||
TrendState state = GetTrendState();
|
||||
double rsi1 = GetRsi(InpSymbol, InpTimeframe, InpRsiPeriod, 1);
|
||||
double rsi2 = GetRsi(InpSymbol, InpTimeframe, InpRsiPeriod, 2);
|
||||
int posCount = CountPositionsByMagic(InpSymbol, InpMagic);
|
||||
DebugLog(StringFormat("Bar=%s state=%s rsi1=%.2f rsi2=%.2f positions=%d",
|
||||
TimeToString(iTime(InpSymbol, InpTimeframe, 1), TIME_DATE|TIME_MINUTES),
|
||||
TrendStateToString(state), rsi1, rsi2, posCount));
|
||||
|
||||
// Core idea: when EMA is "dead flat", flatten everything.
|
||||
if(state == TREND_FLAT)
|
||||
{
|
||||
DebugLog("Action: EMA flat -> closing all positions for this magic.");
|
||||
CloseAllByMagic(InpSymbol, InpMagic);
|
||||
return;
|
||||
}
|
||||
|
||||
if(posCount >= InpMaxPositions)
|
||||
{
|
||||
DebugLog(StringFormat("Skipped: max positions reached (%d).", InpMaxPositions));
|
||||
return;
|
||||
}
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
{
|
||||
DebugLog("Skipped: SymbolInfoTick failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
trade.SetDeviationInPoints(InpSlippagePoints);
|
||||
|
||||
if(state == TREND_UP && BuySignal())
|
||||
{
|
||||
ComputeSLTP(true, tick.ask, sl, tp);
|
||||
if(trade.Buy(InpLots, InpSymbol, tick.ask, sl, tp, "EMAUp_RSIDip_Buy"))
|
||||
DebugLog(StringFormat("BUY opened lots=%.2f price=%.2f sl=%.2f tp=%.2f", InpLots, tick.ask, sl, tp));
|
||||
else
|
||||
DebugLog(StringFormat("BUY failed retcode=%d", trade.ResultRetcode()));
|
||||
}
|
||||
else if(state == TREND_DOWN && SellSignal())
|
||||
{
|
||||
ComputeSLTP(false, tick.bid, sl, tp);
|
||||
if(trade.Sell(InpLots, InpSymbol, tick.bid, sl, tp, "EMADown_RSISurge_Sell"))
|
||||
DebugLog(StringFormat("SELL opened lots=%.2f price=%.2f sl=%.2f tp=%.2f", InpLots, tick.bid, sl, tp));
|
||||
else
|
||||
DebugLog(StringFormat("SELL failed retcode=%d", trade.ResultRetcode()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(state == TREND_UP)
|
||||
DebugLog("No entry: UP trend but RSI dip condition not met.");
|
||||
else if(state == TREND_DOWN)
|
||||
DebugLog("No entry: DOWN trend but RSI surge condition not met.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
\section{Simple EMA Price-Action: V1 Exploration Roadmap}
|
||||
\label{sec:simple-ema-v1-roadmap}
|
||||
|
||||
\textbf{Objective (V1).}
|
||||
Establish a robust baseline for the BTCUSD EMA price-action cross strategy before adding complexity. V1 prioritizes stability, explainability, and out-of-sample consistency.
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Baseline calibration}: optimize core parameters ($EMA$ period, minimum candle body, ATR stop/take-profit multipliers) with bounded search ranges and fixed transaction-cost assumptions.
|
||||
\item \textbf{Regime segmentation}: split results by volatility/trend regime (e.g., ATR percentile and ADX bins) to identify where the strategy has structural edge.
|
||||
\item \textbf{Session effects}: evaluate performance across Asia, London, and New York sessions; test session-specific body-size and risk multipliers.
|
||||
\item \textbf{Exit policy comparison}: compare fixed ATR exits vs. trailing stop and partial take-profit exits; report trade duration, payoff skew, and drawdown impact.
|
||||
\item \textbf{Execution stress test}: re-run with adverse spread/slippage scenarios to measure fragility and realistic live-trading degradation.
|
||||
\item \textbf{Position-sizing study}: benchmark fixed lot, volatility targeting, and capped fractional sizing with drawdown constraints.
|
||||
\item \textbf{Signal quality filters}: test wick/body ratio and momentum confirmation to reduce false crosses; quantify precision-recall tradeoff.
|
||||
\item \textbf{Walk-forward validation}: use rolling train-test windows and report parameter drift, out-of-sample Sharpe, and failure periods.
|
||||
\item \textbf{Statistical confidence}: include bootstrap confidence intervals for Sharpe, profit factor, win rate, and max drawdown.
|
||||
\item \textbf{Portfolio contribution}: evaluate correlation-adjusted P\&L contribution when combined with other robots in the united\_dynamic stack.
|
||||
\end{enumerate}
|
||||
|
||||
\textbf{V1 deliverables.}
|
||||
For each experiment, report: net P\&L, Sharpe, Sortino, max drawdown, profit factor, win rate, average trade duration, and out-of-sample performance delta.
|
||||
@@ -0,0 +1,179 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input group "=== Market ==="
|
||||
input string InpSymbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
||||
input double InpLots = 0.01;
|
||||
input int InpSlippagePoints = 30;
|
||||
input int InpMagic = 910001;
|
||||
|
||||
input group "=== Signal ==="
|
||||
input int InpEmaPeriod = 50;
|
||||
input int InpBodyMinPoints = 100; // Minimal candle body size
|
||||
|
||||
input group "=== Risk ==="
|
||||
input bool InpUseAtrStops = true;
|
||||
input int InpAtrPeriod = 14;
|
||||
input double InpSlAtrMult = 1.8;
|
||||
input double InpTpAtrMult = 3.0;
|
||||
input double InpFallbackSLPoints = 2500;
|
||||
input double InpFallbackTPPoints = 4500;
|
||||
|
||||
CTrade trade;
|
||||
datetime g_lastBarTime = 0;
|
||||
|
||||
bool IsNewBar(const string symbol, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
datetime t = iTime(symbol, tf, 0);
|
||||
if(t <= 0)
|
||||
return false;
|
||||
|
||||
if(t == g_lastBarTime)
|
||||
return false;
|
||||
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectOwnPosition(const string symbol, const int magic)
|
||||
{
|
||||
if(!PositionSelect(symbol))
|
||||
return false;
|
||||
return (int)PositionGetInteger(POSITION_MAGIC) == magic;
|
||||
}
|
||||
|
||||
double GetAtrPoints(const string symbol, const ENUM_TIMEFRAMES tf, const int period)
|
||||
{
|
||||
int hAtr = iATR(symbol, tf, period);
|
||||
if(hAtr == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
|
||||
double atrBuff[1];
|
||||
if(CopyBuffer(hAtr, 0, 1, 1, atrBuff) <= 0)
|
||||
{
|
||||
IndicatorRelease(hAtr);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
IndicatorRelease(hAtr);
|
||||
return atrBuff[0] / _Point;
|
||||
}
|
||||
|
||||
double GetEmaValue(const string symbol, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int hEma = iMA(symbol, tf, period, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if(hEma == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
|
||||
double emaBuff[1];
|
||||
if(CopyBuffer(hEma, 0, shift, 1, emaBuff) <= 0)
|
||||
{
|
||||
IndicatorRelease(hEma);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
IndicatorRelease(hEma);
|
||||
return emaBuff[0];
|
||||
}
|
||||
|
||||
void ComputeStops(const bool isBuy, const double entry, double &sl, double &tp)
|
||||
{
|
||||
double slPts = InpFallbackSLPoints;
|
||||
double tpPts = InpFallbackTPPoints;
|
||||
|
||||
if(InpUseAtrStops)
|
||||
{
|
||||
double atrPts = GetAtrPoints(InpSymbol, InpTimeframe, InpAtrPeriod);
|
||||
if(atrPts > 0.0)
|
||||
{
|
||||
slPts = MathMax(atrPts * InpSlAtrMult, 100.0);
|
||||
tpPts = MathMax(atrPts * InpTpAtrMult, 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
if(isBuy)
|
||||
{
|
||||
sl = entry - slPts * _Point;
|
||||
tp = entry + tpPts * _Point;
|
||||
}
|
||||
else
|
||||
{
|
||||
sl = entry + slPts * _Point;
|
||||
tp = entry - tpPts * _Point;
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
if(!SymbolSelect(InpSymbol, true))
|
||||
{
|
||||
Print("Failed to select symbol: ", InpSymbol);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
trade.SetDeviationInPoints(InpSlippagePoints);
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(_Symbol != InpSymbol)
|
||||
return;
|
||||
|
||||
if(!IsNewBar(InpSymbol, InpTimeframe))
|
||||
return;
|
||||
|
||||
// Use closed candles (shift 1 and 2) to avoid intrabar repainting behavior.
|
||||
double o1 = iOpen(InpSymbol, InpTimeframe, 1);
|
||||
double c1 = iClose(InpSymbol, InpTimeframe, 1);
|
||||
double o2 = iOpen(InpSymbol, InpTimeframe, 2);
|
||||
double c2 = iClose(InpSymbol, InpTimeframe, 2);
|
||||
double e1 = GetEmaValue(InpSymbol, InpTimeframe, InpEmaPeriod, 1);
|
||||
double e2 = GetEmaValue(InpSymbol, InpTimeframe, InpEmaPeriod, 2);
|
||||
|
||||
if(e1 == 0.0 || e2 == 0.0)
|
||||
return;
|
||||
|
||||
bool bullishBody = (c1 > o1) && ((c1 - o1) / _Point >= InpBodyMinPoints);
|
||||
bool bearishBody = (o1 > c1) && ((o1 - c1) / _Point >= InpBodyMinPoints);
|
||||
bool crossedUp = (c2 <= e2 && c1 > e1);
|
||||
bool crossedDown = (c2 >= e2 && c1 < e1);
|
||||
|
||||
bool longSignal = crossedUp && bullishBody;
|
||||
bool shortSignal = crossedDown && bearishBody;
|
||||
|
||||
bool hasPos = SelectOwnPosition(InpSymbol, InpMagic);
|
||||
if(hasPos)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if((posType == POSITION_TYPE_BUY && shortSignal) ||
|
||||
(posType == POSITION_TYPE_SELL && longSignal))
|
||||
{
|
||||
trade.PositionClose(InpSymbol);
|
||||
hasPos = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(hasPos)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return;
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(longSignal)
|
||||
{
|
||||
ComputeStops(true, tick.ask, sl, tp);
|
||||
trade.Buy(InpLots, InpSymbol, tick.ask, sl, tp, "Simple EMA PA Cross");
|
||||
}
|
||||
else if(shortSignal)
|
||||
{
|
||||
ComputeStops(false, tick.bid, sl, tp);
|
||||
trade.Sell(InpLots, InpSymbol, tick.bid, sl, tp, "Simple EMA PA Cross");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
#property strict
|
||||
#property version "1.10"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input group "=== Market ==="
|
||||
input string InpSymbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
||||
input double InpLots = 0.01;
|
||||
input int InpSlippagePoints = 30;
|
||||
input int InpMagic = 910011;
|
||||
|
||||
input group "=== Signal ==="
|
||||
input int InpEmaPeriod = 50;
|
||||
input int InpBodyMinPoints = 100;
|
||||
input bool InpUseAdxFilter = true;
|
||||
input int InpAdxPeriod = 14;
|
||||
input double InpAdxMin = 18.0;
|
||||
|
||||
input group "=== Session Filter (Server Hour) ==="
|
||||
input bool InpUseSessionFilter = false;
|
||||
input int InpSessionStartHour = 6;
|
||||
input int InpSessionEndHour = 22;
|
||||
|
||||
input group "=== Risk ==="
|
||||
input bool InpUseAtrStops = true;
|
||||
input int InpAtrPeriod = 14;
|
||||
input double InpSlAtrMult = 1.8;
|
||||
input double InpTpAtrMult = 3.0;
|
||||
input bool InpUseHardSL = true;
|
||||
input bool InpUseHardTP = false;
|
||||
input bool InpUseTrailingStop = true;
|
||||
input double InpTrailAtrMult = 1.2;
|
||||
input bool InpUseBreakEven = true;
|
||||
input double InpBreakEvenAtrTrigger = 1.0;
|
||||
input double InpBreakEvenLockPoints = 100;
|
||||
input double InpFallbackSLPoints = 2500;
|
||||
input double InpFallbackTPPoints = 4500;
|
||||
|
||||
CTrade trade;
|
||||
datetime g_lastBarTime = 0;
|
||||
|
||||
bool IsNewBar(const string symbol, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
datetime t = iTime(symbol, tf, 0);
|
||||
if(t <= 0 || t == g_lastBarTime)
|
||||
return false;
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInAllowedSession()
|
||||
{
|
||||
if(!InpUseSessionFilter)
|
||||
return true;
|
||||
|
||||
MqlDateTime dt;
|
||||
if(!TimeToStruct(TimeCurrent(), dt))
|
||||
return true;
|
||||
int h = dt.hour;
|
||||
if(InpSessionStartHour <= InpSessionEndHour)
|
||||
return (h >= InpSessionStartHour && h < InpSessionEndHour);
|
||||
|
||||
// Overnight window, e.g. 22 -> 6
|
||||
return (h >= InpSessionStartHour || h < InpSessionEndHour);
|
||||
}
|
||||
|
||||
bool SelectOwnPosition(const string symbol, const int magic)
|
||||
{
|
||||
if(!PositionSelect(symbol))
|
||||
return false;
|
||||
return (int)PositionGetInteger(POSITION_MAGIC) == magic;
|
||||
}
|
||||
|
||||
double GetIndicatorValue(const int handle, const int bufferIndex, const int shift)
|
||||
{
|
||||
if(handle == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
|
||||
double buff[1];
|
||||
if(CopyBuffer(handle, bufferIndex, shift, 1, buff) <= 0)
|
||||
return 0.0;
|
||||
return buff[0];
|
||||
}
|
||||
|
||||
double GetAtrPoints(const string symbol, const ENUM_TIMEFRAMES tf, const int period)
|
||||
{
|
||||
int hAtr = iATR(symbol, tf, period);
|
||||
double atr = GetIndicatorValue(hAtr, 0, 1);
|
||||
if(hAtr != INVALID_HANDLE)
|
||||
IndicatorRelease(hAtr);
|
||||
if(atr <= 0.0)
|
||||
return 0.0;
|
||||
return atr / _Point;
|
||||
}
|
||||
|
||||
double GetEmaValue(const string symbol, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int hEma = iMA(symbol, tf, period, 0, MODE_EMA, PRICE_CLOSE);
|
||||
double ema = GetIndicatorValue(hEma, 0, shift);
|
||||
if(hEma != INVALID_HANDLE)
|
||||
IndicatorRelease(hEma);
|
||||
return ema;
|
||||
}
|
||||
|
||||
double GetAdxValue(const string symbol, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int hAdx = iADX(symbol, tf, period);
|
||||
double adx = GetIndicatorValue(hAdx, 0, shift);
|
||||
if(hAdx != INVALID_HANDLE)
|
||||
IndicatorRelease(hAdx);
|
||||
return adx;
|
||||
}
|
||||
|
||||
void ComputeStops(const bool isBuy, const double entry, double &sl, double &tp)
|
||||
{
|
||||
double slPts = InpFallbackSLPoints;
|
||||
double tpPts = InpFallbackTPPoints;
|
||||
|
||||
if(InpUseAtrStops)
|
||||
{
|
||||
double atrPts = GetAtrPoints(InpSymbol, InpTimeframe, InpAtrPeriod);
|
||||
if(atrPts > 0.0)
|
||||
{
|
||||
slPts = MathMax(atrPts * InpSlAtrMult, 100.0);
|
||||
tpPts = MathMax(atrPts * InpTpAtrMult, 100.0);
|
||||
}
|
||||
}
|
||||
|
||||
if(isBuy)
|
||||
{
|
||||
sl = InpUseHardSL ? (entry - slPts * _Point) : 0.0;
|
||||
tp = InpUseHardTP ? (entry + tpPts * _Point) : 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
sl = InpUseHardSL ? (entry + slPts * _Point) : 0.0;
|
||||
tp = InpUseHardTP ? (entry - tpPts * _Point) : 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void ManageOpenPosition()
|
||||
{
|
||||
if(!SelectOwnPosition(InpSymbol, InpMagic))
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return;
|
||||
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double curSL = PositionGetDouble(POSITION_SL);
|
||||
double curTP = PositionGetDouble(POSITION_TP);
|
||||
|
||||
double atrPts = GetAtrPoints(InpSymbol, InpTimeframe, InpAtrPeriod);
|
||||
if(atrPts <= 0.0)
|
||||
atrPts = InpFallbackSLPoints;
|
||||
|
||||
double triggerPts = atrPts * InpBreakEvenAtrTrigger;
|
||||
double trailPts = MathMax(atrPts * InpTrailAtrMult, 50.0);
|
||||
|
||||
double newSL = curSL;
|
||||
bool needModify = false;
|
||||
|
||||
if(posType == POSITION_TYPE_BUY)
|
||||
{
|
||||
double profitPts = (tick.bid - openPrice) / _Point;
|
||||
|
||||
if(InpUseBreakEven && profitPts >= triggerPts)
|
||||
{
|
||||
double beSL = openPrice + InpBreakEvenLockPoints * _Point;
|
||||
if(newSL == 0.0 || beSL > newSL)
|
||||
{
|
||||
newSL = beSL;
|
||||
needModify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(InpUseTrailingStop)
|
||||
{
|
||||
double trailSL = tick.bid - trailPts * _Point;
|
||||
if((newSL == 0.0 || trailSL > newSL) && trailSL < tick.bid)
|
||||
{
|
||||
newSL = trailSL;
|
||||
needModify = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(posType == POSITION_TYPE_SELL)
|
||||
{
|
||||
double profitPts = (openPrice - tick.ask) / _Point;
|
||||
|
||||
if(InpUseBreakEven && profitPts >= triggerPts)
|
||||
{
|
||||
double beSL = openPrice - InpBreakEvenLockPoints * _Point;
|
||||
if(newSL == 0.0 || beSL < newSL)
|
||||
{
|
||||
newSL = beSL;
|
||||
needModify = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(InpUseTrailingStop)
|
||||
{
|
||||
double trailSL = tick.ask + trailPts * _Point;
|
||||
if((newSL == 0.0 || trailSL < newSL) && trailSL > tick.ask)
|
||||
{
|
||||
newSL = trailSL;
|
||||
needModify = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(needModify)
|
||||
trade.PositionModify(InpSymbol, newSL, curTP);
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
if(!SymbolSelect(InpSymbol, true))
|
||||
{
|
||||
Print("Failed to select symbol: ", InpSymbol);
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
trade.SetDeviationInPoints(InpSlippagePoints);
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(_Symbol != InpSymbol)
|
||||
return;
|
||||
|
||||
ManageOpenPosition();
|
||||
if(!IsInAllowedSession())
|
||||
return;
|
||||
if(!IsNewBar(InpSymbol, InpTimeframe))
|
||||
return;
|
||||
|
||||
double o1 = iOpen(InpSymbol, InpTimeframe, 1);
|
||||
double c1 = iClose(InpSymbol, InpTimeframe, 1);
|
||||
double c2 = iClose(InpSymbol, InpTimeframe, 2);
|
||||
double e1 = GetEmaValue(InpSymbol, InpTimeframe, InpEmaPeriod, 1);
|
||||
double e2 = GetEmaValue(InpSymbol, InpTimeframe, InpEmaPeriod, 2);
|
||||
if(e1 == 0.0 || e2 == 0.0)
|
||||
return;
|
||||
|
||||
if(InpUseAdxFilter)
|
||||
{
|
||||
double adx = GetAdxValue(InpSymbol, InpTimeframe, InpAdxPeriod, 1);
|
||||
if(adx < InpAdxMin)
|
||||
return;
|
||||
}
|
||||
|
||||
bool bullishBody = (c1 > o1) && ((c1 - o1) / _Point >= InpBodyMinPoints);
|
||||
bool bearishBody = (o1 > c1) && ((o1 - c1) / _Point >= InpBodyMinPoints);
|
||||
bool crossedUp = (c2 <= e2 && c1 > e1);
|
||||
bool crossedDown = (c2 >= e2 && c1 < e1);
|
||||
|
||||
bool longSignal = crossedUp && bullishBody;
|
||||
bool shortSignal = crossedDown && bearishBody;
|
||||
|
||||
bool hasPos = SelectOwnPosition(InpSymbol, InpMagic);
|
||||
if(hasPos)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
if((posType == POSITION_TYPE_BUY && shortSignal) ||
|
||||
(posType == POSITION_TYPE_SELL && longSignal))
|
||||
{
|
||||
trade.PositionClose(InpSymbol);
|
||||
hasPos = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(hasPos)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return;
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(longSignal)
|
||||
{
|
||||
ComputeStops(true, tick.ask, sl, tp);
|
||||
trade.Buy(InpLots, InpSymbol, tick.ask, sl, tp, "Simple EMA PA Cross V1");
|
||||
}
|
||||
else if(shortSignal)
|
||||
{
|
||||
ComputeStops(false, tick.bid, sl, tp);
|
||||
trade.Sell(InpLots, InpSymbol, tick.bid, sl, tp, "Simple EMA PA Cross V1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,378 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input group "=== Common ==="
|
||||
input string InpSymbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
||||
input int InpSlippagePoints = 30;
|
||||
input int InpPivotLookbackBars = 120;
|
||||
input int InpMinSwingPoints = 500;
|
||||
|
||||
input group "=== Robot 1: Fibonacci Retracement ==="
|
||||
input bool FR_Enabled = true;
|
||||
input int FR_Magic = 920101;
|
||||
input double FR_Lots = 0.01;
|
||||
input bool FR_BuyAt618 = true;
|
||||
input bool FR_BuyAt500 = false;
|
||||
input bool FR_UseHardSLTP = true;
|
||||
input double FR_SL_BufferPoints = 400;
|
||||
input double FR_TP_BufferPoints = 400;
|
||||
input int FR_MaxHoldingBars = 96; // time-stop safety
|
||||
input bool FR_CloseOnStructureBreak = true; // close if recent swing low breaks
|
||||
|
||||
input group "=== Robot 2: Fibonacci Trend Extension ==="
|
||||
input bool FE_Enabled = true;
|
||||
input int FE_Magic = 920202;
|
||||
input double FE_Lots = 0.01;
|
||||
input bool FE_UseHardSLTP = true;
|
||||
input double FE_SL_BufferPoints = 400;
|
||||
input double FE_ExtensionLevel = 1.272; // Common values: 1.272 / 1.618
|
||||
input int FE_MinBarsBetweenTrades = 6;
|
||||
input double FE_MinStopPoints = 3000;
|
||||
input int FE_AtrPeriod = 14;
|
||||
input double FE_MinStopAtrMult = 1.2;
|
||||
input double FE_MinRR = 1.5;
|
||||
|
||||
CTrade trade;
|
||||
datetime g_lastBarTime = 0;
|
||||
datetime g_lastFEEntryTime = 0;
|
||||
datetime g_lastFREntryTime = 0;
|
||||
|
||||
bool IsNewBar(const string symbol, ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
datetime t = iTime(symbol, tf, 0);
|
||||
if(t <= 0 || t == g_lastBarTime)
|
||||
return false;
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetLowestLow(const string symbol, ENUM_TIMEFRAMES tf, const int bars, int &idx, double &price)
|
||||
{
|
||||
idx = iLowest(symbol, tf, MODE_LOW, bars, 1);
|
||||
if(idx < 0)
|
||||
return false;
|
||||
price = iLow(symbol, tf, idx);
|
||||
return (price > 0.0);
|
||||
}
|
||||
|
||||
bool GetHighestHigh(const string symbol, ENUM_TIMEFRAMES tf, const int bars, int &idx, double &price)
|
||||
{
|
||||
idx = iHighest(symbol, tf, MODE_HIGH, bars, 1);
|
||||
if(idx < 0)
|
||||
return false;
|
||||
price = iHigh(symbol, tf, idx);
|
||||
return (price > 0.0);
|
||||
}
|
||||
|
||||
double GetAtrPrice(const string symbol, ENUM_TIMEFRAMES tf, const int period)
|
||||
{
|
||||
int hAtr = iATR(symbol, tf, period);
|
||||
if(hAtr == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
double b[1];
|
||||
if(CopyBuffer(hAtr, 0, 1, 1, b) <= 0)
|
||||
{
|
||||
IndicatorRelease(hAtr);
|
||||
return 0.0;
|
||||
}
|
||||
IndicatorRelease(hAtr);
|
||||
return b[0];
|
||||
}
|
||||
|
||||
bool PositionExistsByMagic(const string symbol, const int magic)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(int)PositionGetInteger(POSITION_MAGIC) == magic)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetPositionByMagic(const string symbol, const int magic, ulong &ticket, ENUM_POSITION_TYPE &posType, datetime &openTime)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; --i)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(int)PositionGetInteger(POSITION_MAGIC) == magic)
|
||||
{
|
||||
ticket = t;
|
||||
posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double NormalizePrice(const string symbol, const double price)
|
||||
{
|
||||
int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS);
|
||||
return NormalizeDouble(price, digits);
|
||||
}
|
||||
|
||||
bool ValidateAndAdjustStops(const bool isBuy, double &sl, double &tp)
|
||||
{
|
||||
if(sl == 0.0 && tp == 0.0)
|
||||
return true;
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return false;
|
||||
|
||||
int stopsLevelPts = (int)SymbolInfoInteger(InpSymbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
int freezeLevelPts = (int)SymbolInfoInteger(InpSymbol, SYMBOL_TRADE_FREEZE_LEVEL);
|
||||
double minDist = (double)MathMax(stopsLevelPts, freezeLevelPts) * _Point + 2.0 * _Point;
|
||||
|
||||
if(isBuy)
|
||||
{
|
||||
if(sl > 0.0 && sl >= tick.bid - minDist)
|
||||
sl = tick.bid - minDist;
|
||||
if(tp > 0.0 && tp <= tick.ask + minDist)
|
||||
tp = tick.ask + minDist;
|
||||
if(sl > 0.0 && sl >= tick.bid)
|
||||
return false;
|
||||
if(tp > 0.0 && tp <= tick.ask)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(sl > 0.0 && sl <= tick.ask + minDist)
|
||||
sl = tick.ask + minDist;
|
||||
if(tp > 0.0 && tp >= tick.bid - minDist)
|
||||
tp = tick.bid - minDist;
|
||||
if(sl > 0.0 && sl <= tick.ask)
|
||||
return false;
|
||||
if(tp > 0.0 && tp >= tick.bid)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(sl > 0.0)
|
||||
sl = NormalizePrice(InpSymbol, sl);
|
||||
if(tp > 0.0)
|
||||
tp = NormalizePrice(InpSymbol, tp);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenBuy(const int magic, const double lots, const string comment, const double sl, const double tp)
|
||||
{
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return false;
|
||||
double useSL = sl, useTP = tp;
|
||||
if(!ValidateAndAdjustStops(true, useSL, useTP))
|
||||
return false;
|
||||
trade.SetExpertMagicNumber(magic);
|
||||
bool ok = trade.Buy(lots, InpSymbol, tick.ask, useSL, useTP, comment);
|
||||
if(ok && magic == FR_Magic)
|
||||
g_lastFREntryTime = iTime(InpSymbol, InpTimeframe, 0);
|
||||
if(ok && magic == FE_Magic)
|
||||
g_lastFEEntryTime = iTime(InpSymbol, InpTimeframe, 0);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool OpenSell(const int magic, const double lots, const string comment, const double sl, const double tp)
|
||||
{
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return false;
|
||||
double useSL = sl, useTP = tp;
|
||||
if(!ValidateAndAdjustStops(false, useSL, useTP))
|
||||
return false;
|
||||
trade.SetExpertMagicNumber(magic);
|
||||
bool ok = trade.Sell(lots, InpSymbol, tick.bid, useSL, useTP, comment);
|
||||
if(ok && magic == FE_Magic)
|
||||
g_lastFEEntryTime = iTime(InpSymbol, InpTimeframe, 0);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void RunFibonacciRetracement()
|
||||
{
|
||||
if(!FR_Enabled)
|
||||
return;
|
||||
if(PositionExistsByMagic(InpSymbol, FR_Magic))
|
||||
return;
|
||||
|
||||
int idxLow = -1, idxHigh = -1;
|
||||
double swingLow = 0.0, swingHigh = 0.0;
|
||||
if(!GetLowestLow(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxLow, swingLow))
|
||||
return;
|
||||
if(!GetHighestHigh(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxHigh, swingHigh))
|
||||
return;
|
||||
|
||||
double rangePts = (swingHigh - swingLow) / _Point;
|
||||
if(rangePts < InpMinSwingPoints)
|
||||
return;
|
||||
|
||||
// Uptrend retracement model: low appears before high.
|
||||
bool upSwing = (idxLow > idxHigh);
|
||||
if(!upSwing)
|
||||
return;
|
||||
|
||||
double fib50 = swingHigh - (swingHigh - swingLow) * 0.500;
|
||||
double fib61 = swingHigh - (swingHigh - swingLow) * 0.618;
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return;
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(FR_UseHardSLTP)
|
||||
{
|
||||
// Positional levels: SL below swing low, TP near prior swing high breakout.
|
||||
sl = swingLow - FR_SL_BufferPoints * _Point;
|
||||
tp = swingHigh + FR_TP_BufferPoints * _Point;
|
||||
}
|
||||
|
||||
if(FR_BuyAt618 && tick.ask <= fib61)
|
||||
OpenBuy(FR_Magic, FR_Lots, "FiboRetrace-61.8 Buy", sl, tp);
|
||||
else if(FR_BuyAt500 && tick.ask <= fib50)
|
||||
OpenBuy(FR_Magic, FR_Lots, "FiboRetrace-50.0 Buy", sl, tp);
|
||||
}
|
||||
|
||||
void ManageFibonacciRetracementExit()
|
||||
{
|
||||
if(!FR_Enabled)
|
||||
return;
|
||||
|
||||
ulong ticket = 0;
|
||||
ENUM_POSITION_TYPE posType = WRONG_VALUE;
|
||||
datetime openTime = 0;
|
||||
if(!GetPositionByMagic(InpSymbol, FR_Magic, ticket, posType, openTime))
|
||||
return;
|
||||
|
||||
int tfSec = PeriodSeconds(InpTimeframe);
|
||||
if(tfSec <= 0)
|
||||
tfSec = 60;
|
||||
int barsHeld = (int)((iTime(InpSymbol, InpTimeframe, 0) - openTime) / tfSec);
|
||||
|
||||
// 1) Time stop: force close stale retracement trades.
|
||||
if(FR_MaxHoldingBars > 0 && barsHeld >= FR_MaxHoldingBars)
|
||||
{
|
||||
trade.PositionClose(ticket);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) Structure invalidation: if latest swing violates the trade idea, exit.
|
||||
if(FR_CloseOnStructureBreak)
|
||||
{
|
||||
int idxLow = -1, idxHigh = -1;
|
||||
double swingLow = 0.0, swingHigh = 0.0;
|
||||
if(GetLowestLow(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxLow, swingLow) &&
|
||||
GetHighestHigh(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxHigh, swingHigh))
|
||||
{
|
||||
MqlTick tick;
|
||||
if(SymbolInfoTick(InpSymbol, tick))
|
||||
{
|
||||
double invalidateBuffer = FR_SL_BufferPoints * _Point;
|
||||
if(posType == POSITION_TYPE_BUY && tick.bid < (swingLow - invalidateBuffer))
|
||||
trade.PositionClose(ticket);
|
||||
else if(posType == POSITION_TYPE_SELL && tick.ask > (swingHigh + invalidateBuffer))
|
||||
trade.PositionClose(ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunFibonacciExtension()
|
||||
{
|
||||
if(!FE_Enabled)
|
||||
return;
|
||||
if(PositionExistsByMagic(InpSymbol, FE_Magic))
|
||||
return;
|
||||
if(g_lastFEEntryTime > 0)
|
||||
{
|
||||
int tfSec = PeriodSeconds(InpTimeframe);
|
||||
if(tfSec > 0)
|
||||
{
|
||||
int barsSince = (int)((iTime(InpSymbol, InpTimeframe, 0) - g_lastFEEntryTime) / tfSec);
|
||||
if(barsSince < FE_MinBarsBetweenTrades)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int idxLow = -1, idxHigh = -1;
|
||||
double swingLow = 0.0, swingHigh = 0.0;
|
||||
if(!GetLowestLow(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxLow, swingLow))
|
||||
return;
|
||||
if(!GetHighestHigh(InpSymbol, InpTimeframe, InpPivotLookbackBars, idxHigh, swingHigh))
|
||||
return;
|
||||
|
||||
double rangePts = (swingHigh - swingLow) / _Point;
|
||||
if(rangePts < InpMinSwingPoints)
|
||||
return;
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(InpSymbol, tick))
|
||||
return;
|
||||
|
||||
// Continuation breakout model:
|
||||
// - If up swing (low before high), buy above swing high and target extension.
|
||||
// - If down swing (high before low), sell below swing low and target extension.
|
||||
bool upSwing = (idxLow > idxHigh);
|
||||
|
||||
if(upSwing && tick.ask > swingHigh)
|
||||
{
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(FE_UseHardSLTP)
|
||||
{
|
||||
sl = swingHigh - FE_SL_BufferPoints * _Point;
|
||||
double extTP = swingLow + (swingHigh - swingLow) * FE_ExtensionLevel;
|
||||
double atr = GetAtrPrice(InpSymbol, InpTimeframe, FE_AtrPeriod);
|
||||
double minRisk = MathMax(FE_MinStopPoints * _Point, atr * FE_MinStopAtrMult);
|
||||
double risk = tick.ask - sl;
|
||||
if(risk < minRisk)
|
||||
return; // Skip fragile entries with overly tight stop.
|
||||
double rrTP = tick.ask + risk * FE_MinRR;
|
||||
tp = MathMax(extTP, rrTP);
|
||||
}
|
||||
OpenBuy(FE_Magic, FE_Lots, "FiboExtension Buy", sl, tp);
|
||||
}
|
||||
else if(!upSwing && tick.bid < swingLow)
|
||||
{
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(FE_UseHardSLTP)
|
||||
{
|
||||
sl = swingLow + FE_SL_BufferPoints * _Point;
|
||||
double extTP = swingHigh - (swingHigh - swingLow) * FE_ExtensionLevel;
|
||||
double atr = GetAtrPrice(InpSymbol, InpTimeframe, FE_AtrPeriod);
|
||||
double minRisk = MathMax(FE_MinStopPoints * _Point, atr * FE_MinStopAtrMult);
|
||||
double risk = sl - tick.bid;
|
||||
if(risk < minRisk)
|
||||
return; // Skip fragile entries with overly tight stop.
|
||||
double rrTP = tick.bid - risk * FE_MinRR;
|
||||
tp = MathMin(extTP, rrTP);
|
||||
}
|
||||
OpenSell(FE_Magic, FE_Lots, "FiboExtension Sell", sl, tp);
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
if(!SymbolSelect(InpSymbol, true))
|
||||
return(INIT_FAILED);
|
||||
trade.SetDeviationInPoints(InpSlippagePoints);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(_Symbol != InpSymbol)
|
||||
return;
|
||||
if(!IsNewBar(InpSymbol, InpTimeframe))
|
||||
return;
|
||||
|
||||
ManageFibonacciRetracementExit();
|
||||
RunFibonacciRetracement();
|
||||
RunFibonacciExtension();
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| rsi-scalping.mq5 |
|
||||
//| Lab EA: EMA 9/21 + Stochastic RSI — M1 scalping rules (tutorial) |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Lab"
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- inputs: indicator tuning (video defaults)
|
||||
input ENUM_TIMEFRAMES InpTf = PERIOD_M1; // Chart / signal timeframe
|
||||
input int InpEmaFast = 9; // EMA fast (short-term)
|
||||
input int InpEmaSlow = 21; // EMA slow (trend)
|
||||
input int InpRsiLen = 14; // RSI length (Stoch RSI core)
|
||||
input int InpStochLen = 14; // Stochastic lookback on RSI
|
||||
input int InpStochK = 4; // Stoch RSI %K smoothing
|
||||
input int InpStochD = 7; // Stoch RSI %D smoothing
|
||||
input double InpObLevel = 80.0; // Overbought line
|
||||
input double InpOsLevel = 20.0; // Oversold line
|
||||
//--- filters
|
||||
input bool InpUseMidZoneFilter = true; // Skip if K,D in 40–60 (indecision)
|
||||
input int InpMinBarsSinceCross = 10; // Min bars between EMA crosses
|
||||
input bool InpUseHtfFilter = false; // Align with higher TF EMAs
|
||||
input ENUM_TIMEFRAMES InpHtf = PERIOD_M5; // Higher timeframe
|
||||
input double InpMinEmaSepPts = 0.0; // Min |EMA9-EMA21| in points (0=off)
|
||||
//--- risk
|
||||
input double InpLots = 0.01;
|
||||
input int InpSlBufferPts = 20; // Extra SL beyond last 2-bar extreme
|
||||
input double InpTpRiskMultiple = 1.75; // TP = risk * this (1.5–2.0 typical)
|
||||
input bool InpExitOnEma9Break = true; // Close long if close < EMA9 (vice versa shorts)
|
||||
input bool InpExitOnStochZone = true; // Close long at Stoch RSI ≥ OB; short at ≤ OS
|
||||
//--- session
|
||||
input ulong InpMagic = 20260412;
|
||||
input int InpSlippagePts = 30;
|
||||
|
||||
CTrade g_trade;
|
||||
|
||||
int g_hEmaFast = INVALID_HANDLE;
|
||||
int g_hEmaSlow = INVALID_HANDLE;
|
||||
int g_hRsi = INVALID_HANDLE;
|
||||
int g_hEmaFastHtf = INVALID_HANDLE;
|
||||
int g_hEmaSlowHtf = INVALID_HANDLE;
|
||||
|
||||
double g_emaFast[];
|
||||
double g_emaSlow[];
|
||||
double g_rsi[];
|
||||
double g_stochK[];
|
||||
double g_stochD[];
|
||||
double g_emaFastHtf[];
|
||||
double g_emaSlowHtf[];
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
g_trade.SetExpertMagicNumber(InpMagic);
|
||||
g_trade.SetDeviationInPoints(InpSlippagePts);
|
||||
SetTradeFillingBySymbol();
|
||||
|
||||
g_hEmaFast = iMA(_Symbol, InpTf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
|
||||
g_hEmaSlow = iMA(_Symbol, InpTf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
|
||||
g_hRsi = iRSI(_Symbol, InpTf, InpRsiLen, PRICE_CLOSE);
|
||||
if(InpUseHtfFilter)
|
||||
{
|
||||
g_hEmaFastHtf = iMA(_Symbol, InpHtf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
|
||||
g_hEmaSlowHtf = iMA(_Symbol, InpHtf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
|
||||
}
|
||||
|
||||
if(g_hEmaFast == INVALID_HANDLE || g_hEmaSlow == INVALID_HANDLE || g_hRsi == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
if(InpUseHtfFilter && (g_hEmaFastHtf == INVALID_HANDLE || g_hEmaSlowHtf == INVALID_HANDLE))
|
||||
return INIT_FAILED;
|
||||
|
||||
ArraySetAsSeries(g_emaFast, true);
|
||||
ArraySetAsSeries(g_emaSlow, true);
|
||||
ArraySetAsSeries(g_rsi, true);
|
||||
ArraySetAsSeries(g_stochK, true);
|
||||
ArraySetAsSeries(g_stochD, true);
|
||||
ArraySetAsSeries(g_emaFastHtf, true);
|
||||
ArraySetAsSeries(g_emaSlowHtf, true);
|
||||
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_hEmaFast != INVALID_HANDLE) IndicatorRelease(g_hEmaFast);
|
||||
if(g_hEmaSlow != INVALID_HANDLE) IndicatorRelease(g_hEmaSlow);
|
||||
if(g_hRsi != INVALID_HANDLE) IndicatorRelease(g_hRsi);
|
||||
if(g_hEmaFastHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaFastHtf);
|
||||
if(g_hEmaSlowHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaSlowHtf);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
static datetime last_bar = 0;
|
||||
datetime t = iTime(_Symbol, InpTf, 0);
|
||||
if(t == last_bar)
|
||||
{
|
||||
// Still manage exits on tick if you use break-even / trailing — here bar-based only
|
||||
return;
|
||||
}
|
||||
last_bar = t;
|
||||
|
||||
const int need = 400;
|
||||
if(CopyBuffer(g_hEmaFast, 0, 0, need, g_emaFast) < need) return;
|
||||
if(CopyBuffer(g_hEmaSlow, 0, 0, need, g_emaSlow) < need) return;
|
||||
if(CopyBuffer(g_hRsi, 0, 0, need + InpStochLen + InpStochK + InpStochD + 5, g_rsi) < need) return;
|
||||
|
||||
if(!ComputeStochRsi(g_rsi, InpStochLen, InpStochK, InpStochD, g_stochK, g_stochD, need))
|
||||
return;
|
||||
|
||||
if(InpUseHtfFilter)
|
||||
{
|
||||
if(CopyBuffer(g_hEmaFastHtf, 0, 0, 3, g_emaFastHtf) < 3) return;
|
||||
if(CopyBuffer(g_hEmaSlowHtf, 0, 0, 3, g_emaSlowHtf) < 3) return;
|
||||
}
|
||||
|
||||
// bar 1 = last closed candle (tutorial: trade after confirmation candle closes)
|
||||
const int c = 1;
|
||||
const int p = 2;
|
||||
|
||||
if(PositionExistsForMagic())
|
||||
{
|
||||
ManageOpenPosition(c, p);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!PassesFlatEmaFilter(c))
|
||||
return;
|
||||
|
||||
// Long: EMA9 crosses EMA21 up at bar 1 close; Stoch RSI K,D leave oversold with bullish K/D cross
|
||||
const bool bull_cross = (g_emaFast[p] < g_emaSlow[p] && g_emaFast[c] > g_emaSlow[c]);
|
||||
const bool bear_cross = (g_emaFast[p] > g_emaSlow[p] && g_emaFast[c] < g_emaSlow[c]);
|
||||
|
||||
if(!bull_cross && !bear_cross)
|
||||
return;
|
||||
|
||||
if(InpUseHtfFilter)
|
||||
{
|
||||
if(bull_cross && !(g_emaFastHtf[c] > g_emaSlowHtf[c]))
|
||||
return;
|
||||
if(bear_cross && !(g_emaFastHtf[c] < g_emaSlowHtf[c]))
|
||||
return;
|
||||
}
|
||||
|
||||
if(!MinBarsSincePreviousCrossOk())
|
||||
return;
|
||||
|
||||
const bool stoch_long_ok =
|
||||
(g_stochK[p] < InpOsLevel && g_stochD[p] < InpOsLevel) &&
|
||||
(g_stochK[c] > g_stochD[c] && g_stochK[p] <= g_stochD[p]) &&
|
||||
(g_stochK[c] > InpOsLevel * 0.9); // "left" oversold — allow ~18 if OS=20
|
||||
|
||||
const bool stoch_short_ok =
|
||||
(g_stochK[p] > InpObLevel && g_stochD[p] > InpObLevel) &&
|
||||
(g_stochK[c] < g_stochD[c] && g_stochK[p] >= g_stochD[p]) &&
|
||||
(g_stochK[c] < InpObLevel * 1.05);
|
||||
|
||||
if(InpUseMidZoneFilter)
|
||||
{
|
||||
if(g_stochK[c] > 40.0 && g_stochK[c] < 60.0 && g_stochD[c] > 40.0 && g_stochD[c] < 60.0)
|
||||
return;
|
||||
}
|
||||
|
||||
if(bull_cross && stoch_long_ok)
|
||||
{
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
double low12 = MathMin(iLow(_Symbol, InpTf, c), iLow(_Symbol, InpTf, p));
|
||||
double sl = low12 - InpSlBufferPts * pt;
|
||||
sl = NormalizeDouble(sl, dg);
|
||||
if(sl >= ask - pt)
|
||||
sl = ask - 10 * pt;
|
||||
double risk = ask - sl;
|
||||
if(risk <= 0) return;
|
||||
double tp = ask + risk * InpTpRiskMultiple;
|
||||
tp = NormalizeDouble(tp, dg);
|
||||
g_trade.Buy(InpLots, _Symbol, ask, sl, tp, "EMA+StochRSI long");
|
||||
return;
|
||||
}
|
||||
|
||||
if(bear_cross && stoch_short_ok)
|
||||
{
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
double hi12 = MathMax(iHigh(_Symbol, InpTf, c), iHigh(_Symbol, InpTf, p));
|
||||
double sl = hi12 + InpSlBufferPts * pt;
|
||||
sl = NormalizeDouble(sl, dg);
|
||||
if(sl <= bid + pt)
|
||||
sl = bid + 10 * pt;
|
||||
double risk = sl - bid;
|
||||
if(risk <= 0) return;
|
||||
double tp = bid - risk * InpTpRiskMultiple;
|
||||
tp = NormalizeDouble(tp, dg);
|
||||
g_trade.Sell(InpLots, _Symbol, bid, sl, tp, "EMA+StochRSI short");
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool ComputeStochRsi(const double &rsi[], const int stoch_len, const int k_len, const int d_len,
|
||||
double &out_k[], double &out_d[], const int out_count)
|
||||
{
|
||||
int rsi_count = ArraySize(rsi);
|
||||
static double raw[];
|
||||
ArrayResize(raw, rsi_count);
|
||||
ArraySetAsSeries(raw, true);
|
||||
|
||||
for(int i = 0; i < rsi_count; i++)
|
||||
{
|
||||
if(i + stoch_len > rsi_count)
|
||||
{
|
||||
raw[i] = 50.0;
|
||||
continue;
|
||||
}
|
||||
double lo = rsi[i];
|
||||
double hi = rsi[i];
|
||||
for(int j = 0; j < stoch_len; j++)
|
||||
{
|
||||
double v = rsi[i + j];
|
||||
if(v < lo) lo = v;
|
||||
if(v > hi) hi = v;
|
||||
}
|
||||
if(hi == lo)
|
||||
raw[i] = 50.0;
|
||||
else
|
||||
raw[i] = (rsi[i] - lo) / (hi - lo) * 100.0;
|
||||
}
|
||||
|
||||
ArrayResize(out_k, out_count);
|
||||
ArrayResize(out_d, out_count);
|
||||
ArraySetAsSeries(out_k, true);
|
||||
ArraySetAsSeries(out_d, true);
|
||||
|
||||
static double k_unsm[];
|
||||
ArrayResize(k_unsm, rsi_count);
|
||||
ArraySetAsSeries(k_unsm, true);
|
||||
|
||||
for(int i = 0; i < rsi_count; i++)
|
||||
{
|
||||
if(i + k_len > rsi_count)
|
||||
{
|
||||
k_unsm[i] = raw[i];
|
||||
continue;
|
||||
}
|
||||
double s = 0.0;
|
||||
for(int j = 0; j < k_len; j++)
|
||||
s += raw[i + j];
|
||||
k_unsm[i] = s / (double)k_len;
|
||||
}
|
||||
|
||||
for(int i = 0; i < out_count; i++)
|
||||
{
|
||||
if(i + d_len > rsi_count)
|
||||
{
|
||||
out_k[i] = k_unsm[i];
|
||||
out_d[i] = k_unsm[i];
|
||||
continue;
|
||||
}
|
||||
double sk = 0.0;
|
||||
for(int j = 0; j < d_len; j++)
|
||||
sk += k_unsm[i + j];
|
||||
out_d[i] = sk / (double)d_len;
|
||||
out_k[i] = k_unsm[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool PassesFlatEmaFilter(const int c)
|
||||
{
|
||||
if(InpMinEmaSepPts <= 0.0)
|
||||
return true;
|
||||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
double sep = MathAbs(g_emaFast[c] - g_emaSlow[c]) / pt;
|
||||
return (sep >= InpMinEmaSepPts);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool MinBarsSincePreviousCrossOk()
|
||||
{
|
||||
if(InpMinBarsSinceCross <= 0)
|
||||
return true;
|
||||
// Cross under test completed on bar 1 (index c=1): between shift 2 and 1.
|
||||
// Earliest earlier cross: between i+1 and i for i >= 3.
|
||||
for(int i = 3; i < 300; i++)
|
||||
{
|
||||
const bool cu = (g_emaFast[i + 1] < g_emaSlow[i + 1] && g_emaFast[i] > g_emaSlow[i]);
|
||||
const bool cd = (g_emaFast[i + 1] > g_emaSlow[i + 1] && g_emaFast[i] < g_emaSlow[i]);
|
||||
if(cu || cd)
|
||||
return (i - 1 >= InpMinBarsSinceCross);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool PositionExistsForMagic()
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0) continue;
|
||||
if(!PositionSelectByTicket(ticket)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void SetTradeFillingBySymbol()
|
||||
{
|
||||
long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||||
if((mask & SYMBOL_FILLING_IOC) != 0)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
else if((mask & SYMBOL_FILLING_FOK) != 0)
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
else
|
||||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void ManageOpenPosition(const int c, const int p)
|
||||
{
|
||||
if(!PositionSelectBySymbolForMagic())
|
||||
return;
|
||||
ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
|
||||
long type = PositionGetInteger(POSITION_TYPE);
|
||||
double k1 = g_stochK[c];
|
||||
double d1 = g_stochD[c];
|
||||
|
||||
if(InpExitOnEma9Break)
|
||||
{
|
||||
double close1 = iClose(_Symbol, InpTf, c);
|
||||
if(type == POSITION_TYPE_BUY && close1 < g_emaFast[c])
|
||||
{
|
||||
g_trade.PositionClose(ticket);
|
||||
return;
|
||||
}
|
||||
if(type == POSITION_TYPE_SELL && close1 > g_emaFast[c])
|
||||
{
|
||||
g_trade.PositionClose(ticket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(InpExitOnStochZone)
|
||||
{
|
||||
if(type == POSITION_TYPE_BUY && k1 >= InpObLevel && d1 >= InpObLevel * 0.95)
|
||||
{
|
||||
g_trade.PositionClose(ticket);
|
||||
return;
|
||||
}
|
||||
if(type == POSITION_TYPE_SELL && k1 <= InpOsLevel && d1 <= InpOsLevel * 1.05)
|
||||
{
|
||||
g_trade.PositionClose(ticket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool PositionSelectBySymbolForMagic()
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0) continue;
|
||||
if(!PositionSelectByTicket(t)) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||||
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,25 @@
|
||||
; saved for genetic optimization — rsi-scalping (rsisauce) lab EA
|
||||
; copy to: ...\MQL5\Profiles\Tester\ then Load from Inputs tab
|
||||
; last field: Y = optimize, N = fixed
|
||||
;
|
||||
InpTf=1||0||0||49153||N
|
||||
InpEmaFast=9||5||1||15||Y
|
||||
InpEmaSlow=21||15||1||34||Y
|
||||
InpRsiLen=14||10||1||21||Y
|
||||
InpStochLen=14||8||1||24||Y
|
||||
InpStochK=4||3||1||8||Y
|
||||
InpStochD=7||3||1||12||Y
|
||||
InpObLevel=80.0||72.0||1.0||88.0||Y
|
||||
InpOsLevel=20.0||12.0||1.0||28.0||Y
|
||||
InpUseMidZoneFilter=true||false||0||true||N
|
||||
InpMinBarsSinceCross=10||4||1||18||Y
|
||||
InpUseHtfFilter=false||false||0||true||N
|
||||
InpHtf=5||0||0||49153||N
|
||||
InpMinEmaSepPts=0.0||0.0||2.0||40.0||Y
|
||||
InpLots=0.01||0.01||0.001000||0.100000||N
|
||||
InpSlBufferPts=20||5||2||60||Y
|
||||
InpTpRiskMultiple=1.75||1.25||0.05||2.50||Y
|
||||
InpExitOnEma9Break=true||false||0||true||N
|
||||
InpExitOnStochZone=true||false||0||true||N
|
||||
InpMagic=20260412||20260412||1||202604120||N
|
||||
InpSlippagePts=30||30||1||300||N
|
||||
Reference in New Issue
Block a user