Update
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user