Prepare source-only public release for develop.

Add cluster audit pipeline, united EA updates, brochure generators, and publication hygiene (gitignore, MT5 path desensitization, pre-upload scan). Remove tracked reports, models, and binary artifacts from the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhutoutoutousan
2026-07-02 15:03:43 +02:00
co-authored by Cursor
parent 3f75a08848
commit 605faf5310
1014 changed files with 83437 additions and 10413 deletions
@@ -1,26 +0,0 @@
; CandleChartPattern/main.mq5 — Strategy Tester → Inputs → Load
; Format: Name=Value||From||Step||To||Optimize(Y/N)
; Value = load default (aligned with EA + Desktop 123.set 2026.05.14). From/Step/To used when Y.
;
; === Market ===
InpSymbol=
InpLots=0.01||0.01||0.01||0.2||N
InpMagic=771001||771001||1||771001||N
InpSlippagePoints=30||30||1||300||N
InpMaxSpreadPoints=50||10||5||200||Y
; === Timeframes ===
; Enum timeframes: keep fixed during optimization (change manually if needed).
InpSignalTF=15||0||0||49153||N
InpConfirmTF=16385||0||0||49153||N
; === Patterns (signal TF, shift 1) ===
InpUseEngulfing=true||false||0||true||Y
InpUseHammerPin=true||false||0||true||Y
InpMinBodyPoints=5.0||2.0||0.5||25.0||Y
InpHammerWickRatio=2.0||1.2||0.1||4.0||Y
; === HTF confirmation ===
InpRequireHtfCandleDir=true||false||0||true||Y
InpRequireHtfPattern=false||false||0||true||Y
; === Behaviour ===
InpOnlyOnePosition=true||false||0||true||N
InpCloseOnReverseSignal=true||false||0||true||Y
InpCloseOnAdversePattern=true||false||0||true||Y
-332
View File
@@ -1,332 +0,0 @@
//+------------------------------------------------------------------+
//| CandleChartPattern.mq5 |
//| Lab EA: candle patterns on signal TF + HTF confirmation. |
//| No SL/TP. Exit on opposite signal or adverse pattern. |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property link ""
#property version "1.01"
#property strict
#include <Trade/Trade.mqh>
input group "=== Market ==="
input string InpSymbol = ""; // empty = chart symbol
input double InpLots = 0.01;
input int InpMagic = 771001;
input int InpSlippagePoints = 30;
input int InpMaxSpreadPoints = 50; // 0 = ignore
input group "=== Timeframes ==="
input ENUM_TIMEFRAMES InpSignalTF = PERIOD_M15; // patterns evaluated here (bar 1 = last closed)
input ENUM_TIMEFRAMES InpConfirmTF = PERIOD_H1; // must be >= InpSignalTF for stable bias (not enforced)
input group "=== Patterns (signal TF, shift 1) ==="
input bool InpUseEngulfing = true;
input bool InpUseHammerPin = true;
input double InpMinBodyPoints = 5.0; // min body size for engulfing (points)
input double InpHammerWickRatio = 2.0; // shadow >= ratio * body for hammer/pin
input group "=== HTF confirmation ==="
input bool InpRequireHtfCandleDir = true; // HTF last closed bar same direction as trade idea
input bool InpRequireHtfPattern = false; // if true, same pattern class must also print on HTF bar 1
input group "=== Behaviour ==="
input bool InpOnlyOnePosition = true;
input bool InpCloseOnReverseSignal = true; // close long if validated short setup appears (and vice versa)
input bool InpCloseOnAdversePattern = true; // close long on bearish engulf / bear pin on signal or HTF
CTrade g_trade;
string g_sym;
datetime g_lastSignalBarTime = 0;
ENUM_ORDER_TYPE_FILLING ResolveFilling(const string sym)
{
const long mask = SymbolInfoInteger(sym, SYMBOL_FILLING_MODE);
if((mask & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
return ORDER_FILLING_IOC;
if((mask & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
return ORDER_FILLING_FOK;
return ORDER_FILLING_RETURN;
}
bool SpreadOk(const string sym)
{
if(InpMaxSpreadPoints <= 0)
return true;
const double point = SymbolInfoDouble(sym, SYMBOL_POINT);
if(point <= 0.0)
return false;
const double spreadPts = (SymbolInfoDouble(sym, SYMBOL_ASK) - SymbolInfoDouble(sym, SYMBOL_BID)) / point;
return (spreadPts <= (double)InpMaxSpreadPoints);
}
bool IsNewSignalBar()
{
const datetime t = iTime(g_sym, InpSignalTF, 0);
if(t <= 0)
return false;
if(t == g_lastSignalBarTime)
return false;
g_lastSignalBarTime = t;
return true;
}
double BodyPoints(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
const double o = iOpen(s, tf, sh);
const double c = iClose(s, tf, sh);
const double point = SymbolInfoDouble(s, SYMBOL_POINT);
if(point <= 0.0)
return 0.0;
return MathAbs(c - o) / point;
}
bool BullishEngulfing(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
if(!InpUseEngulfing)
return false;
const double o1 = iOpen(s, tf, sh);
const double c1 = iClose(s, tf, sh);
const double o2 = iOpen(s, tf, sh + 1);
const double c2 = iClose(s, tf, sh + 1);
if(c2 >= o2)
return false;
if(c1 <= o1)
return false;
if(BodyPoints(s, tf, sh) < InpMinBodyPoints || BodyPoints(s, tf, sh + 1) < InpMinBodyPoints)
return false;
return (o1 <= c2 && c1 >= o2);
}
bool BearishEngulfing(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
if(!InpUseEngulfing)
return false;
const double o1 = iOpen(s, tf, sh);
const double c1 = iClose(s, tf, sh);
const double o2 = iOpen(s, tf, sh + 1);
const double c2 = iClose(s, tf, sh + 1);
if(c2 <= o2)
return false;
if(c1 >= o1)
return false;
if(BodyPoints(s, tf, sh) < InpMinBodyPoints || BodyPoints(s, tf, sh + 1) < InpMinBodyPoints)
return false;
return (o1 >= c2 && c1 <= o2);
}
bool BullishHammer(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
if(!InpUseHammerPin)
return false;
const double o = iOpen(s, tf, sh);
const double c = iClose(s, tf, sh);
const double h = iHigh(s, tf, sh);
const double l = iLow(s, tf, sh);
const double body = MathAbs(c - o);
const double lower = MathMin(o, c) - l;
const double upper = h - MathMax(o, c);
const double point = SymbolInfoDouble(s, SYMBOL_POINT);
if(point <= 0.0 || body < point * 0.1)
return false;
return (lower >= InpHammerWickRatio * body && upper <= body);
}
bool BearishPinBar(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
if(!InpUseHammerPin)
return false;
const double o = iOpen(s, tf, sh);
const double c = iClose(s, tf, sh);
const double h = iHigh(s, tf, sh);
const double l = iLow(s, tf, sh);
const double body = MathAbs(c - o);
const double lower = MathMin(o, c) - l;
const double upper = h - MathMax(o, c);
const double point = SymbolInfoDouble(s, SYMBOL_POINT);
if(point <= 0.0 || body < point * 0.1)
return false;
return (upper >= InpHammerWickRatio * body && lower <= body);
}
bool BullishPatternBar(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
return BullishEngulfing(s, tf, sh) || BullishHammer(s, tf, sh);
}
bool BearishPatternBar(const string s, const ENUM_TIMEFRAMES tf, const int sh)
{
return BearishEngulfing(s, tf, sh) || BearishPinBar(s, tf, sh);
}
bool HtfBullishClosedBar(const string s, const ENUM_TIMEFRAMES htf)
{
return (iClose(s, htf, 1) > iOpen(s, htf, 1));
}
bool HtfBearishClosedBar(const string s, const ENUM_TIMEFRAMES htf)
{
return (iClose(s, htf, 1) < iOpen(s, htf, 1));
}
bool ConfirmLong(const string s)
{
if(!InpRequireHtfCandleDir && !InpRequireHtfPattern)
return true;
if(InpRequireHtfCandleDir && !HtfBullishClosedBar(s, InpConfirmTF))
return false;
if(InpRequireHtfPattern && !BullishPatternBar(s, InpConfirmTF, 1))
return false;
return true;
}
bool ConfirmShort(const string s)
{
if(!InpRequireHtfCandleDir && !InpRequireHtfPattern)
return true;
if(InpRequireHtfCandleDir && !HtfBearishClosedBar(s, InpConfirmTF))
return false;
if(InpRequireHtfPattern && !BearishPatternBar(s, InpConfirmTF, 1))
return false;
return true;
}
bool ValidatedLongSetup(const string s)
{
if(!BullishPatternBar(s, InpSignalTF, 1))
return false;
return ConfirmLong(s);
}
bool ValidatedShortSetup(const string s)
{
if(!BearishPatternBar(s, InpSignalTF, 1))
return false;
return ConfirmShort(s);
}
bool HasOurPosition(const string s, const int magic, int &dir)
{
dir = -1;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != s)
continue;
if((int)PositionGetInteger(POSITION_MAGIC) != magic)
continue;
const long typ = PositionGetInteger(POSITION_TYPE);
dir = (typ == POSITION_TYPE_BUY) ? 0 : 1;
return true;
}
return false;
}
bool CloseOurPositions(const string s, const int magic)
{
bool ok = true;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != s)
continue;
if((int)PositionGetInteger(POSITION_MAGIC) != magic)
continue;
if(!g_trade.PositionClose(ticket))
ok = false;
}
return ok;
}
int OnInit()
{
g_sym = (StringLen(InpSymbol) == 0) ? _Symbol : InpSymbol;
if(!SymbolSelect(g_sym, true))
{
Print("SymbolSelect failed: ", g_sym);
return INIT_FAILED;
}
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
g_trade.SetTypeFilling(ResolveFilling(g_sym));
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
}
void OnTick()
{
if(!IsNewSignalBar())
return;
if(Bars(g_sym, InpSignalTF) < 5 || Bars(g_sym, InpConfirmTF) < 5)
return;
if(!SpreadOk(g_sym))
return;
const bool longSetup = ValidatedLongSetup(g_sym);
const bool shortSetup = ValidatedShortSetup(g_sym);
int dir = -1;
bool has = HasOurPosition(g_sym, InpMagic, dir);
if(has)
{
if(dir == 0)
{
bool adverse = false;
if(InpCloseOnAdversePattern)
{
if(BearishPatternBar(g_sym, InpSignalTF, 1) || BearishPatternBar(g_sym, InpConfirmTF, 1))
adverse = true;
}
const bool reverse = (InpCloseOnReverseSignal && shortSetup);
if(adverse || reverse)
CloseOurPositions(g_sym, InpMagic);
}
else if(dir == 1)
{
bool adverse = false;
if(InpCloseOnAdversePattern)
{
if(BullishPatternBar(g_sym, InpSignalTF, 1) || BullishPatternBar(g_sym, InpConfirmTF, 1))
adverse = true;
}
const bool reverse = (InpCloseOnReverseSignal && longSetup);
if(adverse || reverse)
CloseOurPositions(g_sym, InpMagic);
}
}
has = HasOurPosition(g_sym, InpMagic, dir);
if(InpOnlyOnePosition && has)
return;
if(longSetup && !shortSetup)
{
const double ask = SymbolInfoDouble(g_sym, SYMBOL_ASK);
g_trade.Buy(InpLots, g_sym, ask, 0.0, 0.0, "CandlePattern long");
}
else if(shortSetup && !longSetup)
{
const double bid = SymbolInfoDouble(g_sym, SYMBOL_BID);
g_trade.Sell(InpLots, g_sym, bid, 0.0, 0.0, "CandlePattern short");
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-425
View File
@@ -1,425 +0,0 @@
//+------------------------------------------------------------------+
//| DerivativePlots.mq5 |
//| Subwindow line plots for d1 / d2 / d3 — use with Derivative EA |
//| Compile into MQL5\\Indicators\\ (same name). EA can ChartIndicatorAdd.|
//+------------------------------------------------------------------+
#property copyright "Lab"
#property link ""
#property version "1.10"
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots 3
#property description "Plots d1 d2 d3 below chart. Match inputs to Derivative EA."
#property indicator_label1 "d1 velocity"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
#property indicator_width1 1
#property indicator_label2 "d2 acceleration"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrOrange
#property indicator_width2 1
#property indicator_label3 "d3 jerk"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrMagenta
#property indicator_width3 1
enum ENUM_DERIVATIVE_VIEW
{
DERIVATIVE_ALL = 0,
DERIVATIVE_LEVEL_1 = 1,
DERIVATIVE_LEVEL_2 = 2,
DERIVATIVE_LEVEL_3 = 3
};
input group "=== Source ==="
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;
input group "=== Layout ==="
input ENUM_DERIVATIVE_VIEW InpWhichDerivative = DERIVATIVE_ALL; // Single-line modes clear other buffers to EMPTY_VALUE so Y-scale matches the visible line
input bool InpUnifyPlotYScale = true; // Scale d2,d3 for comparable magnitude when normalized (shared subwindow)
input group "=== Calculus ==="
input int InpDiffStep = 1;
input bool InpNormalizePoints = true;
input group "=== Smoothing ==="
input int InpSmoothPeriod = 0;
input group "=== Status ==="
input bool InpShowValueBanner = true; // Text label; short name is DERIV_ALL / DERIV_d1 / DERIV_d2 / DERIV_d3 for ChartWindowFind
input group "=== Debug (Experts / Journal) ==="
input bool InpDebugTrace = false; // Print diagnostics to Experts tab
input bool InpDebugLogEveryCalculate = false; // Log every OnCalculate (very verbose)
double ExtD1[];
double ExtD2[];
double ExtD3[];
string g_deriv_chart_title = "DERIV_ALL";
string g_deriv_stat_obj = "DerivPV_ALL";
void SetupDerivIdentity()
{
switch(InpWhichDerivative)
{
case DERIVATIVE_ALL:
g_deriv_chart_title = "DERIV_ALL";
g_deriv_stat_obj = "DerivPV_ALL";
break;
case DERIVATIVE_LEVEL_1:
g_deriv_chart_title = "DERIV_d1";
g_deriv_stat_obj = "DerivPV_d1";
break;
case DERIVATIVE_LEVEL_2:
g_deriv_chart_title = "DERIV_d2";
g_deriv_stat_obj = "DerivPV_d2";
break;
default:
g_deriv_chart_title = "DERIV_d3";
g_deriv_stat_obj = "DerivPV_d3";
break;
}
}
// OnCalculate passes OHLC with index 0 = oldest bar (non-series). Do not ArraySetAsSeries() those arrays.
double AppliedPriceRowNs(const int pos, const double &open[], const double &high[],
const double &low[], const double &close[])
{
switch(InpAppliedPrice)
{
case PRICE_OPEN: return open[pos];
case PRICE_HIGH: return high[pos];
case PRICE_LOW: return low[pos];
case PRICE_CLOSE: return close[pos];
case PRICE_MEDIAN: return (high[pos] + low[pos]) * 0.5;
case PRICE_TYPICAL: return (high[pos] + low[pos] + close[pos]) / 3.0;
case PRICE_WEIGHTED: return (high[pos] + low[pos] + close[pos] + close[pos]) / 4.0;
default: return close[pos];
}
}
void SmoothPriceArrayNs(const int total, const double &src[], double &dst[])
{
ArrayResize(dst, total);
const int p = InpSmoothPeriod;
if(p <= 1)
{
ArrayCopy(dst, src);
return;
}
const double alpha = 2.0 / (p + 1.0);
dst[0] = src[0];
for(int pos = 1; pos < total; pos++)
dst[pos] = alpha * src[pos] + (1.0 - alpha) * dst[pos - 1];
}
double SrcNs(const int pos, const bool useSmooth, const double &smooth[], const double &raw[])
{
return useSmooth ? smooth[pos] : raw[pos];
}
double DerivativeScalePts()
{
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
if(pt <= 0.0 || !MathIsValidNumber(pt))
pt = _Point;
if(!InpNormalizePoints)
return 1.0;
if(pt <= 0.0)
return 1.0;
return pt;
}
void DerivPlotsTrace(const int rates_total, const int prev_calculated,
const int h, const int min_bars, const double scale, const bool useSmooth,
const double &close[], const double &WorkNs[], const datetime &time[])
{
if(!InpDebugTrace)
return;
static int s_call = 0;
s_call++;
const int newest = rates_total - 1;
const datetime barOpen = time[newest];
static datetime s_prevBarOpen = 0;
const bool isNewBarTime = (barOpen != s_prevBarOpen);
if(isNewBarTime)
s_prevBarOpen = barOpen;
const bool fullRecalc = (prev_calculated == 0);
if(InpDebugLogEveryCalculate)
{
PrintFormat("DERIV_PLOTS #%d prev_calc=%d rates=%d bar=%s | d1[0]=%.8g d2[0]=%.8g d3[0]=%.8g",
s_call, prev_calculated, rates_total, TimeToString(barOpen, TIME_DATE | TIME_MINUTES),
ExtD1[0], ExtD2[0], ExtD3[0]);
return;
}
if(fullRecalc)
{
const double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
const double rawStep = (newest >= h) ? (WorkNs[newest] - WorkNs[newest - h]) : 0.0;
PrintFormat("DERIV_PLOTS FULL_CALC #%d sym=%s rates=%d prev_calc=%d h=%d min_need=%d smooth=%s which=%d",
s_call, _Symbol, rates_total, prev_calculated, h, min_bars,
useSmooth ? "on" : "off", (int)InpWhichDerivative);
PrintFormat(" scale=%.12g normalize=%s SYPOINT=%.12g _Point=%.12g SYM_DIGITS=%d",
scale, InpNormalizePoints ? "on" : "off", pt, _Point,
(int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
PrintFormat(" close[oldest]=%.8f close[newest]=%.8f rawStep(newest..newest-h)=%.8f",
close[0], close[newest], rawStep);
PrintFormat(" series buf [0]=current bar: d1=%.8g d2=%.8g d3=%.8g (EMPTY_VALUE=%.8g)",
ExtD1[0], ExtD2[0], ExtD3[0], EMPTY_VALUE);
}
else if(isNewBarTime)
{
PrintFormat("DERIV_PLOTS BAR %s rates=%d prev_calc=%d | d1[0]=%.8g d2[0]=%.8g d3[0]=%.8g",
TimeToString(barOpen, TIME_DATE | TIME_MINUTES), rates_total, prev_calculated,
ExtD1[0], ExtD2[0], ExtD3[0]);
}
}
string FormatPlotVal(const double v)
{
if(v == EMPTY_VALUE || !MathIsValidNumber(v))
return "—";
return DoubleToString(v, 4);
}
void UpdateValueBanner(const int rates_total)
{
if(!InpShowValueBanner || rates_total < 1)
return;
string txt = "";
switch(InpWhichDerivative)
{
case DERIVATIVE_ALL:
txt = StringFormat("d1=%s d2=%s d3=%s (h=%d sm=%d%s)",
FormatPlotVal(ExtD1[0]), FormatPlotVal(ExtD2[0]), FormatPlotVal(ExtD3[0]),
InpDiffStep, InpSmoothPeriod, InpUnifyPlotYScale ? " unifyY" : "");
break;
case DERIVATIVE_LEVEL_1:
txt = StringFormat("d1=%s", FormatPlotVal(ExtD1[0]));
break;
case DERIVATIVE_LEVEL_2:
txt = StringFormat("d2=%s", FormatPlotVal(ExtD2[0]));
break;
default:
txt = StringFormat("d3=%s", FormatPlotVal(ExtD3[0]));
break;
}
IndicatorSetString(INDICATOR_SHORTNAME, g_deriv_chart_title);
const int sub = ChartWindowFind(0, g_deriv_chart_title);
if(sub < 0)
return;
if(ObjectFind(0, g_deriv_stat_obj) < 0)
{
if(!ObjectCreate(0, g_deriv_stat_obj, OBJ_LABEL, sub, 0, 0))
return;
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_XDISTANCE, 6);
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_YDISTANCE, 16);
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_COLOR, clrSilver);
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_FONTSIZE, 9);
ObjectSetString(0, g_deriv_stat_obj, OBJPROP_FONT, "Consolas");
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, g_deriv_stat_obj, OBJPROP_HIDDEN, true);
}
ObjectSetString(0, g_deriv_stat_obj, OBJPROP_TEXT, txt);
}
// Hide unused buffers from autoscale: DRAW_NONE plots can still skew separate-window limits if buffers hold numbers.
void MaskBuffersForDerivativeView()
{
switch(InpWhichDerivative)
{
case DERIVATIVE_ALL:
break;
case DERIVATIVE_LEVEL_1:
ArrayInitialize(ExtD2, EMPTY_VALUE);
ArrayInitialize(ExtD3, EMPTY_VALUE);
break;
case DERIVATIVE_LEVEL_2:
ArrayInitialize(ExtD1, EMPTY_VALUE);
ArrayInitialize(ExtD3, EMPTY_VALUE);
break;
default:
ArrayInitialize(ExtD1, EMPTY_VALUE);
ArrayInitialize(ExtD2, EMPTY_VALUE);
break;
}
}
void ApplyDerivativeViewMode()
{
switch(InpWhichDerivative)
{
case DERIVATIVE_ALL:
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrDodgerBlue);
PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrOrange);
PlotIndexSetInteger(2, PLOT_LINE_COLOR, clrMagenta);
PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);
PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 3);
PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 3);
PlotIndexSetInteger(0, PLOT_LINE_STYLE, STYLE_SOLID);
PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_SOLID);
PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_SOLID);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
break;
case DERIVATIVE_LEVEL_1:
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_NONE);
PlotIndexSetInteger(0, PLOT_LINE_COLOR, clrDodgerBlue);
PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
break;
case DERIVATIVE_LEVEL_2:
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_NONE);
PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrOrange);
PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 3);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
break;
default:
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_NONE);
PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_NONE);
PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(2, PLOT_LINE_COLOR, clrMagenta);
PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 3);
PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
break;
}
}
int OnInit()
{
SetIndexBuffer(0, ExtD1, INDICATOR_DATA);
SetIndexBuffer(1, ExtD2, INDICATOR_DATA);
SetIndexBuffer(2, ExtD3, INDICATOR_DATA);
SetupDerivIdentity();
ApplyDerivativeViewMode();
IndicatorSetString(INDICATOR_SHORTNAME, g_deriv_chart_title);
const int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
IndicatorSetInteger(INDICATOR_DIGITS, MathMax(6, dig));
if(InpDebugTrace)
PrintFormat("DERIV_PLOTS INIT sym=%s applied=%s h=%d sm=%d norm=%s dbg_every_calc=%s",
_Symbol, EnumToString(InpAppliedPrice), InpDiffStep, InpSmoothPeriod,
InpNormalizePoints ? "on" : "off", InpDebugLogEveryCalculate ? "on" : "off");
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
ObjectDelete(0, g_deriv_stat_obj);
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
const int h = MathMax(InpDiffStep, 1);
const int min_bars = 3 * h + 2;
ApplyDerivativeViewMode();
ArrayResize(ExtD1, rates_total);
ArrayResize(ExtD2, rates_total);
ArrayResize(ExtD3, rates_total);
ArraySetAsSeries(ExtD1, true);
ArraySetAsSeries(ExtD2, true);
ArraySetAsSeries(ExtD3, true);
ArrayInitialize(ExtD1, EMPTY_VALUE);
ArrayInitialize(ExtD2, EMPTY_VALUE);
ArrayInitialize(ExtD3, EMPTY_VALUE);
if(rates_total < min_bars)
{
if(InpDebugTrace)
PrintFormat("DERIV_PLOTS SHORT_HISTORY sym=%s rates=%d need=%d (3*h+2, h=%d) — buffers left EMPTY",
_Symbol, rates_total, min_bars, h);
return rates_total;
}
double WorkNs[];
ArrayResize(WorkNs, rates_total);
for(int pos = 0; pos < rates_total; pos++)
WorkNs[pos] = AppliedPriceRowNs(pos, open, high, low, close);
static double SmoothNs[];
SmoothPriceArrayNs(rates_total, WorkNs, SmoothNs);
const bool useSmooth = (InpSmoothPeriod > 1);
const double scale = DerivativeScalePts();
// Bar index pos: 0 = oldest, rates_total-1 = newest. Map to series buffer si = rates_total - 1 - pos (0 = current bar).
const double hs = (double)h * scale;
const bool unify = InpUnifyPlotYScale;
for(int pos = h; pos < rates_total; pos++)
{
const double d1 = (SrcNs(pos, useSmooth, SmoothNs, WorkNs) - SrcNs(pos - h, useSmooth, SmoothNs, WorkNs)) / ((double)h * scale);
const int si = rates_total - 1 - pos;
ExtD1[si] = d1;
}
for(int pos = 2 * h; pos < rates_total; pos++)
{
const double d1_pos = (SrcNs(pos, useSmooth, SmoothNs, WorkNs) - SrcNs(pos - h, useSmooth, SmoothNs, WorkNs)) / ((double)h * scale);
const double d1_pm = (SrcNs(pos - h, useSmooth, SmoothNs, WorkNs) - SrcNs(pos - 2 * h, useSmooth, SmoothNs, WorkNs)) / ((double)h * scale);
double d2 = (d1_pos - d1_pm) / ((double)h * scale);
if(unify)
d2 *= hs;
const int si = rates_total - 1 - pos;
ExtD2[si] = d2;
}
for(int pos = 3 * h; pos < rates_total; pos++)
{
const double d1_pos = (SrcNs(pos, useSmooth, SmoothNs, WorkNs) - SrcNs(pos - h, useSmooth, SmoothNs, WorkNs)) / ((double)h * scale);
const double d1_pm = (SrcNs(pos - h, useSmooth, SmoothNs, WorkNs) - SrcNs(pos - 2 * h, useSmooth, SmoothNs, WorkNs)) / ((double)h * scale);
const double d1_pm2 = (SrcNs(pos - 2 * h, useSmooth, SmoothNs, WorkNs) - SrcNs(pos - 3 * h, useSmooth, SmoothNs, WorkNs)) / ((double)h * scale);
const double d2_pos = (d1_pos - d1_pm) / ((double)h * scale);
const double d2_pm = (d1_pm - d1_pm2) / ((double)h * scale);
double d3 = (d2_pos - d2_pm) / ((double)h * scale);
if(unify)
d3 *= hs * hs;
const int si = rates_total - 1 - pos;
ExtD3[si] = d3;
}
MaskBuffersForDerivativeView();
DerivPlotsTrace(rates_total, prev_calculated, h, min_bars, scale, useSmooth, close, WorkNs, time);
UpdateValueBanner(rates_total);
return rates_total;
}
//+------------------------------------------------------------------+
-559
View File
@@ -1,559 +0,0 @@
//+------------------------------------------------------------------+
//| EMAPriceSlope.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#property description "Expert Advisor using EMA Slope for intelligent trend trading"
#property description "Trades based on EMA momentum, slope strength, and price confirmation"
#include <Trade\Trade.mqh>
//--- Input parameters
input group "Timeframe Settings"
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15; // Trading Timeframe
input group "EMA Settings"
input int InpEMAPeriod = 20; // EMA Period
input int InpSlopeBars = 3; // Slope Calculation Bars (lookback for slope)
input group "Slope Trading Logic"
input double InpMinSlopeStrength = 0.0001; // Minimum Slope Strength (0.01% per bar)
input bool InpUseSlopeAcceleration = true; // Require slope acceleration (increasing momentum)
input double InpMinAcceleration = 0.00005; // Minimum Acceleration Threshold
input bool InpUsePriceConfirmation = true; // Require price above/below EMA for confirmation
input double InpPriceDistanceMultiplier = 0.5; // Price distance from EMA (ATR multiplier)
input group "Entry Filters"
input bool InpUseVolatilityFilter = true; // Use ATR volatility filter
input double InpMinATR = 0.0002; // Minimum ATR for trading (filter low volatility)
input double InpMaxATR = 0.01; // Maximum ATR for trading (filter high volatility)
input bool InpUseRSIFilter = false; // Use RSI filter
input int InpRSIPeriod = 14; // RSI Period
input double InpRSIOverbought = 70; // RSI Overbought (avoid longs)
input double InpRSIOversold = 30; // RSI Oversold (avoid shorts)
input group "Trading Hours (Server Time)"
input int InpStartHour = 8; // Trading Start Hour (0-23)
input int InpEndHour = 18; // Trading End Hour (0-23)
input bool InpUseTimeFilter = true; // Use Trading Hours Filter
input group "Risk Management"
input double InpLotSize = 0.01; // Lot Size
input int InpStopLoss = 50; // Stop Loss (pips) - 0 = no SL
input int InpTakeProfit = 100; // Take Profit (pips) - 0 = no TP
input bool InpUseTrailingStop = true; // Use Trailing Stop
input int InpTrailingStop = 30; // Trailing Stop (pips)
input int InpTrailingStep = 5; // Trailing Step (pips)
input int InpMagicNumber = 890123; // Magic Number
input int InpSlippage = 3; // Slippage (points)
input group "Exit Strategy"
input bool InpUseSlopeReversalExit = true; // Exit on slope reversal
input double InpSlopeReversalThreshold = -0.00005; // Slope reversal threshold (negative slope for long exit)
input bool InpUseEMAExit = false; // Exit when price crosses EMA
input group "Loss Minimization"
input bool InpUseMaxDailyLoss = true; // Use Max Daily Loss
input double InpMaxDailyLoss = 50.0; // Max Daily Loss (USD)
//--- Global variables
CTrade trade;
int ema_handle;
int atr_handle;
int rsi_handle;
datetime last_bar_time = 0;
double daily_profit = 0.0;
datetime last_daily_reset = 0;
double last_profit = 0.0;
double last_slope = 0.0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Set trade parameters
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetDeviationInPoints(InpSlippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
// Create indicators
ema_handle = iMA(_Symbol, InpTimeframe, InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(InpUseVolatilityFilter)
{
atr_handle = iATR(_Symbol, InpTimeframe, 14);
if(atr_handle == INVALID_HANDLE)
{
Print("ERROR: Failed to create ATR indicator");
return(INIT_FAILED);
}
}
if(InpUseRSIFilter)
{
rsi_handle = iRSI(_Symbol, InpTimeframe, InpRSIPeriod, PRICE_CLOSE);
if(rsi_handle == INVALID_HANDLE)
{
Print("ERROR: Failed to create RSI indicator");
return(INIT_FAILED);
}
}
if(ema_handle == INVALID_HANDLE)
{
Print("ERROR: Failed to create EMA indicator");
return(INIT_FAILED);
}
// Initialize daily tracking
last_daily_reset = TimeCurrent();
daily_profit = 0.0;
Print("EMAPriceSlope EA initialized for ", _Symbol);
Print("Timeframe: ", EnumToString(InpTimeframe));
Print("EMA Period: ", InpEMAPeriod, " Slope Bars: ", InpSlopeBars);
Print("Min Slope Strength: ", InpMinSlopeStrength);
Print("Trading Hours: ", InpStartHour, ":00 - ", InpEndHour, ":00");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Release indicators
if(ema_handle != INVALID_HANDLE)
IndicatorRelease(ema_handle);
if(atr_handle != INVALID_HANDLE)
IndicatorRelease(atr_handle);
if(rsi_handle != INVALID_HANDLE)
IndicatorRelease(rsi_handle);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Check if new bar on the specified timeframe
datetime current_bar_time = iTime(_Symbol, InpTimeframe, 0);
if(current_bar_time == last_bar_time)
{
// Still same bar - only manage existing positions
ManagePosition();
return;
}
last_bar_time = current_bar_time;
// Reset daily profit at midnight
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
MqlDateTime last_dt;
TimeToStruct(last_daily_reset, last_dt);
bool is_new_day = (dt.day != last_dt.day || dt.month != last_dt.month || dt.year != last_dt.year);
if(is_new_day)
{
daily_profit = 0.0;
last_daily_reset = TimeCurrent();
Print("Daily reset: New trading day started. Daily profit reset to 0.");
}
// Check daily loss limit
if(InpUseMaxDailyLoss && daily_profit <= -InpMaxDailyLoss)
{
Print("Daily loss limit reached: ", daily_profit, " USD. Trading stopped for today.");
return;
}
// Check trading hours
if(InpUseTimeFilter && !IsWithinTradingHours())
{
return; // Outside trading hours
}
// Get EMA values for slope calculation
double ema[];
ArraySetAsSeries(ema, true);
// Need enough bars for slope calculation
int bars_needed = InpSlopeBars + 5;
if(CopyBuffer(ema_handle, 0, 0, bars_needed, ema) < bars_needed)
{
Print("ERROR: Failed to copy EMA buffer");
return;
}
// Calculate EMA slope (rate of change)
double current_ema = ema[0];
double previous_ema = ema[InpSlopeBars];
double slope = (current_ema - previous_ema) / previous_ema; // Percentage change
// Calculate slope acceleration (change in slope)
double previous_slope = last_slope;
double acceleration = 0.0;
if(previous_slope != 0.0)
{
acceleration = slope - previous_slope;
}
last_slope = slope;
// Get current price
double current_price = iClose(_Symbol, InpTimeframe, 0);
double price_distance_from_ema = MathAbs(current_price - current_ema) / current_ema;
// Get ATR for volatility filter
double atr_value = 0.0;
if(InpUseVolatilityFilter)
{
double atr_array[];
ArraySetAsSeries(atr_array, true);
if(CopyBuffer(atr_handle, 0, 0, 1, atr_array) > 0)
{
atr_value = atr_array[0];
}
}
// Get RSI for filter
double rsi_value = 50.0;
if(InpUseRSIFilter)
{
double rsi_array[];
ArraySetAsSeries(rsi_array, true);
if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_array) > 0)
{
rsi_value = rsi_array[0];
}
}
// Check existing position
if(PositionSelect(_Symbol))
{
ManagePosition();
// Check exit conditions
long position_type = PositionGetInteger(POSITION_TYPE);
// Exit on slope reversal
if(InpUseSlopeReversalExit)
{
if(position_type == POSITION_TYPE_BUY && slope < InpSlopeReversalThreshold)
{
// Long position: exit on negative slope reversal
if(trade.PositionClose(_Symbol))
{
Print("Position closed: Slope reversal (slope=", slope, ")");
}
return;
}
else if(position_type == POSITION_TYPE_SELL && slope > -InpSlopeReversalThreshold)
{
// Short position: exit on positive slope reversal
if(trade.PositionClose(_Symbol))
{
Print("Position closed: Slope reversal (slope=", slope, ")");
}
return;
}
}
// Exit when price crosses EMA (if enabled)
if(InpUseEMAExit)
{
double prev_price = iClose(_Symbol, InpTimeframe, 1);
if(position_type == POSITION_TYPE_BUY && current_price < current_ema && prev_price >= ema[1])
{
if(trade.PositionClose(_Symbol))
{
Print("Position closed: Price crossed below EMA");
}
return;
}
else if(position_type == POSITION_TYPE_SELL && current_price > current_ema && prev_price <= ema[1])
{
if(trade.PositionClose(_Symbol))
{
Print("Position closed: Price crossed above EMA");
}
return;
}
}
}
else
{
// No position - check for entry signals
// Volatility filter
if(InpUseVolatilityFilter && atr_value > 0)
{
if(atr_value < InpMinATR || atr_value > InpMaxATR)
{
return; // Volatility out of range
}
}
// RSI filter
if(InpUseRSIFilter)
{
if(rsi_value > InpRSIOverbought || rsi_value < InpRSIOversold)
{
return; // RSI in extreme zone
}
}
// BUY Signal: Positive slope with strength
bool buy_signal = false;
if(slope > InpMinSlopeStrength)
{
// Check acceleration (if enabled)
if(InpUseSlopeAcceleration)
{
if(acceleration > InpMinAcceleration)
{
buy_signal = true;
}
}
else
{
buy_signal = true;
}
// Price confirmation (if enabled)
if(buy_signal && InpUsePriceConfirmation)
{
double min_distance = atr_value * InpPriceDistanceMultiplier / current_price;
if(price_distance_from_ema < min_distance || current_price < current_ema)
{
buy_signal = false; // Price too close to EMA or below EMA
}
}
// RSI filter for buy
if(buy_signal && InpUseRSIFilter && rsi_value > InpRSIOverbought)
{
buy_signal = false;
}
}
// SELL Signal: Negative slope with strength
bool sell_signal = false;
if(slope < -InpMinSlopeStrength)
{
// Check acceleration (if enabled)
if(InpUseSlopeAcceleration)
{
if(acceleration < -InpMinAcceleration)
{
sell_signal = true;
}
}
else
{
sell_signal = true;
}
// Price confirmation (if enabled)
if(sell_signal && InpUsePriceConfirmation)
{
double min_distance = atr_value * InpPriceDistanceMultiplier / current_price;
if(price_distance_from_ema < min_distance || current_price > current_ema)
{
sell_signal = false; // Price too close to EMA or above EMA
}
}
// RSI filter for sell
if(sell_signal && InpUseRSIFilter && rsi_value < InpRSIOversold)
{
sell_signal = false;
}
}
// Execute trades
if(buy_signal)
{
Print("BUY Signal: Slope=", slope, " Acceleration=", acceleration, " Price=", current_price);
OpenBuyPosition();
}
else if(sell_signal)
{
Print("SELL Signal: Slope=", slope, " Acceleration=", acceleration, " Price=", current_price);
OpenSellPosition();
}
}
}
//+------------------------------------------------------------------+
//| Check if current time is within trading hours |
//+------------------------------------------------------------------+
bool IsWithinTradingHours()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int current_hour = dt.hour;
// Handle case where end hour is before start hour (overnight)
if(InpEndHour < InpStartHour)
{
return (current_hour >= InpStartHour || current_hour < InpEndHour);
}
else
{
return (current_hour >= InpStartHour && current_hour < InpEndHour);
}
}
//+------------------------------------------------------------------+
//| Open buy position |
//+------------------------------------------------------------------+
void OpenBuyPosition()
{
double price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = 0.0;
double tp = 0.0;
if(InpStopLoss > 0)
{
sl = price - InpStopLoss * _Point * 10;
}
if(InpTakeProfit > 0)
{
tp = price + InpTakeProfit * _Point * 10;
}
// Validate stops
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double min_stop = stop_level * point;
if(sl > 0 && (price - sl) < min_stop)
sl = price - min_stop;
if(tp > 0 && (tp - price) < min_stop)
tp = price + min_stop;
if(trade.Buy(InpLotSize, _Symbol, price, sl, tp, "EMA Slope Buy"))
{
Print("Buy order opened at ", price, " SL: ", sl, " TP: ", tp);
}
else
{
Print("Failed to open buy order: ", trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
//| Open sell position |
//+------------------------------------------------------------------+
void OpenSellPosition()
{
double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = 0.0;
double tp = 0.0;
if(InpStopLoss > 0)
{
sl = price + InpStopLoss * _Point * 10;
}
if(InpTakeProfit > 0)
{
tp = price - InpTakeProfit * _Point * 10;
}
// Validate stops
int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
double min_stop = stop_level * point;
if(sl > 0 && (sl - price) < min_stop)
sl = price + min_stop;
if(tp > 0 && (price - tp) < min_stop)
tp = price - min_stop;
if(trade.Sell(InpLotSize, _Symbol, price, sl, tp, "EMA Slope Sell"))
{
Print("Sell order opened at ", price, " SL: ", sl, " TP: ", tp);
}
else
{
Print("Failed to open sell order: ", trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
//| Manage existing position |
//+------------------------------------------------------------------+
void ManagePosition()
{
if(!PositionSelect(_Symbol))
return;
// Update daily profit
double current_profit = PositionGetDouble(POSITION_PROFIT);
if(current_profit != last_profit)
{
daily_profit += (current_profit - last_profit);
last_profit = current_profit;
}
// Apply trailing stop
if(InpUseTrailingStop && InpTrailingStop > 0)
{
ApplyTrailingStop();
}
}
//+------------------------------------------------------------------+
//| Apply trailing stop |
//+------------------------------------------------------------------+
void ApplyTrailingStop()
{
if(!PositionSelect(_Symbol))
return;
double position_sl = PositionGetDouble(POSITION_SL);
double position_tp = PositionGetDouble(POSITION_TP);
long position_type = PositionGetInteger(POSITION_TYPE);
double current_price = (position_type == POSITION_TYPE_BUY) ?
SymbolInfoDouble(_Symbol, SYMBOL_BID) :
SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double trailing_distance = InpTrailingStop * _Point * 10;
double new_sl = 0;
if(position_type == POSITION_TYPE_BUY)
{
new_sl = current_price - trailing_distance;
if(new_sl > position_sl && new_sl < current_price)
{
// Check trailing step
if(position_sl == 0 || (new_sl - position_sl) >= InpTrailingStep * _Point * 10)
{
if(trade.PositionModify(_Symbol, new_sl, position_tp))
{
Print("Trailing stop updated: New SL=", new_sl);
}
}
}
}
else if(position_type == POSITION_TYPE_SELL)
{
new_sl = current_price + trailing_distance;
if((position_sl == 0 || new_sl < position_sl) && new_sl > current_price)
{
// Check trailing step
if(position_sl == 0 || (position_sl - new_sl) >= InpTrailingStep * _Point * 10)
{
if(trade.PositionModify(_Symbol, new_sl, position_tp))
{
Print("Trailing stop updated: New SL=", new_sl);
}
}
}
}
}
-256
View File
@@ -1,256 +0,0 @@
#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.");
}
}
@@ -1,21 +0,0 @@
; EMASlopeDistanceCocktailBTCUSD\main.mq5 — fixed inputs (same as desktop ultimate.set)
; Attach EA to BTCUSD chart. Timeframe 16385 = H1.
;
EMA_Periode=50||50||1||500||N
PreisSchwelle=700.0||700.0||70.000000||7000.000000||N
SteigungSchwelle=25.0||25.0||2.500000||250.000000||N
ÜberwachungTimeout=340||340||1||3400||N
TrailingStop=370.0||370.0||37.000000||3700.000000||N
LotGröße=0.07||0.07||0.007000||0.700000||N
MagicNumber=135790||135790||1||1357900||N
UseSpreadAdjustment=true||false||0||true||N
Timeframe=16385||0||0||49153||N
UseBarData=true||false||0||true||N
MaxTradesPerCrossover=10||10||1||100||N
ProfitCheckBars=15||15||1||150||N
CloseUnprofitableTrades=true||false||0||true||N
UseWeeklyADXFilter=true||false||0||true||N
WeeklyADXPeriod=15||15||1||150||N
WeeklyADXMin=40.0||40.0||4.000000||400.000000||N
WeeklyADXBarShift=2||2||1||20||N
WeeklyADXUseDirection=true||false||0||true||N
@@ -1,24 +0,0 @@
; EMASlopeDistanceCocktailBTCUSD\main.mq5 — BTCUSD Strategy Tester preset
; Load: Tester → Inputs → context menu → Load. Attach EA to BTCUSD chart (EA uses _Symbol).
;
; value||start||step||stop||Y|N (MT5 convention). Timeframe 16385 = H1.
; Tune PreisSchwelle / TrailingStop / SteigungSchwelle to your broker's _Point for BTC.
;
EMA_Periode=50||30||5||200||Y
PreisSchwelle=700.0||200.0||50.0||5000.0||Y
SteigungSchwelle=25.0||5.0||1.0||80.0||Y
ÜberwachungTimeout=340||60||20||900||Y
TrailingStop=370.0||150.0||20.0||5000.0||Y
LotGröße=0.07||0.01||0.01||0.50||N
MagicNumber=135790||135790||1||1357900||N
UseSpreadAdjustment=true||false||0||true||Y
Timeframe=16385||0||0||49153||N
UseBarData=true||false||0||true||N
MaxTradesPerCrossover=10||1||1||25||Y
ProfitCheckBars=15||5||1||60||Y
CloseUnprofitableTrades=true||false||0||true||Y
UseWeeklyADXFilter=true||false||0||true||Y
WeeklyADXPeriod=15||7||1||28||Y
WeeklyADXMin=40.0||15.0||2.0||55.0||Y
WeeklyADXBarShift=2||1||1||5||Y
WeeklyADXUseDirection=true||false||0||true||Y
@@ -1,589 +0,0 @@
//+------------------------------------------------------------------+
//| EMACrossOver.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#include <Trade\Trade.mqh>
#include "../_united/MagicNumberHelpers.mqh"
//--- Eingabeparameter (Input Parameters) - Optimized Profitable Parameters
input int EMA_Periode = 50; // EMA Periode
input double PreisSchwelle = 700.0; // Preisbewegung Schwelle in Pips
input double SteigungSchwelle = 25.0; // EMA Steigung Schwelle in Pips
input int ÜberwachungTimeout = 340; // Überwachungszeit in Sekunden
input double TrailingStop = 370.0; // Gleitender Stop in Pips
input double LotGröße = 0.07; // Handelsvolumen
input int MagicNumber = 135790; // Magic Number für Trades
input bool UseSpreadAdjustment = true; // Spread-Anpassung verwenden
input ENUM_TIMEFRAMES Timeframe = PERIOD_H1; // Zeitraum für Analyse
input bool UseBarData = true; // Bar-Daten statt Tick-Daten verwenden
input int MaxTradesPerCrossover = 10; // Maximale Trades pro Crossover-Ereignis
input int ProfitCheckBars = 15; // Bars bis zur Profit-Prüfung
input bool CloseUnprofitableTrades = true; // Unprofitable Trades nach X Bars schließen
input bool UseWeeklyADXFilter = true; // W1 ADX Trendfilter aktivieren
input int WeeklyADXPeriod = 15; // ADX-Periode auf W1
input double WeeklyADXMin = 40.0; // Minimaler ADX fuer Trendfreigabe
input int WeeklyADXBarShift = 2; // 1=letzte geschlossene W1-Kerze
input bool WeeklyADXUseDirection = true; // +DI/-DI Richtung mitpruefen
//--- Globale Variablen (Global Variables)
int ema_handle; // EMA Indicator Handle
double ema_array[]; // Array für EMA
datetime letzte_überwachung_zeit; // Zeit der letzten Überwachung
bool überwachung_aktiv = false; // Überwachungsstatus
bool preis_trigger_aktiv = false; // Preis-Trigger Status
bool steigung_trigger_aktiv = false; // Steigungs-Trigger Status
int ticket = 0; // Trade Ticket
CTrade trade; // CTrade Objekt
int trades_in_current_crossover = 0; // Anzahl Trades im aktuellen Crossover
bool crossover_detected = false; // Crossover erkannt
datetime trade_open_time = 0; // Zeitpunkt des Trade-Öffnens
//+------------------------------------------------------------------+
//| Weekly ADX trend filter |
//+------------------------------------------------------------------+
bool IsWeeklyADXTrendFavorable(ENUM_ORDER_TYPE order_type)
{
if(!UseWeeklyADXFilter)
return true;
int adxShift = WeeklyADXBarShift;
if(adxShift < 0)
adxShift = 0;
int adx_handle = iADX(_Symbol, PERIOD_W1, WeeklyADXPeriod);
if(adx_handle == INVALID_HANDLE)
{
Print("TRACE: Weekly ADX Handle ungültig - Filter blockiert Entry");
return false;
}
double adx_buf[], plus_di_buf[], minus_di_buf[];
ArraySetAsSeries(adx_buf, true);
ArraySetAsSeries(plus_di_buf, true);
ArraySetAsSeries(minus_di_buf, true);
bool ok_adx = (CopyBuffer(adx_handle, 0, adxShift, 1, adx_buf) > 0);
bool ok_plus = (CopyBuffer(adx_handle, 1, adxShift, 1, plus_di_buf) > 0);
bool ok_minus = (CopyBuffer(adx_handle, 2, adxShift, 1, minus_di_buf) > 0);
IndicatorRelease(adx_handle);
if(!ok_adx || !ok_plus || !ok_minus)
{
Print("TRACE: Weekly ADX Daten nicht verfügbar - Filter blockiert Entry");
return false;
}
double adx_value = adx_buf[0];
double plus_di = plus_di_buf[0];
double minus_di = minus_di_buf[0];
bool strength_ok = (adx_value >= WeeklyADXMin);
bool direction_ok = true;
if(WeeklyADXUseDirection)
{
if(order_type == ORDER_TYPE_BUY)
direction_ok = (plus_di > minus_di);
else
direction_ok = (minus_di > plus_di);
}
Print("TRACE: Weekly ADX Filter | ADX=", DoubleToString(adx_value, 2),
" +DI=", DoubleToString(plus_di, 2),
" -DI=", DoubleToString(minus_di, 2),
" strength_ok=", strength_ok,
" direction_ok=", direction_ok);
return (strength_ok && direction_ok);
}
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- CTrade konfigurieren (Configure CTrade)
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(10);
trade.SetTypeFilling(ORDER_FILLING_IOC);
//--- EMA Indicator Handle erstellen (Create EMA indicator handle)
ema_handle = iMA(_Symbol, Timeframe, EMA_Periode, 0, MODE_EMA, PRICE_CLOSE);
if(ema_handle == INVALID_HANDLE)
{
Print("Fehler beim Erstellen des EMA Indicators");
return(INIT_FAILED);
}
//--- Arrays initialisieren (Initialize arrays)
ArraySetAsSeries(ema_array, true);
//--- Arrays mit aktuellen Werten füllen (Fill arrays with current values)
BerechneEMA();
Print("EMA EA initialisiert - Periode: ", EMA_Periode, " Timeframe: ", EnumToString(Timeframe), " Handle: ", ema_handle);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//--- Indicator Handle freigeben (Release indicator handle)
if(ema_handle != INVALID_HANDLE)
{
IndicatorRelease(ema_handle);
}
Print("EA beendet - Grund: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- Bar-Daten oder Tick-Daten verwenden (Use bar data or tick data)
if(UseBarData)
{
//--- Nur bei neuen Bars ausführen (Only execute on new bars)
static datetime last_bar_time = 0;
datetime current_bar_time = iTime(_Symbol, Timeframe, 0);
if(current_bar_time == last_bar_time)
{
return; // Kein neuer Bar, nichts tun
}
last_bar_time = current_bar_time;
}
//--- EMA Werte berechnen (Calculate EMA values)
BerechneEMA();
//--- Debug: Aktuelle Werte ausgeben (Debug: Output current values)
if(ArraySize(ema_array) > 0)
{
double aktueller_close = iClose(_Symbol, Timeframe, 0);
double ema_aktuell = ema_array[0];
double ema_vorher = ema_array[1];
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / _Point;
double steigung = (ema_aktuell - ema_vorher) / _Point;
if(UseBarData)
{
Print("=== DEBUG INFO (Neuer Bar) ===");
Print("Bar Zeit: ", TimeToString(iTime(_Symbol, Timeframe, 0)));
}
else
{
Print("=== DEBUG INFO (Tick) ===");
}
Print("Aktueller Close: ", aktueller_close);
Print("EMA: ", ema_aktuell);
Print("Preis-Abstand: ", preis_abstand, " Pips");
Print("EMA Steigung: ", steigung, " Pips");
Print("Differenz Close-EMA: ", aktueller_close - ema_aktuell);
Print("Preis-Trigger: ", preis_trigger_aktiv, " Steigungs-Trigger: ", steigung_trigger_aktiv);
Print("Überwachung aktiv: ", überwachung_aktiv);
Print("Position offen: ", PositionExistsByMagic(_Symbol, MagicNumber));
Print("Trades im aktuellen Crossover: ", trades_in_current_crossover, "/", MaxTradesPerCrossover);
Print("==================");
}
//--- Überwachung prüfen (Check monitoring)
if(überwachung_aktiv)
{
if(UseBarData)
{
// Bar-basierte Überwachungszeit
int bars_since_monitoring = iBarShift(_Symbol, Timeframe, letzte_überwachung_zeit);
int timeout_bars = (int)(ÜberwachungTimeout / PeriodSeconds(Timeframe));
if(bars_since_monitoring > timeout_bars)
{
überwachung_aktiv = false;
preis_trigger_aktiv = false;
steigung_trigger_aktiv = false;
Print("Überwachung beendet - Bar-basierte Zeitüberschreitung (", bars_since_monitoring, " Bars)");
}
}
else
{
// Tick-basierte Überwachungszeit
if(TimeCurrent() - letzte_überwachung_zeit > ÜberwachungTimeout)
{
überwachung_aktiv = false;
preis_trigger_aktiv = false;
steigung_trigger_aktiv = false;
Print("Überwachung beendet - Tick-basierte Zeitüberschreitung");
}
}
}
//--- Trigger-Bedingungen prüfen (Check trigger conditions)
PrüfeTrigger();
//--- Trade Management (Trade management)
VerwalteTrades();
}
//+------------------------------------------------------------------+
//| EMA Berechnung (EMA Calculation) |
//+------------------------------------------------------------------+
void BerechneEMA()
{
//--- EMA Werte vom Indicator kopieren (Copy EMA values from indicator)
int copied = CopyBuffer(ema_handle, 0, 0, 3, ema_array);
if(copied <= 0)
{
Print("TRACE: Fehler beim Kopieren der EMA Werte - Copied: ", copied);
return;
}
Print("TRACE: EMA Werte kopiert: ", copied, " Bars");
Print("TRACE: EMA [0]: ", ema_array[0], " [1]: ", ema_array[1], " [2]: ", ema_array[2]);
}
//+------------------------------------------------------------------+
//| Trigger-Bedingungen prüfen (Check trigger conditions) |
//+------------------------------------------------------------------+
void PrüfeTrigger()
{
if(ArraySize(ema_array) < 2)
{
Print("TRACE: Array zu klein - Größe: ", ArraySize(ema_array));
return;
}
//--- Aktuelle Werte (Current values)
double aktueller_preis = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double aktueller_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double aktueller_close = iClose(_Symbol, Timeframe, 0);
double pips_multiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
//--- EMA Werte in Variablen (EMA values in variables)
double ema_aktuell = ema_array[0];
double ema_vorher = ema_array[1];
//--- EMA Crossover Erkennung (EMA Crossover Detection)
// Prüfe ob Preis die EMA kreuzt (Check if price crosses EMA)
static double last_close = 0;
static double last_ema = 0;
if(last_close != 0 && last_ema != 0)
{
bool crossover_bullish = (last_close <= last_ema) && (aktueller_close > ema_aktuell);
bool crossover_bearish = (last_close >= last_ema) && (aktueller_close < ema_aktuell);
//--- Neues Crossover-Ereignis erkannt (New crossover event detected)
if(crossover_bullish || crossover_bearish)
{
trades_in_current_crossover = 0; // Reset trade counter
Print("TRACE: EMA Crossover erkannt - ", (crossover_bullish ? "BULLISH" : "BEARISH"), " - Trade-Counter zurückgesetzt");
Print("TRACE: Vorher: Close=", last_close, " EMA=", last_ema, " Jetzt: Close=", aktueller_close, " EMA=", ema_aktuell);
}
}
//--- Aktuelle Werte für nächsten Vergleich speichern (Save current values for next comparison)
last_close = aktueller_close;
last_ema = ema_aktuell;
//--- Preisbewegung zur EMA prüfen (Check price action to EMA)
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / _Point / pips_multiplier;
Print("TRACE: Preis-Abstand: ", preis_abstand, " Pips (Schwelle: ", PreisSchwelle, ")");
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
Print("TRACE: Trades im aktuellen Crossover: ", trades_in_current_crossover, "/", MaxTradesPerCrossover);
if(preis_abstand > PreisSchwelle && !preis_trigger_aktiv)
{
preis_trigger_aktiv = true;
Print("TRACE: Preis-Trigger aktiviert: ", preis_abstand, " Pips");
}
//--- EMA Steigung prüfen (Check EMA slope)
double steigung = (ema_aktuell - ema_vorher) / _Point / pips_multiplier;
Print("TRACE: EMA Steigung: ", steigung, " Pips (Schwelle: ", SteigungSchwelle, ")");
if(MathAbs(steigung) > SteigungSchwelle && !steigung_trigger_aktiv)
{
steigung_trigger_aktiv = true;
Print("TRACE: Steigungs-Trigger aktiviert: ", steigung, " Pips");
}
//--- Überwachung starten wenn beide Trigger aktiv sind (Start monitoring when both triggers are active)
if(preis_trigger_aktiv && steigung_trigger_aktiv && !überwachung_aktiv)
{
überwachung_aktiv = true;
if(UseBarData)
{
letzte_überwachung_zeit = iTime(_Symbol, Timeframe, 0); // Aktuelle Bar-Zeit
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Bar: ", TimeToString(letzte_überwachung_zeit), ")");
}
else
{
letzte_überwachung_zeit = TimeCurrent(); // Aktuelle Tick-Zeit
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Tick)");
}
}
//--- Trade platzieren wenn Überwachung aktiv und Preis über/unter EMA (Place trade when monitoring active and price above/below EMA)
if(überwachung_aktiv)
{
bool bullish_signal = aktueller_close > ema_aktuell;
bool bearish_signal = aktueller_close < ema_aktuell;
Print("TRACE: Signal Check - Bullish: ", bullish_signal, " Bearish: ", bearish_signal);
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
Print("TRACE: Differenz: ", aktueller_close - ema_aktuell);
//--- Trade-Limit prüfen (Check trade limit)
if(trades_in_current_crossover >= MaxTradesPerCrossover)
{
Print("TRACE: Trade-Limit erreicht (", MaxTradesPerCrossover, ") - Kein neuer Trade");
return;
}
if(bullish_signal && !PositionExistsByMagic(_Symbol, MagicNumber))
{
if(!IsWeeklyADXTrendFavorable(ORDER_TYPE_BUY))
{
Print("TRACE: Weekly ADX blockiert BUY-Entry");
return;
}
Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", trades_in_current_crossover + 1, ")");
if(PlatziereTrade(ORDER_TYPE_BUY))
{
trades_in_current_crossover++;
}
}
else if(bearish_signal && !PositionExistsByMagic(_Symbol, MagicNumber))
{
if(!IsWeeklyADXTrendFavorable(ORDER_TYPE_SELL))
{
Print("TRACE: Weekly ADX blockiert SELL-Entry");
return;
}
Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", trades_in_current_crossover + 1, ")");
if(PlatziereTrade(ORDER_TYPE_SELL))
{
trades_in_current_crossover++;
}
}
else if(PositionExistsByMagic(_Symbol, MagicNumber))
{
Print("TRACE: Position bereits offen - kein neuer Trade");
}
}
}
//+------------------------------------------------------------------+
//| Trade platzieren (Place trade) |
//+------------------------------------------------------------------+
bool PlatziereTrade(ENUM_ORDER_TYPE order_type)
{
Print("TRACE: Versuche Trade zu platzieren - Typ: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF");
Print("TRACE: Lot: ", LotGröße);
bool success = false;
if(order_type == ORDER_TYPE_BUY)
{
success = trade.Buy(LotGröße, _Symbol, 0, 0, 0, "EMA Crossover Trade");
}
else
{
success = trade.Sell(LotGröße, _Symbol, 0, 0, 0, "EMA Crossover Trade");
}
if(success)
{
ticket = (int)trade.ResultOrder();
Print("TRACE: Trade erfolgreich platziert: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF", " Ticket: ", ticket);
//--- Trade-Öffnungszeit speichern (Save trade opening time)
trade_open_time = iTime(_Symbol, Timeframe, 0);
Print("TRACE: Trade-Öffnungszeit: ", TimeToString(trade_open_time));
//--- Überwachung zurücksetzen (Reset monitoring)
überwachung_aktiv = false;
preis_trigger_aktiv = false;
steigung_trigger_aktiv = false;
return true;
}
else
{
Print("TRACE: Fehler beim Platzieren des Trades - Retcode: ", trade.ResultRetcode());
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
return false;
}
}
//+------------------------------------------------------------------+
//| Trades verwalten (Manage trades) |
//+------------------------------------------------------------------+
void VerwalteTrades()
{
if(!PositionSelectByMagic(_Symbol, MagicNumber))
return;
double position_profit = PositionGetDouble(POSITION_PROFIT);
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
double pips_multiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
double trailing_stop_pips = TrailingStop;
//--- Gleitender Stop (Trailing Stop) - nur wenn Position im Profit ist
if(position_profit > 0) // Only apply trailing stop when in profit
{
if(position_type == POSITION_TYPE_BUY)
{
double new_stop_loss = current_price - (trailing_stop_pips * _Point * pips_multiplier);
double current_stop_loss = PositionGetDouble(POSITION_SL);
// Only move stop loss if new stop is higher than current stop
if(new_stop_loss > current_stop_loss)
{
ÄndereStopLoss(new_stop_loss);
}
}
else if(position_type == POSITION_TYPE_SELL)
{
double new_stop_loss = current_price + (trailing_stop_pips * _Point * pips_multiplier);
double current_stop_loss = PositionGetDouble(POSITION_SL);
// Only move stop loss if new stop is lower than current stop
if(new_stop_loss < current_stop_loss || current_stop_loss == 0)
{
ÄndereStopLoss(new_stop_loss);
}
}
}
//--- Ausstieg bei Preis unter/über EMA (Exit when price below/above EMA)
if(ArraySize(ema_array) >= 1)
{
double aktueller_close = iClose(_Symbol, Timeframe, 0);
double ema_aktuell = ema_array[0];
bool exit_bullish = (position_type == POSITION_TYPE_SELL && aktueller_close > ema_aktuell);
bool exit_bearish = (position_type == POSITION_TYPE_BUY && aktueller_close < ema_aktuell);
if(exit_bullish || exit_bearish)
{
Print("TRACE: Ausstiegssignal - Close: ", aktueller_close, " EMA: ", ema_aktuell);
SchließePosition("EMA Crossover Exit");
Print("TRACE: Position geschlossen - Trade-Counter bleibt bei ", trades_in_current_crossover);
}
}
//--- Profit-Prüfung nach X Bars (Profit check after X bars)
if(CloseUnprofitableTrades && trade_open_time != 0 && PositionExistsByMagic(_Symbol, MagicNumber))
{
Print("TRACE: Profit-Prüfung aktiviert - CloseUnprofitableTrades: ", CloseUnprofitableTrades);
PrüfeProfitNachBars();
}
else if(!CloseUnprofitableTrades)
{
Print("TRACE: Profit-Prüfung deaktiviert - CloseUnprofitableTrades: ", CloseUnprofitableTrades);
}
}
//+------------------------------------------------------------------+
//| Profit-Prüfung nach X Bars (Profit check after X bars) |
//+------------------------------------------------------------------+
void PrüfeProfitNachBars()
{
if(!PositionSelectByMagic(_Symbol, MagicNumber))
{
return; // Keine Position offen
}
datetime current_bar_time = iTime(_Symbol, Timeframe, 0);
int bars_since_trade_open = iBarShift(_Symbol, Timeframe, trade_open_time);
Print("TRACE: Bars seit Trade-Öffnung: ", bars_since_trade_open, "/", ProfitCheckBars);
//--- Prüfe ob genügend Bars vergangen sind (Check if enough bars have passed)
if(bars_since_trade_open >= ProfitCheckBars)
{
double position_profit = PositionGetDouble(POSITION_PROFIT);
double position_volume = PositionGetDouble(POSITION_VOLUME);
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
Print("TRACE: Profit-Prüfung nach ", ProfitCheckBars, " Bars");
Print("TRACE: Position Profit: ", position_profit, " USD");
//--- Schließe Position wenn nicht im Profit (Close position if not in profit)
if(position_profit <= 0)
{
Print("TRACE: Position nicht im Profit - Schließe Position");
SchließePosition("Profit Check - Unprofitable");
//--- Trade-Öffnungszeit zurücksetzen (Reset trade opening time)
trade_open_time = 0;
Print("TRACE: Trade-Öffnungszeit zurückgesetzt");
}
else
{
Print("TRACE: Position im Profit - Behalte Position");
//--- Trade-Öffnungszeit zurücksetzen um weitere Prüfungen zu vermeiden (Reset to avoid further checks)
trade_open_time = 0;
}
}
}
//+------------------------------------------------------------------+
//| Stop Loss ändern (Modify Stop Loss) |
//+------------------------------------------------------------------+
void ÄndereStopLoss(double new_stop_loss)
{
Print("TRACE: Versuche Stop Loss zu ändern auf: ", new_stop_loss);
bool success = ModifyPositionByMagic(trade, _Symbol, MagicNumber, new_stop_loss, PositionGetDouble(POSITION_TP));
if(success)
{
Print("TRACE: Stop Loss erfolgreich geändert auf: ", new_stop_loss);
}
else
{
Print("TRACE: Fehler beim Ändern des Stop Loss - Retcode: ", trade.ResultRetcode());
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
//| Position schließen (Close position) |
//+------------------------------------------------------------------+
void SchließePosition(string reason = "Unbekannt")
{
Print("TRACE: Versuche Position zu schließen - Grund: ", reason);
bool success = ClosePositionByMagic(trade, _Symbol, MagicNumber);
if(success)
{
Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason);
}
else
{
Print("TRACE: Fehler beim Schließen der Position - Retcode: ", trade.ResultRetcode());
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
}
}
//+------------------------------------------------------------------+
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

-246
View File
@@ -1,246 +0,0 @@
//+------------------------------------------------------------------+
//| MartingaleBTCUSD_Safe.mq5 |
//| Classic martingale: double lot after loss, reset after win (BTC) |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property version "1.01"
#property strict
#include <Trade\Trade.mqh>
input group "=== Market ==="
input string InpSymbol = "BTCUSD";
input ENUM_TIMEFRAMES InpTf = PERIOD_M15;
input ulong InpMagic = 202604241;
input int InpSlippagePts = 50;
input group "=== Martingale (classic) ==="
input double InpBaseLots = 0.01;
input double InpLotMultiplier = 2.0; // traditional = 2.0
input int InpMaxDoublings = 16; // cap exponent (0..MaxDoublings); then lot stops growing
input group "=== Entry (RSI) ==="
input int InpRsiPeriod = 14;
input double InpRsiBuyBelow = 32.0;
input double InpRsiSellAbove = 68.0;
input group "=== SL / TP (optional) ==="
input bool InpUseSLTP = false;
input double InpSLPts = 4000.0;
input double InpTPPts = 3500.0;
CTrade g_trade;
int g_hRsi = INVALID_HANDLE;
int g_lossStreak = 0;
ulong g_lastPosId = 0;
string WorkSym() { return InpSymbol; }
double SymPoint() { return SymbolInfoDouble(WorkSym(), SYMBOL_POINT); }
void SetFilling()
{
const long fill = SymbolInfoInteger(WorkSym(), SYMBOL_FILLING_MODE);
if((fill & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
else if((fill & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
}
double NetProfitForPositionId(const ulong posId)
{
if(posId == 0)
return 0.0;
const datetime to = TimeCurrent();
if(!HistorySelect(0, to))
return 0.0;
double sum = 0.0;
const int n = HistoryDealsTotal();
for(int i = 0; i < n; i++)
{
const ulong deal = HistoryDealGetTicket(i);
if(deal == 0)
continue;
if((ulong)HistoryDealGetInteger(deal, DEAL_POSITION_ID) != posId)
continue;
sum += HistoryDealGetDouble(deal, DEAL_PROFIT);
sum += HistoryDealGetDouble(deal, DEAL_SWAP);
sum += HistoryDealGetDouble(deal, DEAL_COMMISSION);
}
return sum;
}
int OurPositionCount()
{
int c = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong t = PositionGetTicket(i);
if(t == 0 || !PositionSelectByTicket(t))
continue;
if(PositionGetString(POSITION_SYMBOL) != WorkSym())
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
c++;
}
return c;
}
double LotsNow()
{
const int exp = MathMax(0, MathMin(g_lossStreak, InpMaxDoublings));
double lot = InpBaseLots * MathPow(InpLotMultiplier, (double)exp);
const double minLot = SymbolInfoDouble(WorkSym(), SYMBOL_VOLUME_MIN);
const double maxLot = SymbolInfoDouble(WorkSym(), SYMBOL_VOLUME_MAX);
const double stepLot = SymbolInfoDouble(WorkSym(), SYMBOL_VOLUME_STEP);
if(stepLot > 0.0)
lot = MathFloor(lot / stepLot) * stepLot;
if(lot < minLot)
lot = minLot;
if(lot > maxLot)
lot = maxLot;
return NormalizeDouble(lot, 8);
}
bool CopyRsi1(double &rsi1)
{
double buf[1];
if(CopyBuffer(g_hRsi, 0, 1, 1, buf) != 1)
return false;
rsi1 = buf[0];
return true;
}
void BuildSLTP(const bool isBuy, const double price, double &sl, double &tp)
{
sl = tp = 0.0;
if(!InpUseSLTP)
return;
const double pt = SymPoint();
if(pt <= 0.0)
return;
if(isBuy)
{
sl = price - InpSLPts * pt;
tp = price + InpTPPts * pt;
}
else
{
sl = price + InpSLPts * pt;
tp = price - InpTPPts * pt;
}
}
void OnClosedPosition()
{
const double net = NetProfitForPositionId(g_lastPosId);
if(net < 0.0)
g_lossStreak++;
else
g_lossStreak = 0;
Print("Martingale: closed net=", net, " lossStreak=", g_lossStreak, " next lot=", LotsNow());
g_lastPosId = 0;
}
bool OurPositionOpenById(const ulong posId)
{
if(posId == 0)
return false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong t = PositionGetTicket(i);
if(t == 0 || !PositionSelectByTicket(t))
continue;
if(PositionGetString(POSITION_SYMBOL) != WorkSym())
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
if((ulong)PositionGetInteger(POSITION_IDENTIFIER) == posId)
return true;
}
return false;
}
void CaptureLastPositionId()
{
Sleep(20);
for(int k = 0; k < PositionsTotal(); k++)
{
const ulong t = PositionGetTicket(k);
if(t == 0 || !PositionSelectByTicket(t))
continue;
if(PositionGetString(POSITION_SYMBOL) != WorkSym())
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
g_lastPosId = (ulong)PositionGetInteger(POSITION_IDENTIFIER);
return;
}
}
int OnInit()
{
if(InpBaseLots <= 0.0 || InpLotMultiplier < 1.0 || InpMaxDoublings < 0)
return INIT_PARAMETERS_INCORRECT;
if(!SymbolSelect(WorkSym(), true))
Print("Martingale: SymbolSelect note ", WorkSym());
g_hRsi = iRSI(WorkSym(), InpTf, InpRsiPeriod, PRICE_CLOSE);
if(g_hRsi == INVALID_HANDLE)
return INIT_FAILED;
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePts);
SetFilling();
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(g_hRsi != INVALID_HANDLE)
IndicatorRelease(g_hRsi);
}
void OnTick()
{
if(_Symbol != WorkSym())
return;
if(g_lastPosId != 0 && !OurPositionOpenById(g_lastPosId))
OnClosedPosition();
static datetime lastBar = 0;
const datetime tb = iTime(WorkSym(), InpTf, 0);
if(tb == 0 || tb == lastBar)
return;
lastBar = tb;
if(OurPositionCount() > 0)
return;
double rsi1 = 0.0;
if(!CopyRsi1(rsi1))
return;
const double lot = LotsNow();
if(lot <= 0.0)
return;
MqlTick tick;
if(!SymbolInfoTick(WorkSym(), tick))
return;
double sl = 0.0, tp = 0.0;
const bool wantBuy = (rsi1 <= InpRsiBuyBelow);
const bool wantSell = (rsi1 >= InpRsiSellAbove);
if(wantBuy && !wantSell)
{
BuildSLTP(true, tick.ask, sl, tp);
if(g_trade.Buy(lot, WorkSym(), tick.ask, sl, tp, "Martingale buy"))
CaptureLastPositionId();
}
else if(wantSell && !wantBuy)
{
BuildSLTP(false, tick.bid, sl, tp);
if(g_trade.Sell(lot, WorkSym(), tick.bid, sl, tp, "Martingale sell"))
CaptureLastPositionId();
}
}
@@ -1,367 +0,0 @@
//+------------------------------------------------------------------+
//| RSIConsolidation.mq5 |
//| Mean-reversion RSI for ranging markets; trend filters block runs |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025"
#property link "https://www.mql5.com"
#property version "1.01"
#include <Trade\Trade.mqh>
//--- Symbol (empty = chart symbol)
input group "=== Symbol & session ==="
input string InpSymbol = "";
input group "=== Timeframe & bar logic ==="
input ENUM_TIMEFRAMES SignalTF = PERIOD_M15;
input bool EntryOnNewBarOnly = true;
//--- Core: no trend / consolidation regime
input group "=== Regime: consolidation (anti-trend) ==="
input int ADX_Period = 23;
input double ADX_Max = 38.0; // allow more bars (was 29 — very few on BTC)
input bool UseATRRatioFilter = true;
input int ATR_Period = 8;
input int ATR_SMA_Period = 35;
input double ATR_Ratio_Max = 1.55; // slightly looser vs 1.36
input bool UseFlatEMAFilter = true;
input int EMA_Fast = 13;
input int EMA_Slow = 17;
input double EMA_Separation_MaxPct = 0.42; // %; was 0.26 — very strict on crypto
//--- RSI entries (fade extremes toward mean)
input group "=== RSI entries ==="
input int RSI_Period = 8;
input ENUM_APPLIED_PRICE RSI_Price = PRICE_CLOSE; // OPEN made crosses rarer; CLOSE is standard
input double RSI_Oversold = 28.0;
input double RSI_Overbought = 68.0;
input bool UseStrictRsiCross = false; // true = exact cross; false = looser bounce (more trades)
input double RsiCrossSlack = 4.0; // only if !UseStrictRsiCross: widen cross band
//--- Exits: mean target + hard ATR bracket
input group "=== Exits ==="
input bool UseRSI_MeanExit = true;
input double RSI_Exit_Long = 48.0;
input double RSI_Exit_Short = 52.0;
input double SL_ATR_Mult = 2.15;
input double TP_ATR_Mult = 2.40;
input int MaxBarsInTrade = 54;
input group "=== Risk & execution ==="
input double Lots = 0.10;
input ulong MagicNumber = 20250420;
input int Slippage = 10;
input int MaxSpreadPoints = 0; // 0 = off (BTC tester/live often blocked at 28)
CTrade trade;
string g_sym;
int h_rsi = INVALID_HANDLE;
int h_adx = INVALID_HANDLE;
int h_atr = INVALID_HANDLE;
int h_ema_fast = INVALID_HANDLE;
int h_ema_slow = INVALID_HANDLE;
datetime g_last_bar = 0;
bool PositionExistsByMagicSym(string sym, ulong magic)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(t == 0) continue;
if(PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == (long)magic)
return true;
}
return false;
}
ulong GetPositionTicketByMagicSym(string sym, ulong magic)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(t == 0) continue;
if(PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == (long)magic)
return t;
}
return 0;
}
bool SelectPositionTicketSymMagic(ulong ticket, string sym, ulong magic)
{
if(!PositionSelectByTicket(ticket)) return false;
return PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == (long)magic;
}
double NormalizeVolume(string sym, double vol)
{
double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
if(step > 0.0)
vol = MathFloor(vol / step) * step;
if(vol < minLot) vol = minLot;
if(vol > maxLot) vol = maxLot;
return vol;
}
int CurrentSpreadPoints(string sym)
{
long spread = 0;
if(!SymbolInfoInteger(sym, SYMBOL_SPREAD, spread))
return 999999;
return (int)spread;
}
double MinStopsDistancePrice(string sym)
{
long lvl = 0;
if(!SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL, lvl))
return 0;
double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
if(pt <= 0)
return 0;
return (double)lvl * pt;
}
bool Copy1(int handle, double &v)
{
double b[];
ArraySetAsSeries(b, true);
if(CopyBuffer(handle, 0, 0, 1, b) < 1) return false;
v = b[0];
return true;
}
bool CopyAtShift(int handle, const int shift, double &v)
{
double b[];
ArraySetAsSeries(b, true);
if(CopyBuffer(handle, 0, shift, 1, b) < 1) return false;
v = b[0];
return true;
}
bool RSI_Buffers(double &cur, double &prev, double &twoAgo)
{
double b[];
ArraySetAsSeries(b, true);
if(CopyBuffer(h_rsi, 0, 0, 3, b) < 3) return false;
cur = b[0];
prev = b[1];
twoAgo = b[2];
return true;
}
bool Regime_IsConsolidation()
{
const int sh = 1;
double adx = 0;
if(!CopyAtShift(h_adx, sh, adx))
return false;
if(adx >= ADX_Max)
return false;
if(UseATRRatioFilter)
{
double atrArr[];
ArraySetAsSeries(atrArr, true);
if(CopyBuffer(h_atr, 0, sh, ATR_SMA_Period + 1, atrArr) < ATR_SMA_Period + 1)
return false;
double sum = 0;
for(int i = 1; i <= ATR_SMA_Period; i++)
sum += atrArr[i];
double smaAtr = sum / (double)ATR_SMA_Period;
if(smaAtr <= 0.0)
return false;
double ratio = atrArr[0] / smaAtr;
if(ratio > ATR_Ratio_Max)
return false;
}
if(UseFlatEMAFilter)
{
double ef[], es[];
ArraySetAsSeries(ef, true);
ArraySetAsSeries(es, true);
if(CopyBuffer(h_ema_fast, 0, sh, 1, ef) < 1) return false;
if(CopyBuffer(h_ema_slow, 0, sh, 1, es) < 1) return false;
double c = SymbolInfoDouble(g_sym, SYMBOL_BID);
if(c <= 0) return false;
double sep = MathAbs(ef[0] - es[0]) / c * 100.0;
if(sep > EMA_Separation_MaxPct)
return false;
}
return true;
}
bool Entry_BuyCross(double twoAgo, double prev)
{
if(UseStrictRsiCross)
return (twoAgo <= RSI_Oversold && prev > RSI_Oversold);
const double lo = RSI_Oversold - RsiCrossSlack;
const double hi = RSI_Oversold + RsiCrossSlack;
return (twoAgo <= hi && prev > lo && prev > twoAgo);
}
bool Entry_SellCross(double twoAgo, double prev)
{
if(UseStrictRsiCross)
return (twoAgo >= RSI_Overbought && prev < RSI_Overbought);
const double lo = RSI_Overbought - RsiCrossSlack;
const double hi = RSI_Overbought + RsiCrossSlack;
return (twoAgo >= lo && prev < hi && prev < twoAgo);
}
void TryCloseByRSI(ENUM_POSITION_TYPE typ, double rsi)
{
ulong tk = GetPositionTicketByMagicSym(g_sym, MagicNumber);
if(tk == 0 || !SelectPositionTicketSymMagic(tk, g_sym, MagicNumber))
return;
if(!UseRSI_MeanExit)
return;
if(typ == POSITION_TYPE_BUY && rsi >= RSI_Exit_Long)
trade.PositionClose(tk);
else if(typ == POSITION_TYPE_SELL && rsi <= RSI_Exit_Short)
trade.PositionClose(tk);
}
void ManageOpenPosition(double rsi)
{
ulong tk = GetPositionTicketByMagicSym(g_sym, MagicNumber);
if(tk == 0 || !SelectPositionTicketSymMagic(tk, g_sym, MagicNumber))
return;
ENUM_POSITION_TYPE typ = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
datetime openT = (datetime)PositionGetInteger(POSITION_TIME);
int barsAgo = iBarShift(g_sym, SignalTF, openT, false);
if(barsAgo >= 0 && barsAgo >= MaxBarsInTrade)
{
trade.PositionClose(tk);
return;
}
TryCloseByRSI(typ, rsi);
}
int OnInit()
{
g_sym = InpSymbol;
StringTrimLeft(g_sym);
StringTrimRight(g_sym);
if(StringLen(g_sym) == 0)
g_sym = _Symbol;
if(!SymbolSelect(g_sym, true))
{
Print("RSIConsolidation: SymbolSelect failed: ", g_sym);
return INIT_FAILED;
}
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_RETURN);
h_rsi = iRSI(g_sym, SignalTF, RSI_Period, RSI_Price);
h_adx = iADX(g_sym, SignalTF, ADX_Period);
h_atr = iATR(g_sym, SignalTF, ATR_Period);
h_ema_fast = iMA(g_sym, SignalTF, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
h_ema_slow = iMA(g_sym, SignalTF, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
if(h_rsi == INVALID_HANDLE || h_adx == INVALID_HANDLE || h_atr == INVALID_HANDLE
|| h_ema_fast == INVALID_HANDLE || h_ema_slow == INVALID_HANDLE)
{
Print("RSIConsolidation: indicator init failed");
return INIT_FAILED;
}
Print("RSIConsolidation: symbol=", g_sym, " TF=", EnumToString(SignalTF));
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(h_rsi != INVALID_HANDLE) IndicatorRelease(h_rsi);
if(h_adx != INVALID_HANDLE) IndicatorRelease(h_adx);
if(h_atr != INVALID_HANDLE) IndicatorRelease(h_atr);
if(h_ema_fast != INVALID_HANDLE) IndicatorRelease(h_ema_fast);
if(h_ema_slow != INVALID_HANDLE) IndicatorRelease(h_ema_slow);
}
bool EnoughHistory()
{
int need = MathMax(RSI_Period + 3, MathMax(ADX_Period + 2, ATR_SMA_Period + 3));
if(Bars(g_sym, SignalTF) < need)
return false;
return true;
}
void OnTick()
{
if(!EnoughHistory())
return;
if(MaxSpreadPoints > 0 && CurrentSpreadPoints(g_sym) > MaxSpreadPoints)
return;
double rsi, rsiPrev, rsi2;
if(!RSI_Buffers(rsi, rsiPrev, rsi2))
return;
datetime barTime = iTime(g_sym, SignalTF, 0);
bool isNew = (barTime != g_last_bar);
if(PositionExistsByMagicSym(g_sym, MagicNumber))
{
ManageOpenPosition(rsi);
if(isNew)
g_last_bar = barTime;
return;
}
if(EntryOnNewBarOnly && !isNew)
return;
g_last_bar = barTime;
if(!Regime_IsConsolidation())
return;
double atrArr[];
ArraySetAsSeries(atrArr, true);
if(CopyBuffer(h_atr, 0, 0, 1, atrArr) < 1)
return;
double atr = atrArr[0];
int dig = (int)SymbolInfoInteger(g_sym, SYMBOL_DIGITS);
double slDist = atr * SL_ATR_Mult;
double tpDist = atr * TP_ATR_Mult;
double minD = MinStopsDistancePrice(g_sym);
if(slDist < minD)
slDist = minD;
if(tpDist < minD)
tpDist = minD;
double vol = NormalizeVolume(g_sym, Lots);
if(Entry_BuyCross(rsi2, rsiPrev))
{
double ask = SymbolInfoDouble(g_sym, SYMBOL_ASK);
double sl = ask - slDist;
double tp = ask + tpDist;
sl = NormalizeDouble(sl, dig);
tp = NormalizeDouble(tp, dig);
trade.Buy(vol, g_sym, ask, sl, tp, "RSIConsolidation BUY");
}
else if(Entry_SellCross(rsi2, rsiPrev))
{
double bid = SymbolInfoDouble(g_sym, SYMBOL_BID);
double sl = bid + slDist;
double tp = bid - tpDist;
sl = NormalizeDouble(sl, dig);
tp = NormalizeDouble(tp, dig);
trade.Sell(vol, g_sym, bid, sl, tp, "RSIConsolidation SELL");
}
}
//+------------------------------------------------------------------+
@@ -1,38 +0,0 @@
; RSIConsolidation.mq5 v1.01 — BTCUSD preset (matches relaxed defaults)
; Strategy Tester → Inputs → Load
;
; === Symbol & session ===
InpSymbol=BTCUSD
; === Timeframe & bar logic ===
SignalTF=15||15||0||15||N
EntryOnNewBarOnly=true||false||0||true||N
; === Regime: consolidation (anti-trend) ===
ADX_Period=23||10||1||40||Y
ADX_Max=38.0||22.0||1.0||50.0||Y
UseATRRatioFilter=true||false||0||true||N
ATR_Period=8||5||1||21||Y
ATR_SMA_Period=35||14||2||80||Y
ATR_Ratio_Max=1.55||1.0||0.02||2.0||Y
UseFlatEMAFilter=true||false||0||true||N
EMA_Fast=13||5||1||21||Y
EMA_Slow=17||10||1||34||Y
EMA_Separation_MaxPct=0.42||0.10||0.02||0.70||Y
; === RSI entries ===
RSI_Period=8||5||1||21||Y
RSI_Price=0||0||0||7||Y
RSI_Oversold=28.0||18.0||1.0||42.0||Y
RSI_Overbought=68.0||55.0||1.0||82.0||Y
UseStrictRsiCross=false||false||0||true||Y
RsiCrossSlack=4.0||0.0||0.5||12.0||Y
; === Exits ===
UseRSI_MeanExit=true||false||0||true||N
RSI_Exit_Long=48.0||40.0||1.0||55.0||Y
RSI_Exit_Short=52.0||45.0||1.0||60.0||Y
SL_ATR_Mult=2.15||1.0||0.05||3.5||Y
TP_ATR_Mult=2.40||1.2||0.05||4.0||Y
MaxBarsInTrade=54||20||2||120||Y
; === Risk & execution ===
Lots=0.1||0.01||0.01||0.50||N
MagicNumber=20250420||20250420||1||20250420||N
Slippage=30||20||5||200||N
MaxSpreadPoints=0||0||1||400||Y
-437
View File
@@ -1,437 +0,0 @@
//+------------------------------------------------------------------+
//| RSIScalping.mq5 |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.03"
#include <Trade\Trade.mqh>
#include "../_united/MagicNumberHelpers.mqh"
//--- Input parameters
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis
input int RSI_Period = 14; // RSI Period
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
input double RSI_Overbought = 71; // RSI Overbought Level
input double RSI_Oversold = 57; // RSI Oversold Level
input bool UseEntrySlopeFilter = false; // require RSI momentum on entry bars
input double EntryMinSlopePerBar = 1.0; // minimum RSI delta per bar for entry
input double RSI_Target_Buy = 80; // RSI Target for Buy Exit
input double RSI_Target_Sell = 57; // RSI Target for Sell Exit
input int BarsToWait = 4; // Bars to wait when RSI goes against position
input bool ExitOnAdverseRsiBarStep = true; // new bar: exit if last closed RSI vs prior closed is against trade
input double LotSize = 0.1; // Lot Size
input int MagicNumber = 129102315; // Magic Number
input int Slippage = 3; // Slippage in points
input group "=== Reversal escape (intrabar, multi-signal) ==="
input bool UseReversalEscape = true; // run while in position every tick
input int ReversalATRPeriod = 14; // ATR lookback on signal timeframe
input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR
input int ReversalSignsRequired = 2; // how many independent signs must align
input double ReversalRsiVelocity = 16.0; // RSI points drop (long) / rise (short) vs prior buffer
input double ReversalBodyAtrMult = 5.1; // last closed bar body >= this * ATR counts as one sign
//--- Global variables
CTrade trade;
int rsi_handle;
double rsi_buffer[];
double rsi_prev, rsi_current, rsi_two_bars_ago;
bool position_open = false;
int position_ticket = 0;
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
datetime last_bar_time = 0;
bool rsi_against_position = false;
int bars_against_count = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize RSI indicator
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
if(rsi_handle == INVALID_HANDLE)
{
return(INIT_FAILED);
}
// Initialize trade object
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
// Allocate arrays
ArraySetAsSeries(rsi_buffer, true);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(rsi_handle != INVALID_HANDLE)
IndicatorRelease(rsi_handle);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
return;
const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
const bool new_bar = (current_bar_time != last_bar_time);
const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
if(!in_pos && !new_bar)
return;
if(!UpdateRSI())
return;
if(in_pos && UseReversalEscape)
TryReversalEscape();
if(!new_bar)
return;
last_bar_time = current_bar_time;
ResyncPositionFromMarket();
CheckExistingPosition();
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
CheckEntrySignals();
}
//+------------------------------------------------------------------+
//| Update RSI values |
//+------------------------------------------------------------------+
bool UpdateRSI()
{
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
{
return false;
}
rsi_current = rsi_buffer[0]; // Current bar
rsi_prev = rsi_buffer[1]; // Previous bar
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
return true;
}
//+------------------------------------------------------------------+
//| Wilder ATR in price units (signal timeframe) |
//+------------------------------------------------------------------+
double ATRPriceOnTF(const int period)
{
if(period < 1)
return 0.0;
MqlRates rates[];
const int need = period + 2;
if(CopyRates(_Symbol, TimeFrame, 0, need, rates) < need)
return 0.0;
ArraySetAsSeries(rates, true);
double sum = 0.0;
for(int i = 1; i <= period; i++)
{
const double hl = rates[i].high - rates[i].low;
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
sum += MathMax(hl, MathMax(hc, lc));
}
return sum / (double)period;
}
//+------------------------------------------------------------------+
//| Independent adverse signs (need ReversalSignsRequired to exit) |
//+------------------------------------------------------------------+
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr)
{
if(atr <= 0.0)
return 0;
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
int signs = 0;
if(ptype == POSITION_TYPE_BUY)
{
if(entry - bid >= ReversalAdverseAtrMult * atr)
signs++;
if(rsi_prev - rsi_current >= ReversalRsiVelocity)
signs++;
}
else if(ptype == POSITION_TYPE_SELL)
{
if(ask - entry >= ReversalAdverseAtrMult * atr)
signs++;
if(rsi_current - rsi_prev >= ReversalRsiVelocity)
signs++;
}
else
return 0;
MqlRates r[];
if(CopyRates(_Symbol, TimeFrame, 0, 4, r) >= 4)
{
ArraySetAsSeries(r, true);
const double body = MathAbs(r[1].close - r[1].open);
if(body >= ReversalBodyAtrMult * atr)
{
if(ptype == POSITION_TYPE_BUY && r[1].close < r[1].open)
signs++;
else if(ptype == POSITION_TYPE_SELL && r[1].close > r[1].open)
signs++;
}
if(ptype == POSITION_TYPE_BUY)
{
if(r[1].close < r[2].close && r[2].close < r[3].close)
signs++;
}
else
{
if(r[1].close > r[2].close && r[2].close > r[3].close)
signs++;
}
}
return signs;
}
//+------------------------------------------------------------------+
//| Cut losers fast on violent reversals (evaluated every tick) |
//+------------------------------------------------------------------+
void TryReversalEscape()
{
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
return;
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
const double atr = ATRPriceOnTF(ReversalATRPeriod);
if(atr <= 0.0)
return;
const int n = CountReversalEscapeSigns(ptype, atr);
if(n < ReversalSignsRequired)
return;
ClosePosition();
Print("RSIScalpingXAUUSD: reversal escape signs=", n, " need=", ReversalSignsRequired,
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
}
void ResyncPositionFromMarket()
{
if(position_open)
return;
ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
if(t == 0 || !PositionSelectByTicket(t))
return;
position_ticket = (int)t;
position_open = true;
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
}
//+------------------------------------------------------------------+
//| Check existing position for exit conditions |
//+------------------------------------------------------------------+
void CheckExistingPosition()
{
if(!position_open)
{
return;
}
// Check if position still exists with correct magic number
if(!PositionSelectByTicketAndMagic(position_ticket, MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
// On each new bar: last completed RSI vs the bar before — exit if that step is adverse to the position
if(ExitOnAdverseRsiBarStep)
{
if(current_position_type == POSITION_TYPE_BUY && rsi_prev < rsi_two_bars_ago)
{
ClosePosition();
return;
}
if(current_position_type == POSITION_TYPE_SELL && rsi_prev > rsi_two_bars_ago)
{
ClosePosition();
return;
}
}
// Exit conditions based on RSI target
if(current_position_type == POSITION_TYPE_BUY)
{
// Check if RSI is against the position (below oversold)
if(rsi_current < RSI_Oversold)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
{
bars_against_count++;
}
// Close position if RSI has been against for Y bars
if(bars_against_count >= BarsToWait)
{
ClosePosition();
return;
}
}
else
{
// RSI is no longer against the position, reset counter
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
// Exit long position when RSI reaches buy target
if(rsi_current >= RSI_Target_Buy)
{
ClosePosition();
}
}
}
else if(current_position_type == POSITION_TYPE_SELL)
{
// Check if RSI is against the position (above overbought)
if(rsi_current > RSI_Overbought)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
{
bars_against_count++;
}
// Close position if RSI has been against for Y bars
if(bars_against_count >= BarsToWait)
{
ClosePosition();
return;
}
}
else
{
// RSI is no longer against the position, reset counter
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
// Exit short position when RSI reaches sell target
if(rsi_current <= RSI_Target_Sell)
{
ClosePosition();
}
}
}
}
//+------------------------------------------------------------------+
//| Check for entry signals |
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
const double upSlope1 = rsi_prev - rsi_two_bars_ago; // older->prev
const double upSlope2 = rsi_current - rsi_prev; // prev->current
const double dnSlope1 = rsi_two_bars_ago - rsi_prev; // older->prev
const double dnSlope2 = rsi_prev - rsi_current; // prev->current
const bool buySlopeOk = (!UseEntrySlopeFilter) || (upSlope1 >= EntryMinSlopePerBar && upSlope2 >= EntryMinSlopePerBar);
const bool sellSlopeOk = (!UseEntrySlopeFilter) || (dnSlope1 >= EntryMinSlopePerBar && dnSlope2 >= EntryMinSlopePerBar);
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold && buySlopeOk)
{
OpenBuyPosition();
}
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought && sellSlopeOk)
{
OpenSellPosition();
}
}
//+------------------------------------------------------------------+
//| Open buy position |
//+------------------------------------------------------------------+
void OpenBuyPosition()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
{
position_ticket = trade.ResultOrder();
position_open = true;
current_position_type = POSITION_TYPE_BUY;
}
}
//+------------------------------------------------------------------+
//| Open sell position |
//+------------------------------------------------------------------+
void OpenSellPosition()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
{
position_ticket = trade.ResultOrder();
position_open = true;
current_position_type = POSITION_TYPE_SELL;
}
}
//+------------------------------------------------------------------+
//| Close current position |
//+------------------------------------------------------------------+
void ClosePosition()
{
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
Print("RSIScalpingXAUUSD: close failed (will retry on next bar). retcode=",
trade.ResultRetcode(), " lastError=", GetLastError());
}
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

@@ -1,45 +0,0 @@
; RSIFollowReverseEMACross (RSIMidPointHijackBTCUSD\main.mq5) — optimization preset
; Strategy Tester → Inputs → Load
; Format: Name=value||start||step||stop||Y|N
;
; Timeframe: leave N (ENUM not a linear range). Set manually or duplicate preset per TF.
; General Settings
InpTimeframe=16385||16385||0||16385||N
InpLotSize=0.02||0.02||0.001000||0.100000||N
InpMagicNumberRSIFollow=1001||1001||1||10010||N
InpMagicNumberRSIReverse=1002||1002||1||10020||N
InpMagicNumberEMACross=1003||1003||1||10030||N
; Strategy Switches
InpEnableRSIFollow=true||false||0||true||Y
InpEnableRSIReverse=true||false||0||true||Y
InpEnableEMACross=true||false||0||true||Y
InpEnableStrategyLock=false||false||0||true||Y
InpLockProfitThreshold=0.0||0.0||5.0||200.0||Y
InpCloseOppositeTrades=false||false||0||true||Y
; RSI Follow Strategy
InpRSIPeriod=32||14||2||48||Y
InpRSIOverbought=78||65||2||88||Y
InpRSIOversold=46||20||2||50||Y
InpRSIExitLevel=44||35||1||55||Y
InpRSIFollowStartHour=23||20||1||23||Y
InpRSIFollowEndHour=8||4||1||12||Y
InpRSIFollowCloseOutsideHours=false||false||0||true||Y
; RSI Reverse Strategy
InpRSIReversePeriod=59||28||3||80||Y
InpRSIReverseOverbought=51||48||1||78||Y
InpRSIReverseOversold=49||20||2||55||Y
InpRSIReverseCrossLevel=53||45||1||60||Y
InpRSIReverseExitLevel=48||35||1||55||Y
InpRSIReverseStartHour=7||0||1||12||Y
InpRSIReverseEndHour=13||10||1||18||Y
InpRSIReverseCloseOutsideHours=false||false||0||true||Y
InpRSIReverseCooldownBars=15||0||3||30||Y
InpRSIReverseCooldownOnLoss=true||false||0||true||Y
; EMA Cross Strategy
InpEMAPeriod=120||60||10||200||Y
InpEMACrossStartHour=8||0||1||12||Y
InpEMACrossEndHour=14||12||1||20||Y
InpEMACrossCloseOutsideHours=true||false||0||true||Y
InpUseEMADistanceEntry=true||false||0||true||Y
InpEMADistancePips=160.0||40.0||20.0||400.0||Y
InpEMADistancePeriod=26||10||2||40||Y
-604
View File
@@ -1,604 +0,0 @@
//+------------------------------------------------------------------+
//| RSIFollowReverseEMACrossOver.mq5 |
//| Copyright 2024, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include "../_united/MagicNumberHelpers.mqh"
// Input Parameters
input group "General Settings"
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; // Trading Timeframe
input double InpLotSize = 0.02; // Lot Size
input int InpMagicNumberRSIFollow = 1001; // Magic Number RSI Follow
input int InpMagicNumberRSIReverse = 1002;// Magic Number RSI Reverse
input int InpMagicNumberEMACross = 1003; // Magic Number EMA Cross
input group "Strategy Switches"
input bool InpEnableRSIFollow = true; // Enable RSI Follow Strategy
input bool InpEnableRSIReverse = true; // Enable RSI Reverse Strategy
input bool InpEnableEMACross = true; // Enable EMA Cross Strategy
input bool InpEnableStrategyLock = false; // Enable Strategy Lock
input double InpLockProfitThreshold = 0.0; // Lock Profit Threshold (pips)
input bool InpCloseOppositeTrades = false; // Close Opposite Trades When Profiting
input group "RSI Follow Strategy"
input int InpRSIPeriod = 32; // RSI Period
input int InpRSIOverbought = 78; // RSI Overbought Level
input int InpRSIOversold = 46; // RSI Oversold Level
input int InpRSIExitLevel = 44; // RSI Exit Level
input int InpRSIFollowStartHour = 23; // RSI Follow Start Hour (0-23)
input int InpRSIFollowEndHour = 8; // RSI Follow End Hour (0-23)
input bool InpRSIFollowCloseOutsideHours = false; // Close trades outside trading hours
input group "RSI Reverse Strategy"
input int InpRSIReversePeriod = 59; // RSI Period
input int InpRSIReverseOverbought = 51; // RSI Overbought Level
input int InpRSIReverseOversold = 49; // RSI Oversold Level
input int InpRSIReverseCrossLevel = 53; // RSI Cross Level
input int InpRSIReverseExitLevel = 48; // RSI Exit Level
input int InpRSIReverseStartHour = 7; // RSI Reverse Start Hour (0-23)
input int InpRSIReverseEndHour = 13; // RSI Reverse End Hour (0-23)
input bool InpRSIReverseCloseOutsideHours = false; // Close trades outside trading hours
input int InpRSIReverseCooldownBars = 15; // RSI Reverse Cooldown (bars)
input bool InpRSIReverseCooldownOnLoss = true; // Apply cooldown only on loss
input group "EMA Cross Strategy"
input int InpEMAPeriod = 120; // EMA Period
input int InpEMACrossStartHour = 8; // EMA Cross Start Hour (0-23)
input int InpEMACrossEndHour = 14; // EMA Cross End Hour (0-23)
input bool InpEMACrossCloseOutsideHours = true; // Close trades outside trading hours
input bool InpUseEMADistanceEntry = true; // Use EMA Distance Entry
input double InpEMADistancePips = 160.0; // EMA Distance Threshold (pips)
input int InpEMADistancePeriod = 26; // EMA Distance Period (bars)
// Global Variables
int rsiHandle;
int rsiReverseHandle;
int emaHandle;
bool rsiOverbought = false;
bool rsiOversold = false;
bool rsiReverseOverbought = false;
bool rsiReverseOversold = false;
CTrade trade;
CPositionInfo positionInfo;
bool emaCrossBuySignal = false;
bool emaCrossSellSignal = false;
int emaCrossSignalBar = 0;
datetime lastBarTime = 0;
datetime rsiReverseLastCloseTime = 0;
bool rsiReverseInCooldown = false;
double lastBarRSI = 0; // Store last bar's RSI value
double lastBarRSIReverse = 0; // Store last bar's RSI Reverse value
double lastBarEMA = 0; // Store last bar's EMA value
double lastBarClose = 0; // Store last bar's close value
double lastBarEMAPrev = 0; // Store previous bar's EMA value
double lastBarClosePrev = 0; // Store previous bar's close value
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Initialize indicators
rsiHandle = iRSI(_Symbol, InpTimeframe, InpRSIPeriod, PRICE_CLOSE);
rsiReverseHandle = iRSI(_Symbol, InpTimeframe, InpRSIReversePeriod, PRICE_CLOSE);
emaHandle = iMA(_Symbol, InpTimeframe, InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(rsiHandle == INVALID_HANDLE || rsiReverseHandle == INVALID_HANDLE || emaHandle == INVALID_HANDLE)
{
Print("Error creating indicators");
return INIT_FAILED;
}
// Initialize trade settings
trade.SetExpertMagicNumber(InpMagicNumberRSIFollow);
trade.SetMarginMode();
trade.SetTypeFillingBySymbol(_Symbol);
trade.SetDeviationInPoints(10);
// Initialize last bar time
datetime time[];
if(CopyTime(_Symbol, InpTimeframe, 0, 1, time) > 0)
{
lastBarTime = time[0];
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Check if new bar has formed |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime time[];
if(CopyTime(_Symbol, InpTimeframe, 0, 1, time) > 0)
{
if(time[0] != lastBarTime)
{
lastBarTime = time[0];
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Release indicator handles
IndicatorRelease(rsiHandle);
IndicatorRelease(rsiReverseHandle);
IndicatorRelease(emaHandle);
}
//+------------------------------------------------------------------+
//| Check if current time is within trading hours |
//+------------------------------------------------------------------+
bool IsWithinTradingHours(int startHour, int endHour)
{
MqlDateTime currentTime;
TimeToStruct(TimeCurrent(), currentTime);
if(startHour <= endHour)
{
return (currentTime.hour >= startHour && currentTime.hour < endHour);
}
else
{
return (currentTime.hour >= startHour || currentTime.hour < endHour);
}
}
//+------------------------------------------------------------------+
//| Check if position exists for given magic number AND symbol |
//+------------------------------------------------------------------+
bool HasPosition(int magic)
{
// Use helper function that verifies BOTH symbol AND magic number for THIS EA
return PositionExistsByMagic(_Symbol, magic);
}
//+------------------------------------------------------------------+
//| Check if any strategy has profitable position |
//+------------------------------------------------------------------+
bool HasProfitablePosition(int excludeMagic)
{
bool hasProfitable = false;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(positionInfo.SelectByIndex(i))
{
if(positionInfo.Magic() != excludeMagic)
{
double profit = positionInfo.Profit();
if(profit > InpLockProfitThreshold * _Point)
{
hasProfitable = true;
// If enabled, close opposite trades
if(InpCloseOppositeTrades)
{
// Check if this is an opposite trade to the excluded magic number
if((excludeMagic == InpMagicNumberRSIFollow && positionInfo.Magic() == InpMagicNumberRSIReverse) ||
(excludeMagic == InpMagicNumberRSIReverse && positionInfo.Magic() == InpMagicNumberRSIFollow) ||
(excludeMagic == InpMagicNumberEMACross && (positionInfo.Magic() == InpMagicNumberRSIReverse || positionInfo.Magic() == InpMagicNumberRSIFollow)) ||
((excludeMagic == InpMagicNumberRSIFollow || excludeMagic == InpMagicNumberRSIReverse) && positionInfo.Magic() == InpMagicNumberEMACross))
{
ClosePosition(positionInfo.Magic());
}
}
}
}
}
}
return hasProfitable;
}
//+------------------------------------------------------------------+
//| Check for RSI Follow Strategy signals |
//+------------------------------------------------------------------+
void CheckRSIFollowStrategy()
{
// Check if within trading hours
if(!IsWithinTradingHours(InpRSIFollowStartHour, InpRSIFollowEndHour))
{
if(InpRSIFollowCloseOutsideHours)
{
if(HasPosition(InpMagicNumberRSIFollow))
{
ClosePosition(InpMagicNumberRSIFollow);
}
}
return;
}
// Check strategy lock
if(InpEnableStrategyLock && HasProfitablePosition(InpMagicNumberRSIFollow))
return;
// Use lastBarRSI instead of copying buffer
if(lastBarRSI > InpRSIOverbought)
rsiOverbought = true;
else if(lastBarRSI < InpRSIOversold)
rsiOversold = true;
// Check for entry signals
if(rsiOverbought && lastBarRSI < InpRSIExitLevel)
{
// Sell signal
if(!HasPosition(InpMagicNumberRSIFollow))
{
trade.SetExpertMagicNumber(InpMagicNumberRSIFollow);
trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "RSI Follow");
}
rsiOverbought = false;
}
else if(rsiOversold && lastBarRSI > InpRSIExitLevel)
{
// Buy signal
if(!HasPosition(InpMagicNumberRSIFollow))
{
trade.SetExpertMagicNumber(InpMagicNumberRSIFollow);
trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "RSI Follow");
}
rsiOversold = false;
}
}
//+------------------------------------------------------------------+
//| Check if RSI Reverse is in cooldown |
//+------------------------------------------------------------------+
bool IsRSIReverseInCooldown()
{
if(InpRSIReverseCooldownBars <= 0)
return false;
if(!rsiReverseInCooldown)
return false;
datetime time[];
if(CopyTime(_Symbol, InpTimeframe, 0, 1, time) > 0)
{
datetime currentBarTime = time[0];
datetime cooldownEndTime = rsiReverseLastCloseTime + InpRSIReverseCooldownBars * PeriodSeconds(InpTimeframe);
if(currentBarTime >= cooldownEndTime)
{
rsiReverseInCooldown = false;
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Check for RSI Reverse Strategy signals |
//+------------------------------------------------------------------+
void CheckRSIReverseStrategy()
{
// Check if within trading hours
if(!IsWithinTradingHours(InpRSIReverseStartHour, InpRSIReverseEndHour))
{
if(InpRSIReverseCloseOutsideHours)
{
if(HasPosition(InpMagicNumberRSIReverse))
{
ClosePosition(InpMagicNumberRSIReverse);
}
}
return;
}
// Check strategy lock
if(InpEnableStrategyLock && HasProfitablePosition(InpMagicNumberRSIReverse))
return;
// Check cooldown
if(IsRSIReverseInCooldown())
return;
// Use lastBarRSIReverse instead of copying buffer
if(lastBarRSIReverse > InpRSIReverseOverbought)
rsiReverseOverbought = true;
else if(lastBarRSIReverse < InpRSIReverseOversold)
rsiReverseOversold = true;
// Check for entry signals
if(rsiReverseOverbought && lastBarRSIReverse < InpRSIReverseCrossLevel)
{
// Sell signal
if(!HasPosition(InpMagicNumberRSIReverse))
{
trade.SetExpertMagicNumber(InpMagicNumberRSIReverse);
trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "RSI Reverse");
}
rsiReverseOverbought = false;
}
else if(rsiReverseOversold && lastBarRSIReverse > InpRSIReverseCrossLevel)
{
// Buy signal
if(!HasPosition(InpMagicNumberRSIReverse))
{
trade.SetExpertMagicNumber(InpMagicNumberRSIReverse);
trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "RSI Reverse");
}
rsiReverseOversold = false;
}
}
//+------------------------------------------------------------------+
//| Check for EMA Cross Strategy signals |
//+------------------------------------------------------------------+
void CheckEMACrossStrategy()
{
// Check if within trading hours
if(!IsWithinTradingHours(InpEMACrossStartHour, InpEMACrossEndHour))
{
if(InpEMACrossCloseOutsideHours)
{
if(HasPosition(InpMagicNumberEMACross))
{
ClosePosition(InpMagicNumberEMACross);
}
}
return;
}
// Check strategy lock
if(InpEnableStrategyLock && HasProfitablePosition(InpMagicNumberEMACross))
return;
// Check for cross signals using stored values
if(lastBarEMAPrev < lastBarClosePrev && lastBarEMA > lastBarClose)
{
// Buy cross signal
emaCrossBuySignal = true;
emaCrossSellSignal = false;
emaCrossSignalBar = 0;
}
else if(lastBarEMAPrev > lastBarClosePrev && lastBarEMA < lastBarClose)
{
// Sell cross signal
emaCrossSellSignal = true;
emaCrossBuySignal = false;
emaCrossSignalBar = 0;
}
// Check for distance entry conditions
if(InpUseEMADistanceEntry)
{
if(emaCrossBuySignal)
{
// Check if price has moved above EMA by the required distance for the required period
bool distanceConditionMet = true;
double emaHistory[], closeHistory[];
ArraySetAsSeries(emaHistory, true);
ArraySetAsSeries(closeHistory, true);
if(CopyBuffer(emaHandle, 0, 0, InpEMADistancePeriod, emaHistory) > 0 &&
CopyClose(_Symbol, InpTimeframe, 0, InpEMADistancePeriod, closeHistory) > 0)
{
for(int i = 0; i < InpEMADistancePeriod; i++)
{
double distance = (closeHistory[i] - emaHistory[i]) / _Point;
if(distance < InpEMADistancePips)
{
distanceConditionMet = false;
break;
}
}
if(distanceConditionMet && !HasPosition(InpMagicNumberEMACross))
{
trade.SetExpertMagicNumber(InpMagicNumberEMACross);
trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "EMA Cross Distance");
emaCrossBuySignal = false;
}
}
}
else if(emaCrossSellSignal)
{
// Check if price has moved below EMA by the required distance for the required period
bool distanceConditionMet = true;
double emaHistory[], closeHistory[];
ArraySetAsSeries(emaHistory, true);
ArraySetAsSeries(closeHistory, true);
if(CopyBuffer(emaHandle, 0, 0, InpEMADistancePeriod, emaHistory) > 0 &&
CopyClose(_Symbol, InpTimeframe, 0, InpEMADistancePeriod, closeHistory) > 0)
{
for(int i = 0; i < InpEMADistancePeriod; i++)
{
double distance = (emaHistory[i] - closeHistory[i]) / _Point;
if(distance < InpEMADistancePips)
{
distanceConditionMet = false;
break;
}
}
if(distanceConditionMet && !HasPosition(InpMagicNumberEMACross))
{
trade.SetExpertMagicNumber(InpMagicNumberEMACross);
trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "EMA Cross Distance");
emaCrossSellSignal = false;
}
}
}
}
else
{
// Original cross entry logic using stored values
if(lastBarEMAPrev < lastBarClosePrev && lastBarEMA > lastBarClose)
{
// Buy signal
if(!HasPosition(InpMagicNumberEMACross))
{
trade.SetExpertMagicNumber(InpMagicNumberEMACross);
trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "EMA Cross");
}
}
else if(lastBarEMAPrev > lastBarClosePrev && lastBarEMA < lastBarClose)
{
// Sell signal
if(!HasPosition(InpMagicNumberEMACross))
{
trade.SetExpertMagicNumber(InpMagicNumberEMACross);
trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "EMA Cross");
}
}
}
// Increment signal bar counter
if(emaCrossBuySignal || emaCrossSellSignal)
{
emaCrossSignalBar++;
// Reset signals if they're too old (optional, can be removed if not needed)
if(emaCrossSignalBar > InpEMADistancePeriod * 2)
{
emaCrossBuySignal = false;
emaCrossSellSignal = false;
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// Only process on new bar
if(!IsNewBar())
return;
// Get indicator values for the new bar
double rsi[], rsiReverse[], ema[], close[];
ArraySetAsSeries(rsi, true);
ArraySetAsSeries(rsiReverse, true);
ArraySetAsSeries(ema, true);
ArraySetAsSeries(close, true);
// Store previous values
lastBarEMAPrev = lastBarEMA;
lastBarClosePrev = lastBarClose;
// Get new values
if(CopyBuffer(rsiHandle, 0, 0, 1, rsi) > 0)
lastBarRSI = rsi[0];
if(CopyBuffer(rsiReverseHandle, 0, 0, 1, rsiReverse) > 0)
lastBarRSIReverse = rsiReverse[0];
if(CopyBuffer(emaHandle, 0, 0, 1, ema) > 0)
lastBarEMA = ema[0];
if(CopyClose(_Symbol, InpTimeframe, 0, 1, close) > 0)
lastBarClose = close[0];
// Check for new signals
if(InpEnableRSIFollow)
CheckRSIFollowStrategy();
if(InpEnableRSIReverse)
CheckRSIReverseStrategy();
if(InpEnableEMACross)
CheckEMACrossStrategy();
// Check for exit conditions
CheckExitConditions();
}
//+------------------------------------------------------------------+
//| Check exit conditions for all strategies |
//+------------------------------------------------------------------+
void CheckExitConditions()
{
if(InpEnableRSIFollow)
{
// Check RSI Follow exit conditions
if(HasPosition(InpMagicNumberRSIFollow))
{
if((positionInfo.PositionType() == POSITION_TYPE_BUY && lastBarRSI < InpRSIExitLevel) ||
(positionInfo.PositionType() == POSITION_TYPE_SELL && lastBarRSI > InpRSIExitLevel))
{
ClosePosition(InpMagicNumberRSIFollow);
}
}
}
if(InpEnableRSIReverse)
{
// Check RSI Reverse exit conditions
if(HasPosition(InpMagicNumberRSIReverse))
{
if((positionInfo.PositionType() == POSITION_TYPE_BUY && lastBarRSIReverse < InpRSIReverseExitLevel) ||
(positionInfo.PositionType() == POSITION_TYPE_SELL && lastBarRSIReverse > InpRSIReverseExitLevel))
{
ClosePosition(InpMagicNumberRSIReverse);
}
}
}
if(InpEnableEMACross)
{
// Check EMA Cross exit conditions using stored values
if(HasPosition(InpMagicNumberEMACross))
{
if((positionInfo.PositionType() == POSITION_TYPE_BUY && lastBarEMA > lastBarClose) ||
(positionInfo.PositionType() == POSITION_TYPE_SELL && lastBarEMA < lastBarClose))
{
ClosePosition(InpMagicNumberEMACross);
}
}
}
}
//+------------------------------------------------------------------+
//| Close position by magic number |
//+------------------------------------------------------------------+
void ClosePosition(int magic)
{
// Close position using helper that verifies symbol AND magic number for THIS EA
// First check if position exists for this EA on this symbol
if(!PositionExistsByMagic(_Symbol, magic))
{
return; // No position for this EA on this symbol
}
// Get the position ticket for this EA on this symbol
ulong ticket = GetPositionTicketByMagic(_Symbol, magic);
if(ticket == 0)
{
return; // No valid ticket found
}
// Check if this is RSI Reverse position and update cooldown
if(magic == InpMagicNumberRSIReverse)
{
if(PositionSelectByTicketSymbolAndMagic(ticket, _Symbol, magic))
{
datetime time[];
if(CopyTime(_Symbol, InpTimeframe, 0, 1, time) > 0)
{
rsiReverseLastCloseTime = time[0];
// Only enter cooldown if it's a loss or if cooldown on loss is disabled
double profit = PositionGetDouble(POSITION_PROFIT);
if(!InpRSIReverseCooldownOnLoss || profit < 0)
{
rsiReverseInCooldown = true;
}
}
}
}
// Close the position using helper function
ClosePositionByMagic(trade, _Symbol, magic);
}
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,69 @@
//+------------------------------------------------------------------+
//| MagicNumberHelpers.mqh |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#include <Trade\Trade.mqh>
//+------------------------------------------------------------------+
bool PositionSelectByMagic(string symbol, ulong magic_number)
{
if(!PositionSelect(symbol))
return false;
if(PositionGetInteger(POSITION_MAGIC) != magic_number)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetTicket(i) > 0)
{
if(PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number)
{
return true;
}
}
}
return false;
}
return true;
}
//+------------------------------------------------------------------+
bool PositionSelectByTicketSymbolAndMagic(ulong ticket, string symbol, ulong magic_number)
{
if(!PositionSelectByTicket(ticket))
return false;
return (PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number);
}
//+------------------------------------------------------------------+
bool PositionExistsByMagic(string symbol, ulong magic_number)
{
return PositionSelectByMagic(symbol, magic_number);
}
//+------------------------------------------------------------------+
bool ClosePositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket > 0)
{
if(PositionGetString(POSITION_SYMBOL) == symbol &&
PositionGetInteger(POSITION_MAGIC) == magic_number)
{
return trade_obj.PositionClose(ticket);
}
}
}
return false;
}
@@ -0,0 +1,28 @@
; saved on 2026.02.07
; Genetic Algorithm Optimization Parameters for RSIScalpingNVDA
; Recommended ranges for profitable parameter discovery
;
; Format: Parameter=Start||Step||Min||Max||Optimize(Y/N)
;
; NOTE: Current values show RSI_Overbought=19 and RSI_Oversold=50 which are unusual.
; This config uses STANDARD RSI ranges (60-85 overbought, 15-40 oversold).
; If your current values are intentional, use the alternative ranges in OPTIMIZATION_GUIDE.md
;
; === PHASE 1: CORE RSI PARAMETERS (Primary Optimization) ===
RSI_Period=14||1||7||21||Y
RSI_Overbought=70.0||2.0||60.0||85.0||Y
RSI_Oversold=30.0||2.0||15.0||40.0||Y
RSI_Target_Buy=75.0||2.0||65.0||90.0||Y
RSI_Target_Sell=25.0||2.0||10.0||35.0||Y
; === PHASE 2: RISK MANAGEMENT (Secondary Optimization) ===
BarsToWait=2||1||1||8||Y
TimeFrame=16387||0||16385||16390||Y
; === PHASE 3: POSITION SIZING (Optimize with caution) ===
LotSize=50.0||5.0||10.0||100.0||Y
; === FIXED PARAMETERS (Do Not Optimize) ===
RSI_Applied_Price=1||0||1||1||N
MagicNumber=12345||0||12345||12345||N
Slippage=3||0||3||3||N
@@ -0,0 +1,24 @@
; saved on 2026.02.07
; Alternative Genetic Algorithm Optimization - Respects Current Unusual RSI Values
; Use this if RSI_Overbought=19 and RSI_Oversold=50 are intentional
;
; Format: Parameter=Start||Step||Min||Max||Optimize(Y/N)
;
; === PHASE 1: CORE RSI PARAMETERS ===
RSI_Period=14||1||7||21||Y
RSI_Overbought=19.0||1.0||15.0||30.0||Y
RSI_Oversold=50.0||2.0||40.0||60.0||Y
RSI_Target_Buy=71.0||2.0||65.0||80.0||Y
RSI_Target_Sell=70.0||2.0||60.0||75.0||Y
; === PHASE 2: RISK MANAGEMENT ===
BarsToWait=1||1||1||8||Y
TimeFrame=16387||0||16385||16390||Y
; === PHASE 3: POSITION SIZING ===
LotSize=50.0||5.0||10.0||100.0||Y
; === FIXED PARAMETERS ===
RSI_Applied_Price=1||0||1||1||N
MagicNumber=12345||0||12345||12345||N
Slippage=3||0||3||3||N
@@ -0,0 +1,134 @@
# Genetic Algorithm Optimization Guide for RSIScalpingNVDA
## Recommended Optimization Strategy
### Phase 1: Core RSI Parameters (Primary Focus)
These parameters directly control entry/exit signals and should be optimized first.
#### **RSI_Period** (Y - Optimize)
- **Current**: 14
- **Recommended Range**: 7-21
- **Step**: 1
- **Rationale**: Standard RSI periods. Shorter = more sensitive, longer = smoother signals
#### **RSI_Overbought** (Y - Optimize)
- **Current**: 19.0 (unusually low - verify if this is correct)
- **Standard Range**: 60.0-85.0
- **Step**: 2.0
- **Alternative Range** (if current is intentional): 15.0-30.0
- **Rationale**: Level where RSI indicates overbought condition for sell entries
#### **RSI_Oversold** (Y - Optimize)
- **Current**: 50.0 (unusually high - verify if this is correct)
- **Standard Range**: 15.0-40.0
- **Step**: 2.0
- **Alternative Range** (if current is intentional): 40.0-60.0
- **Rationale**: Level where RSI indicates oversold condition for buy entries
#### **RSI_Target_Buy** (Y - Optimize)
- **Current**: 71.0
- **Recommended Range**: 65.0-90.0
- **Step**: 2.0
- **Rationale**: Exit target for long positions. Must be > RSI_Oversold
#### **RSI_Target_Sell** (Y - Optimize)
- **Current**: 70.0
- **Recommended Range**: 10.0-35.0
- **Step**: 2.0
- **Rationale**: Exit target for short positions. Must be < RSI_Overbought
### Phase 2: Risk Management Parameters
#### **BarsToWait** (Y - Optimize)
- **Current**: 1
- **Recommended Range**: 1-8
- **Step**: 1
- **Rationale**: Bars to wait before closing when RSI goes against position. Higher = more patience
#### **TimeFrame** (Y - Optimize)
- **Current**: 16387 (M5)
- **Recommended**: Test M1, M5, M15, H1
- **Values**:
- M1 = 16385
- M5 = 16387
- M15 = 16388
- H1 = 16390
- **Rationale**: Different timeframes can significantly affect scalping performance
### Phase 3: Position Sizing (Optimize with Caution)
#### **LotSize** (Y - Optimize with Fixed Risk)
- **Current**: 50.0
- **Recommended Range**: 10.0-100.0
- **Step**: 5.0
- **Note**: Consider using fixed risk % instead of fixed lot size
- **Rationale**: Position sizing affects profitability but also risk
### Fixed Parameters (Do NOT Optimize)
#### **RSI_Applied_Price** (N)
- **Value**: 1 (PRICE_CLOSE)
- **Rationale**: Standard choice, changing may not improve results significantly
#### **MagicNumber** (N)
- **Value**: 12345
- **Rationale**: Identifier only, no impact on performance
#### **Slippage** (N)
- **Value**: 3
- **Rationale**: Broker-specific, should match your actual slippage
## Genetic Algorithm Settings
### Recommended GA Settings:
- **Optimization Criterion**: Balance (or Custom: Profit Factor * Total Net Profit)
- **Population Size**: 50-100
- **Mutation Probability**: 0.1-0.2
- **Crossover Probability**: 0.7-0.9
- **Optimization Passes**: 3-5
- **Forward Testing**: Always use out-of-sample data
### Optimization Phases:
1. **Broad Search** (First Pass):
- Optimize: RSI_Period, RSI_Overbought, RSI_Oversold, RSI_Target_Buy, RSI_Target_Sell
- Fix: BarsToWait=1, TimeFrame=M5, LotSize=50
2. **Refinement** (Second Pass):
- Use best results from Phase 1
- Optimize: BarsToWait, TimeFrame
- Narrow ranges around Phase 1 winners
3. **Fine-Tuning** (Third Pass):
- Optimize: LotSize (if needed)
- Very narrow ranges around Phase 2 winners
## Important Notes
⚠️ **Current Parameter Anomaly**:
- RSI_Overbought=19 and RSI_Oversold=50 are unusual
- Standard RSI ranges: Overbought 70-80, Oversold 20-30
- **Verify** if these are intentional or if there's a scaling issue
**Validation Checklist**:
- Ensure RSI_Target_Buy > RSI_Oversold
- Ensure RSI_Target_Sell < RSI_Overbought
- Test on sufficient historical data (at least 6-12 months)
- Use forward testing on unseen data
- Check for overfitting (too many parameters optimized)
## Example .set File Structure
```
RSI_Period=14||1||7||21||Y
RSI_Overbought=70.0||2.0||60.0||85.0||Y
RSI_Oversold=30.0||2.0||15.0||40.0||Y
RSI_Target_Buy=75.0||2.0||65.0||90.0||Y
RSI_Target_Sell=25.0||2.0||10.0||35.0||Y
BarsToWait=2||1||1||8||Y
TimeFrame=16387||0||16385||16390||Y
LotSize=50.0||5.0||10.0||100.0||Y
RSI_Applied_Price=1||0||1||1||N
MagicNumber=12345||0||12345||12345||N
Slippage=3||0||3||3||N
```
+61
View File
@@ -0,0 +1,61 @@
# RSIScalpingAdaptive XAUUSD — MT5 Strategy Tester
## 快速开始(MT5 原生回测)
1. 复制整个 `RSIScalpingAdaptive` 文件夹到 `MQL5\Experts\`
2. MetaEditor 编译 `main.mq5`
3. **或用脚本自动编译 + 启动 Tester**
```powershell
cd lab\EAs\RSIScalpingAdaptive
# 单次回测 2004→现在
python run_mt5_tester.py backtest --symbol XAUUSD --from 2004.01.01 --to 2026.01.01
# 遗传算法优化(MT5 Strategy Tester → Genetic
python run_mt5_tester.py optimize --symbol XAUUSD --from 2004.01.01 --to 2026.01.01
```
脚本会:编译 EA → 写入 `.ini` → 启动 `terminal64.exe /config:...` → 解析 HTML 报告。
## 手动在 MT5 里测
1. 策略测试器 → 专家:`RSIScalpingAdaptive.ex5`
2. 品种:**XAUUSD**,周期:**H1**
3. 日期:**2004.01.01** — **2026.01.01**
4. 模式:每个 tick 基于真实 tick / 1分钟 OHLC
5. Inputs → Load → `XAUUSD_Backtest.set`(固定参数)或 `XAUUSD_Genetic_Optimization.set`(遗传优化)
6. **EnableAdaptive 必须 = false**Tester 里 EA 直接用 Inputs,不做 walk-forward 网格)
## 当前 XAUUSD 参数(MT5 Demo 20042026 验证)
| 参数 | 值 | 说明 |
|------|-----|------|
| TimeFrame | H1 | |
| RSI_Overbought | **6** | 反转 RSI 带(低值=卖入场) |
| RSI_Oversold | **66** | 买入场 |
| RSI_Target_Buy | 98 | 多单止盈 |
| RSI_Target_Sell | 52 | 空单止盈 |
| BarsToWait | 12 | RSI 反向等待 K 线 |
| LotSize | 0.1 | |
| EnableAdaptive | false | Tester 固定参数 |
**MT5 回测结果(MetaQuotes Demo$10,000 初始):**
- 净利润 ≈ **$25,287**
- 盈利因子 **1.38**
- 夏普 **1.30**
- 交易 **1552**
## 文件
| 文件 | 用途 |
|------|------|
| `run_mt5_tester.py` | 启动 MT5 Strategy Tester |
| `XAUUSD_Backtest.set` | 固定参数回测 |
| `XAUUSD_Genetic_Optimization.set` | 遗传优化搜索范围 |
| `XAUUSD_Adaptive.set` | 实盘 adaptiveEnableAdaptive=true |
## 实盘 adaptive
挂 XAUUSD H1`EnableAdaptive=true`,每月自动用上月数据选参。
**Tester 里请关闭 adaptive**,否则每次 OnInit 会跑网格搜索,极慢且干扰优化。
@@ -0,0 +1,532 @@
//+------------------------------------------------------------------+
//| RSIScalpingAdaptiveOptimizer.mqh |
//| Walk-forward: backtest prior month, pick best params for next |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
struct RSIAdaptiveParams
{
ENUM_TIMEFRAMES timeframe;
int rsi_period;
double rsi_overbought;
double rsi_oversold;
double rsi_target_buy;
double rsi_target_sell;
int bars_to_wait;
bool IsValid() const
{
return (rsi_target_buy > rsi_oversold &&
rsi_target_sell < rsi_overbought &&
rsi_period >= 2 &&
bars_to_wait >= 1);
}
string ToString() const
{
return StringFormat(
"TF=%s RSI=%d OB=%.1f OS=%.1f TB=%.1f TS=%.1f Wait=%d",
EnumToString(timeframe),
rsi_period,
rsi_overbought,
rsi_oversold,
rsi_target_buy,
rsi_target_sell,
bars_to_wait
);
}
};
//+------------------------------------------------------------------+
struct RSIAdaptiveMetrics
{
double net_profit;
int total_trades;
double win_rate;
double profit_factor;
double sharpe;
double max_drawdown_pct;
double score;
};
//+------------------------------------------------------------------+
struct RSIAdaptiveSearchConfig
{
ENUM_TIMEFRAMES timeframe;
int rsi_period_min;
int rsi_period_max;
int rsi_period_step;
double rsi_overbought_min;
double rsi_overbought_max;
double rsi_overbought_step;
double rsi_oversold_min;
double rsi_oversold_max;
double rsi_oversold_step;
double rsi_target_buy_min;
double rsi_target_buy_max;
double rsi_target_buy_step;
double rsi_target_sell_min;
double rsi_target_sell_max;
double rsi_target_sell_step;
int bars_to_wait_min;
int bars_to_wait_max;
int bars_to_wait_step;
int min_trades;
double lot_size;
double initial_balance;
int slippage_points;
double weight_sharpe;
double weight_net_profit;
double weight_profit_factor;
double weight_max_dd;
int max_combinations;
};
//+------------------------------------------------------------------+
class CRSIAdaptiveOptimizer
{
private:
string m_symbol;
datetime m_opt_start;
datetime m_opt_end;
int m_combos_tested;
double FillBuy(const double mid, const double point, const double half_spread, const int slippage_pts) const
{
return mid + half_spread + slippage_pts * point;
}
double FillSell(const double mid, const double point, const double half_spread, const int slippage_pts) const
{
return mid - half_spread - slippage_pts * point;
}
double CalcTradeProfit(const ENUM_ORDER_TYPE order_type,
const double volume,
const double entry,
const double exit_px) const
{
double profit = 0.0;
if(!OrderCalcProfit(order_type, m_symbol, volume, entry, exit_px, profit))
return 0.0;
return profit;
}
int BarsPerYear(const ENUM_TIMEFRAMES tf) const
{
switch(tf)
{
case PERIOD_M1: return 252 * 24 * 60;
case PERIOD_M5: return 252 * 24 * 12;
case PERIOD_M15: return 252 * 24 * 4;
case PERIOD_M30: return 252 * 24 * 2;
case PERIOD_H1: return 252 * 24;
case PERIOD_H4: return 252 * 6;
case PERIOD_D1: return 252;
default: return 252 * 24;
}
}
double ComputeSharpe(const double &equity[], const int count, const ENUM_TIMEFRAMES tf) const
{
if(count < 12)
return 0.0;
double sum = 0.0;
double sum_sq = 0.0;
int n = 0;
for(int i = 1; i < count; i++)
{
if(equity[i - 1] <= 0.0)
continue;
double r = (equity[i] - equity[i - 1]) / equity[i - 1];
sum += r;
sum_sq += r * r;
n++;
}
if(n < 10)
return 0.0;
double mean = sum / n;
double var = sum_sq / n - mean * mean;
if(var <= 0.0)
return 0.0;
double std = MathSqrt(var);
double scale = MathSqrt((double)BarsPerYear(tf) / (double)n);
return mean / std * scale;
}
double ComputeScore(const RSIAdaptiveMetrics &m, const RSIAdaptiveSearchConfig &cfg) const
{
if(m.total_trades < cfg.min_trades || m.net_profit <= 0.0 || m.profit_factor < 1.05)
return -1.0e12;
double pf = MathMin(m.profit_factor, 4.0) / 4.0;
return m.sharpe * cfg.weight_sharpe
+ (m.net_profit / 2000.0) * cfg.weight_net_profit
+ pf * cfg.weight_profit_factor
- m.max_drawdown_pct * cfg.weight_max_dd;
}
bool BacktestParams(const RSIAdaptiveParams &params,
const RSIAdaptiveSearchConfig &cfg,
RSIAdaptiveMetrics &out) const
{
out.net_profit = 0.0;
out.total_trades = 0;
out.win_rate = 0.0;
out.profit_factor = 0.0;
out.sharpe = 0.0;
out.max_drawdown_pct = 0.0;
out.score = -1.0e12;
if(!params.IsValid())
return false;
int bt_rsi_handle = iRSI(m_symbol, params.timeframe, params.rsi_period, PRICE_CLOSE);
if(bt_rsi_handle == INVALID_HANDLE)
return false;
int end_shift = iBarShift(m_symbol, params.timeframe, m_opt_end, false);
int start_shift = iBarShift(m_symbol, params.timeframe, m_opt_start, false);
if(end_shift < 0)
end_shift = 0;
if(start_shift < 0)
{
IndicatorRelease(bt_rsi_handle);
return false;
}
int bars_count = start_shift - end_shift + 1;
if(bars_count < params.rsi_period + 5)
{
IndicatorRelease(bt_rsi_handle);
return false;
}
double rsi[];
double opens[];
datetime times[];
ArraySetAsSeries(rsi, false);
ArraySetAsSeries(opens, false);
ArraySetAsSeries(times, false);
// Copy from oldest bar (start_shift): buffer[0]=oldest, buffer[n-1]=newest
if(CopyBuffer(bt_rsi_handle, 0, start_shift, bars_count, rsi) < bars_count ||
CopyOpen(m_symbol, params.timeframe, start_shift, bars_count, opens) < bars_count ||
CopyTime(m_symbol, params.timeframe, start_shift, bars_count, times) < bars_count)
{
IndicatorRelease(bt_rsi_handle);
return false;
}
IndicatorRelease(bt_rsi_handle);
const double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT);
const long spread_pts = SymbolInfoInteger(m_symbol, SYMBOL_SPREAD);
const double half_spread = spread_pts * point / 2.0;
bool has_position = false;
ENUM_ORDER_TYPE pos_type = ORDER_TYPE_BUY;
double entry_px = 0.0;
bool rsi_against = false;
int bars_against = 0;
double balance = cfg.initial_balance;
double peak = balance;
double max_dd_pct = 0.0;
double gross_profit = 0.0;
double gross_loss = 0.0;
int wins = 0;
double equity[];
ArrayResize(equity, bars_count);
int equity_count = 0;
// Chronological loop: index 0 = oldest bar in window (matches Python run_backtest.py)
for(int i = params.rsi_period + 2; i < bars_count; i++)
{
const double sig = rsi[i - 1];
const double prev = rsi[i - 2];
const double two = rsi[i - 3];
const double mid = opens[i];
if(has_position)
{
if(pos_type == ORDER_TYPE_BUY)
{
if(sig < params.rsi_oversold)
{
if(!rsi_against)
{
rsi_against = true;
bars_against = 1;
}
else
bars_against++;
if(bars_against >= params.bars_to_wait)
{
const double exit_px = FillSell(mid, point, half_spread, cfg.slippage_points);
const double pnl = CalcTradeProfit(ORDER_TYPE_BUY, cfg.lot_size, entry_px, exit_px);
balance += pnl;
out.total_trades++;
if(pnl >= 0.0) { gross_profit += pnl; wins++; } else gross_loss += MathAbs(pnl);
has_position = false;
rsi_against = false;
bars_against = 0;
}
}
else
{
rsi_against = false;
bars_against = 0;
if(sig >= params.rsi_target_buy)
{
const double exit_px = FillSell(mid, point, half_spread, cfg.slippage_points);
const double pnl = CalcTradeProfit(ORDER_TYPE_BUY, cfg.lot_size, entry_px, exit_px);
balance += pnl;
out.total_trades++;
if(pnl >= 0.0) { gross_profit += pnl; wins++; } else gross_loss += MathAbs(pnl);
has_position = false;
}
}
}
else
{
if(sig > params.rsi_overbought)
{
if(!rsi_against)
{
rsi_against = true;
bars_against = 1;
}
else
bars_against++;
if(bars_against >= params.bars_to_wait)
{
const double exit_px = FillBuy(mid, point, half_spread, cfg.slippage_points);
const double pnl = CalcTradeProfit(ORDER_TYPE_SELL, cfg.lot_size, entry_px, exit_px);
balance += pnl;
out.total_trades++;
if(pnl >= 0.0) { gross_profit += pnl; wins++; } else gross_loss += MathAbs(pnl);
has_position = false;
rsi_against = false;
bars_against = 0;
}
}
else
{
rsi_against = false;
bars_against = 0;
if(sig <= params.rsi_target_sell)
{
const double exit_px = FillBuy(mid, point, half_spread, cfg.slippage_points);
const double pnl = CalcTradeProfit(ORDER_TYPE_SELL, cfg.lot_size, entry_px, exit_px);
balance += pnl;
out.total_trades++;
if(pnl >= 0.0) { gross_profit += pnl; wins++; } else gross_loss += MathAbs(pnl);
has_position = false;
}
}
}
}
if(!has_position)
{
if(two <= params.rsi_oversold && prev > params.rsi_oversold)
{
entry_px = FillBuy(mid, point, half_spread, cfg.slippage_points);
pos_type = ORDER_TYPE_BUY;
has_position = true;
rsi_against = false;
bars_against = 0;
}
else if(two >= params.rsi_overbought && prev < params.rsi_overbought)
{
entry_px = FillSell(mid, point, half_spread, cfg.slippage_points);
pos_type = ORDER_TYPE_SELL;
has_position = true;
rsi_against = false;
bars_against = 0;
}
}
double mark = balance;
if(has_position)
{
const double mark_mid = opens[i];
if(pos_type == ORDER_TYPE_BUY)
mark += CalcTradeProfit(ORDER_TYPE_BUY, cfg.lot_size, entry_px, FillSell(mark_mid, point, half_spread, 0));
else
mark += CalcTradeProfit(ORDER_TYPE_SELL, cfg.lot_size, entry_px, FillBuy(mark_mid, point, half_spread, 0));
}
if(equity_count < bars_count)
equity[equity_count++] = mark;
if(mark > peak)
peak = mark;
if(peak > 0.0)
{
const double dd = (peak - mark) / peak * 100.0;
if(dd > max_dd_pct)
max_dd_pct = dd;
}
}
if(has_position)
{
const double mid = opens[bars_count - 1];
if(pos_type == ORDER_TYPE_BUY)
{
const double exit_px = FillSell(mid, point, half_spread, cfg.slippage_points);
const double pnl = CalcTradeProfit(ORDER_TYPE_BUY, cfg.lot_size, entry_px, exit_px);
balance += pnl;
out.total_trades++;
if(pnl >= 0.0) { gross_profit += pnl; wins++; } else gross_loss += MathAbs(pnl);
}
else
{
const double exit_px = FillBuy(mid, point, half_spread, cfg.slippage_points);
const double pnl = CalcTradeProfit(ORDER_TYPE_SELL, cfg.lot_size, entry_px, exit_px);
balance += pnl;
out.total_trades++;
if(pnl >= 0.0) { gross_profit += pnl; wins++; } else gross_loss += MathAbs(pnl);
}
}
out.net_profit = balance - cfg.initial_balance;
out.max_drawdown_pct = max_dd_pct;
out.win_rate = (out.total_trades > 0) ? (100.0 * wins / out.total_trades) : 0.0;
out.profit_factor = (gross_loss > 0.0) ? (gross_profit / gross_loss) : (gross_profit > 0.0 ? 999.0 : 0.0);
out.sharpe = ComputeSharpe(equity, equity_count, params.timeframe);
out.score = ComputeScore(out, cfg);
return true;
}
public:
CRSIAdaptiveOptimizer() : m_combos_tested(0) {}
static void PreviousCalendarMonth(const datetime now, datetime &month_start, datetime &month_end)
{
MqlDateTime dt;
TimeToStruct(now, dt);
datetime this_month_start = StringToTime(StringFormat("%04d.%02d.01 00:00", dt.year, dt.mon));
month_end = this_month_start - 1;
TimeToStruct(month_end, dt);
month_start = StringToTime(StringFormat("%04d.%02d.01 00:00", dt.year, dt.mon));
}
static int MonthKey(const datetime t)
{
MqlDateTime dt;
TimeToStruct(t, dt);
return dt.year * 100 + dt.mon;
}
bool Optimize(const string symbol,
const datetime opt_start,
const datetime opt_end,
const RSIAdaptiveParams &fallback,
const RSIAdaptiveSearchConfig &cfg,
RSIAdaptiveParams &best_out,
RSIAdaptiveMetrics &best_metrics_out)
{
m_symbol = symbol;
m_opt_start = opt_start;
m_opt_end = opt_end;
m_combos_tested = 0;
best_out = fallback;
best_metrics_out.net_profit = 0.0;
best_metrics_out.total_trades = 0;
best_metrics_out.win_rate = 0.0;
best_metrics_out.profit_factor = 0.0;
best_metrics_out.sharpe = 0.0;
best_metrics_out.max_drawdown_pct = 0.0;
best_metrics_out.score = -1.0e12;
RSIAdaptiveMetrics fallback_metrics;
if(BacktestParams(fallback, cfg, fallback_metrics))
{
if(fallback_metrics.score > best_metrics_out.score)
{
best_out = fallback;
best_metrics_out = fallback_metrics;
}
m_combos_tested++;
}
bool stop_search = false;
for(int rp = cfg.rsi_period_min; rp <= cfg.rsi_period_max && !stop_search; rp += cfg.rsi_period_step)
{
for(double ob = cfg.rsi_overbought_min; ob <= cfg.rsi_overbought_max + 0.001 && !stop_search; ob += cfg.rsi_overbought_step)
{
for(double os = cfg.rsi_oversold_min; os <= cfg.rsi_oversold_max + 0.001 && !stop_search; os += cfg.rsi_oversold_step)
{
for(double tb = cfg.rsi_target_buy_min; tb <= cfg.rsi_target_buy_max + 0.001 && !stop_search; tb += cfg.rsi_target_buy_step)
{
for(double ts = cfg.rsi_target_sell_min; ts <= cfg.rsi_target_sell_max + 0.001 && !stop_search; ts += cfg.rsi_target_sell_step)
{
for(int bw = cfg.bars_to_wait_min; bw <= cfg.bars_to_wait_max && !stop_search; bw += cfg.bars_to_wait_step)
{
if(m_combos_tested >= cfg.max_combinations)
{
stop_search = true;
break;
}
RSIAdaptiveParams p;
p.timeframe = cfg.timeframe;
p.rsi_period = rp;
p.rsi_overbought = ob;
p.rsi_oversold = os;
p.rsi_target_buy = tb;
p.rsi_target_sell = ts;
p.bars_to_wait = bw;
if(!p.IsValid())
continue;
RSIAdaptiveMetrics m;
if(!BacktestParams(p, cfg, m))
continue;
m_combos_tested++;
if(m.score > best_metrics_out.score)
{
best_out = p;
best_metrics_out = m;
}
}
}
}
}
}
}
PrintFormat("[Adaptive] %s tested %d combos | window %s -> %s",
symbol,
m_combos_tested,
TimeToString(opt_start, TIME_DATE),
TimeToString(opt_end, TIME_DATE));
PrintFormat("[Adaptive] Best score=%.4f net=$%.2f sharpe=%.2f PF=%.2f trades=%d DD=%.2f%% | %s",
best_metrics_out.score,
best_metrics_out.net_profit,
best_metrics_out.sharpe,
best_metrics_out.profit_factor,
best_metrics_out.total_trades,
best_metrics_out.max_drawdown_pct,
best_out.ToString());
return (best_metrics_out.score > -1.0e11);
}
int CombosTested() const { return m_combos_tested; }
};
@@ -0,0 +1,6 @@
#ifndef RSI_SCALPING_SUPER_MAGIC_MQH
#define RSI_SCALPING_SUPER_MAGIC_MQH
#define RS_SUPER_MAGIC_BASE 941001
#endif
@@ -0,0 +1,60 @@
// RSIScalpingSuperParams.mqh — per-symbol H1 RSI scalping
// XAUUSD: MT5 genetic 2026-06-23. Forex: run MT5 Genetic per symbol (see SUPER_EA_README.md)
#ifndef RSI_SCALPING_SUPER_PARAMS_MQH
#define RSI_SCALPING_SUPER_PARAMS_MQH
#include "RSIScalpingSuperMagic.mqh"
#define RS_SUPER_SLOT_COUNT 9
struct RSSlotParams
{
int rsiPeriod;
double rsiOverbought;
double rsiOversold;
double rsiTargetBuy;
double rsiTargetSell;
int barsToWait;
double lotSize;
};
struct RSSlotConfig
{
string symbol;
int magic;
bool enabled;
RSSlotParams p;
};
const RSSlotConfig RS_SUPER_SLOTS[RS_SUPER_SLOT_COUNT] =
{
// EURUSD — pending MT5 genetic (disable until optimized)
{ "EURUSD", RS_SUPER_MAGIC_BASE + 1, false,
{ 14, 8.0, 72.0, 85.0, 18.0, 8, 0.10 } },
// GBPUSD — pending MT5 genetic
{ "GBPUSD", RS_SUPER_MAGIC_BASE + 2, false,
{ 12, 7.0, 70.0, 88.0, 22.0, 10, 0.10 } },
// USDJPY — pending MT5 genetic
{ "USDJPY", RS_SUPER_MAGIC_BASE + 3, false,
{ 16, 5.0, 76.0, 82.0, 28.0, 9, 0.10 } },
// AUDUSD — pending MT5 genetic
{ "AUDUSD", RS_SUPER_MAGIC_BASE + 4, false,
{ 15, 9.0, 68.0, 86.0, 20.0, 7, 0.10 } },
// USDCHF — pending MT5 genetic
{ "USDCHF", RS_SUPER_MAGIC_BASE + 5, false,
{ 13, 6.0, 74.0, 84.0, 26.0, 11, 0.10 } },
// USDCAD — pending MT5 genetic
{ "USDCAD", RS_SUPER_MAGIC_BASE + 6, false,
{ 14, 10.0, 66.0, 87.0, 16.0, 8, 0.10 } },
// NZDUSD — pending MT5 genetic
{ "NZDUSD", RS_SUPER_MAGIC_BASE + 7, false,
{ 11, 8.0, 71.0, 89.0, 19.0, 9, 0.10 } },
// EURJPY — pending MT5 genetic
{ "EURJPY", RS_SUPER_MAGIC_BASE + 8, false,
{ 18, 4.0, 77.0, 80.0, 30.0, 10, 0.10 } },
// XAUUSD — MT5 genetic profit=$28,071 PF=1.47 DD=5.3% (20042026)
{ "XAUUSD", RS_SUPER_MAGIC_BASE + 9, true,
{ 14, 19.0, 68.0, 89.0, 20.0, 12, 0.10 } },
};
#endif
@@ -0,0 +1,50 @@
# RSIScalpingSuper — 多品种 Super EA
9 个品种 H1 RSI Scalping 组合:**EURUSD GBPUSD USDJPY AUDUSD USDCHF USDCAD NZDUSD EURJPY XAUUSD**
每个品种独立 magic、独立 RSI 参数(MT5 遗传算法 20042026 优化)。
## MT5 组合回测
```powershell
cd lab\EAs\RSIScalpingAdaptive
# 编译 + 启动 MT5 Strategy Tester9 品种组合)
python run_mt5_tester.py backtest --expert super --symbol EURUSD --from 2004.01.01 --to 2026.01.01
```
手动测试:
1. 专家:`RSIScalpingAdaptive\SuperEA.ex5`(或 `RSIScalpingSuper.ex5`
2. 挂到 **EURUSD H1**
3. Inputs → Load → `SuperEA_portfolio.set`
4. 日期 2004.01.01 2026.01.01
## 逐品种 MT5 遗传优化(更新参数表)
```powershell
# 全部 9 品种依次跑 MT5 Genetic(约 30min/品种)
python run_mt5_cluster.py optimize --all-forex
# 或单个
python run_mt5_tester.py optimize --symbol EURUSD --from 2004.01.01 --to 2026.01.01
```
优化完成后自动生成 `RSIScalpingSuperParams.mqh`
## 文件
| 文件 | 说明 |
|------|------|
| `SuperEA.mq5` | 多品种 Super EA |
| `RSIScalpingSuperParams.mqh` | 每品种硬编码参数 |
| `SuperEA_portfolio.set` | Tester 输入 |
| `run_mt5_cluster.py` | 批量 MT5 遗传优化 |
| `run_mt5_tester.py` | 单 EA / Super EA Tester 启动器 |
## 已验证
| 品种 | 净利润 | PF | 回撤 | 参数来源 |
|------|--------|-----|------|----------|
| XAUUSD | $10,470 | 1.56 | 9.1% | MT5 genetic Pass 306 |
外汇品种需跑 `run_mt5_cluster.py optimize` 写入真实参数(不能共用 XAUUSD 参数)。
+331
View File
@@ -0,0 +1,331 @@
//+------------------------------------------------------------------+
//| RSIScalpingSuper.mq5 |
//| Multi-symbol RSI Scalping portfolio (H1, MT5-optimized params) |
//+------------------------------------------------------------------+
#property copyright "Frontline"
#property version "1.00"
#property description "RSI Scalping Super EA — EURUSD GBPUSD USDJPY AUDUSD USDCHF USDCAD NZDUSD EURJPY XAUUSD"
#include <Trade\Trade.mqh>
#include "MagicNumberHelpers.mqh"
#include "RSIScalpingSuperParams.mqh"
input group "=== Portfolio ==="
input double LotMultiplier = 1.0;
input bool ScaleLotsToDeposit = true;
input double ReferenceDeposit = 10000.0;
input int Slippage = 3;
input int MaxOpenPositions = 9;
input group "=== Slot toggles ==="
input bool Enable_EURUSD = true;
input bool Enable_GBPUSD = true;
input bool Enable_USDJPY = true;
input bool Enable_AUDUSD = true;
input bool Enable_USDCHF = true;
input bool Enable_USDCAD = true;
input bool Enable_NZDUSD = true;
input bool Enable_EURJPY = true;
input bool Enable_XAUUSD = true;
#define RS_TF PERIOD_H1
struct RSSymCtx
{
string name;
RSSlotParams p;
int magic;
bool enabled;
int rsiHandle;
datetime lastBar;
bool posOpen;
ulong posTicket;
ENUM_POSITION_TYPE posType;
bool rsiAgainst;
int barsAgainst;
};
CTrade g_trade;
RSSymCtx g_ctx[RS_SUPER_SLOT_COUNT];
int g_count = 0;
//+------------------------------------------------------------------+
bool SlotEnabled(const int idx)
{
switch(idx)
{
case 0: return Enable_EURUSD;
case 1: return Enable_GBPUSD;
case 2: return Enable_USDJPY;
case 3: return Enable_AUDUSD;
case 4: return Enable_USDCHF;
case 5: return Enable_USDCAD;
case 6: return Enable_NZDUSD;
case 7: return Enable_EURJPY;
case 8: return Enable_XAUUSD;
}
return true;
}
//+------------------------------------------------------------------+
double CalcLot(const string sym, const double baseLot)
{
double lot = baseLot * LotMultiplier;
if(ScaleLotsToDeposit && ReferenceDeposit > 0)
{
double bal = AccountInfoDouble(ACCOUNT_BALANCE);
lot *= bal / ReferenceDeposit;
}
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
double minL = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
double maxL = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
if(step > 0)
lot = MathFloor(lot / step) * step;
if(lot < minL) lot = minL;
if(lot > maxL) lot = maxL;
return lot;
}
//+------------------------------------------------------------------+
int CountOurPositions()
{
int n = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(PositionGetTicket(i) == 0) continue;
ulong mg = (ulong)PositionGetInteger(POSITION_MAGIC);
if(mg >= (ulong)RS_SUPER_MAGIC_BASE && mg < (ulong)(RS_SUPER_MAGIC_BASE + RS_SUPER_SLOT_COUNT + 1))
n++;
}
return n;
}
//+------------------------------------------------------------------+
bool UpdateRsi(RSSymCtx &c, double &cur, double &prev, double &two)
{
double buf[];
ArraySetAsSeries(buf, true);
if(CopyBuffer(c.rsiHandle, 0, 0, 3, buf) < 3)
return false;
cur = buf[0];
prev = buf[1];
two = buf[2];
return true;
}
//+------------------------------------------------------------------+
void SyncPosition(RSSymCtx &c)
{
if(!PositionExistsByMagic(c.name, c.magic))
{
c.posOpen = false;
c.posTicket = 0;
c.rsiAgainst = false;
c.barsAgainst = 0;
return;
}
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong t = PositionGetTicket(i);
if(t == 0) continue;
if(PositionGetString(POSITION_SYMBOL) != c.name) continue;
if(PositionGetInteger(POSITION_MAGIC) != c.magic) continue;
c.posTicket = t;
c.posOpen = true;
c.posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
return;
}
}
//+------------------------------------------------------------------+
void CloseSlot(RSSymCtx &c)
{
g_trade.SetExpertMagicNumber(c.magic);
ClosePositionByMagic(g_trade, c.name, c.magic);
c.posOpen = false;
c.posTicket = 0;
c.rsiAgainst = false;
c.barsAgainst = 0;
}
//+------------------------------------------------------------------+
void OpenBuy(RSSymCtx &c)
{
if(CountOurPositions() >= MaxOpenPositions) return;
if(PositionExistsByMagic(c.name, c.magic)) return;
g_trade.SetExpertMagicNumber(c.magic);
double ask = SymbolInfoDouble(c.name, SYMBOL_ASK);
double lot = CalcLot(c.name, c.p.lotSize);
if(g_trade.Buy(lot, c.name, ask, 0, 0, "RS Super Buy"))
{
ulong t = g_trade.ResultOrder();
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, c.name, c.magic))
{
c.posTicket = t;
c.posOpen = true;
c.posType = POSITION_TYPE_BUY;
c.rsiAgainst = false;
c.barsAgainst = 0;
}
}
}
//+------------------------------------------------------------------+
void OpenSell(RSSymCtx &c)
{
if(CountOurPositions() >= MaxOpenPositions) return;
if(PositionExistsByMagic(c.name, c.magic)) return;
g_trade.SetExpertMagicNumber(c.magic);
double bid = SymbolInfoDouble(c.name, SYMBOL_BID);
double lot = CalcLot(c.name, c.p.lotSize);
if(g_trade.Sell(lot, c.name, bid, 0, 0, "RS Super Sell"))
{
ulong t = g_trade.ResultOrder();
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, c.name, c.magic))
{
c.posTicket = t;
c.posOpen = true;
c.posType = POSITION_TYPE_SELL;
c.rsiAgainst = false;
c.barsAgainst = 0;
}
}
}
//+------------------------------------------------------------------+
void ManageExit(RSSymCtx &c, const double cur)
{
if(!c.posOpen)
SyncPosition(c);
if(!c.posOpen) return;
if(!PositionSelectByTicketSymbolAndMagic(c.posTicket, c.name, c.magic))
{
c.posOpen = false;
c.posTicket = 0;
return;
}
if(c.posType == POSITION_TYPE_BUY)
{
if(cur < c.p.rsiOversold)
{
if(!c.rsiAgainst) { c.rsiAgainst = true; c.barsAgainst = 1; }
else c.barsAgainst++;
if(c.barsAgainst >= c.p.barsToWait) { CloseSlot(c); return; }
}
else
{
c.rsiAgainst = false;
c.barsAgainst = 0;
if(cur >= c.p.rsiTargetBuy) CloseSlot(c);
}
}
else
{
if(cur > c.p.rsiOverbought)
{
if(!c.rsiAgainst) { c.rsiAgainst = true; c.barsAgainst = 1; }
else c.barsAgainst++;
if(c.barsAgainst >= c.p.barsToWait) { CloseSlot(c); return; }
}
else
{
c.rsiAgainst = false;
c.barsAgainst = 0;
if(cur <= c.p.rsiTargetSell) CloseSlot(c);
}
}
}
//+------------------------------------------------------------------+
void CheckEntry(RSSymCtx &c, const double prev, const double two)
{
if(c.posOpen || PositionExistsByMagic(c.name, c.magic)) return;
if(two <= c.p.rsiOversold && prev > c.p.rsiOversold)
OpenBuy(c);
if(two >= c.p.rsiOverbought && prev < c.p.rsiOverbought)
OpenSell(c);
}
//+------------------------------------------------------------------+
void ProcessSlot(RSSymCtx &c)
{
if(!c.enabled) return;
if(!SymbolSelect(c.name, true)) return;
if(Bars(c.name, RS_TF) < c.p.rsiPeriod + 2) return;
datetime bt = iTime(c.name, RS_TF, 0);
if(bt <= 0 || bt == c.lastBar) return;
c.lastBar = bt;
double cur, prev, two;
if(!UpdateRsi(c, cur, prev, two)) return;
ManageExit(c, cur);
if(!c.posOpen)
CheckEntry(c, prev, two);
}
//+------------------------------------------------------------------+
int OnInit()
{
g_trade.SetDeviationInPoints(Slippage);
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
g_count = 0;
for(int i = 0; i < RS_SUPER_SLOT_COUNT; i++)
{
const RSSlotConfig cfg = RS_SUPER_SLOTS[i];
RSSymCtx c;
c.name = cfg.symbol;
c.p = cfg.p;
c.magic = cfg.magic;
c.enabled = cfg.enabled && SlotEnabled(i);
c.rsiHandle = INVALID_HANDLE;
c.lastBar = 0;
c.posOpen = false;
c.posTicket = 0;
c.rsiAgainst = false;
c.barsAgainst = 0;
if(c.enabled)
{
SymbolSelect(c.name, true);
c.rsiHandle = iRSI(c.name, RS_TF, c.p.rsiPeriod, PRICE_CLOSE);
if(c.rsiHandle == INVALID_HANDLE)
{
Print("Failed RSI handle for ", c.name);
c.enabled = false;
}
SyncPosition(c);
}
g_ctx[g_count] = c;
g_count++;
}
Print("RSIScalpingSuper initialized slots=", g_count);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
for(int i = 0; i < g_count; i++)
if(g_ctx[i].rsiHandle != INVALID_HANDLE)
IndicatorRelease(g_ctx[i].rsiHandle);
Comment("");
}
//+------------------------------------------------------------------+
void OnTick()
{
string status = "RSIScalpingSuper\n";
for(int i = 0; i < g_count; i++)
{
ProcessSlot(g_ctx[i]);
if(g_ctx[i].enabled)
status += StringFormat("%s %s | ", g_ctx[i].name, g_ctx[i].posOpen ? "IN" : "--");
}
Comment(status);
}
@@ -0,0 +1,15 @@
; RSIScalpingSuper portfolio — attach to EURUSD H1 in Strategy Tester
LotMultiplier=1.0||1.0||0.100000||10.000000||N
ScaleLotsToDeposit=true||false||0||true||N
ReferenceDeposit=10000.0||10000.0||1000.000000||100000.000000||N
Slippage=3||3||1||30||N
MaxOpenPositions=9||9||1||90||N
Enable_EURUSD=true||false||0||true||N
Enable_GBPUSD=true||false||0||true||N
Enable_USDJPY=true||false||0||true||N
Enable_AUDUSD=true||false||0||true||N
Enable_USDCHF=true||false||0||true||N
Enable_USDCAD=true||false||0||true||N
Enable_NZDUSD=true||false||0||true||N
Enable_EURJPY=true||false||0||true||N
Enable_XAUUSD=true||false||0||true||N
@@ -0,0 +1,41 @@
; RSIScalpingAdaptive XAUUSD — monthly walk-forward adaptive params
; Attach to XAUUSD chart (H1 recommended). EA re-optimizes each calendar month.
;
TimeFrame=16385||16385||0||16385||N
RSI_Period=14||14||1||140||N
RSI_Applied_Price=1||1||0||7||N
RSI_Overbought=71.0||71.0||1.000000||100.000000||N
RSI_Oversold=57.0||57.0||1.000000||100.000000||N
RSI_Target_Buy=80.0||80.0||1.000000||100.000000||N
RSI_Target_Sell=57.0||57.0||1.000000||100.000000||N
BarsToWait=1||1||1||50||N
LotSize=0.1||0.1||0.010000||1.000000||N
MagicNumber=129102315||129102315||1||1291023150||N
Slippage=3||3||1||30||N
EnableAdaptive=true||false||0||true||N
OptimizationCheckSeconds=3600||3600||60||86400||N
MinTradesForSelection=8||8||1||80||N
MaxCombinations=600||600||50||2000||N
BacktestInitialBalance=10000.0||10000.0||1000.000000||100000.000000||N
ScoreWeightSharpe=0.35||0.35||0.035000||3.500000||N
ScoreWeightNetProfit=0.25||0.25||0.025000||2.500000||N
ScoreWeightProfitFactor=0.15||0.15||0.015000||1.500000||N
ScoreWeightMaxDD=0.10||0.10||0.010000||1.000000||N
Search_RSI_Period_Min=12||12||1||120||N
Search_RSI_Period_Max=18||18||1||180||N
Search_RSI_Period_Step=2||2||1||20||N
Search_RSI_Overbought_Min=65.0||65.0||1.000000||100.000000||N
Search_RSI_Overbought_Max=77.0||77.0||1.000000||100.000000||N
Search_RSI_Overbought_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Oversold_Min=50.0||50.0||1.000000||100.000000||N
Search_RSI_Oversold_Max=63.0||63.0||1.000000||100.000000||N
Search_RSI_Oversold_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Target_Buy_Min=75.0||75.0||1.000000||100.000000||N
Search_RSI_Target_Buy_Max=86.0||86.0||1.000000||100.000000||N
Search_RSI_Target_Buy_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Target_Sell_Min=50.0||50.0||1.000000||100.000000||N
Search_RSI_Target_Sell_Max=63.0||63.0||1.000000||100.000000||N
Search_RSI_Target_Sell_Step=3.0||3.0||0.300000||30.000000||N
Search_BarsToWait_Min=1||1||1||10||N
Search_BarsToWait_Max=4||4||1||40||N
Search_BarsToWait_Step=1||1||1||10||N
@@ -0,0 +1,42 @@
; RSIScalpingAdaptive XAUUSD — MT5 Genetic Optimization winner (Pass 306)
; MetaQuotes Demo 2004.01.012026.01.01 | Profit $10,470 | PF 1.56 | DD 9.1% | Sharpe 2.79
; Strategy Tester → Inputs → Load
;
TimeFrame=16385||16385||0||16385||N
RSI_Period=17||17||1||140||N
RSI_Applied_Price=1||1||0||7||N
RSI_Overbought=6.0||6.0||1.000000||100.000000||N
RSI_Oversold=74.0||74.0||1.000000||100.000000||N
RSI_Target_Buy=79.0||79.0||1.000000||100.000000||N
RSI_Target_Sell=24.0||24.0||1.000000||100.000000||N
BarsToWait=12||12||1||50||N
LotSize=0.1||0.1||0.010000||1.000000||N
MagicNumber=129102315||129102315||1||1291023150||N
Slippage=3||3||1||30||N
EnableAdaptive=false||false||0||true||N
OptimizationCheckSeconds=3600||3600||60||86400||N
MinTradesForSelection=8||8||1||80||N
MaxCombinations=600||600||50||2000||N
BacktestInitialBalance=10000.0||10000.0||1000.000000||100000.000000||N
ScoreWeightSharpe=0.35||0.35||0.035000||3.500000||N
ScoreWeightNetProfit=0.25||0.25||0.025000||2.500000||N
ScoreWeightProfitFactor=0.15||0.15||0.015000||1.500000||N
ScoreWeightMaxDD=0.10||0.10||0.010000||1.000000||N
Search_RSI_Period_Min=12||12||1||120||N
Search_RSI_Period_Max=18||18||1||180||N
Search_RSI_Period_Step=2||2||1||20||N
Search_RSI_Overbought_Min=65.0||65.0||1.000000||100.000000||N
Search_RSI_Overbought_Max=77.0||77.0||1.000000||100.000000||N
Search_RSI_Overbought_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Oversold_Min=50.0||50.0||1.000000||100.000000||N
Search_RSI_Oversold_Max=63.0||63.0||1.000000||100.000000||N
Search_RSI_Oversold_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Target_Buy_Min=75.0||75.0||1.000000||100.000000||N
Search_RSI_Target_Buy_Max=86.0||86.0||1.000000||100.000000||N
Search_RSI_Target_Buy_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Target_Sell_Min=50.0||50.0||1.000000||100.000000||N
Search_RSI_Target_Sell_Max=63.0||63.0||1.000000||100.000000||N
Search_RSI_Target_Sell_Step=3.0||3.0||0.300000||30.000000||N
Search_BarsToWait_Min=1||1||1||10||N
Search_BarsToWait_Max=4||4||1||40||N
Search_BarsToWait_Step=1||1||1||10||N
@@ -0,0 +1,42 @@
; RSIScalpingAdaptive XAUUSD — MT5 Genetic Optimization
; Strategy Tester → Optimization → Genetic algorithm → Load this set
; Criterion: Balance + Profit Factor (or Custom max)
;
TimeFrame=16385||16385||0||16388||N
RSI_Period=14||10||1||21||Y
RSI_Applied_Price=1||1||0||7||N
RSI_Overbought=6.0||4.0||1.0||30.0||Y
RSI_Oversold=66.0||50.0||2.0||78.0||Y
RSI_Target_Buy=98.0||75.0||2.0||99.0||Y
RSI_Target_Sell=52.0||4.0||2.0||65.0||Y
BarsToWait=12||1||1||12||Y
LotSize=0.1||0.1||0||0.1||N
MagicNumber=129102315||129102315||1||1291023150||N
Slippage=3||3||1||30||N
EnableAdaptive=false||false||0||true||N
OptimizationCheckSeconds=3600||3600||60||86400||N
MinTradesForSelection=8||8||1||80||N
MaxCombinations=600||600||50||2000||N
BacktestInitialBalance=10000.0||10000.0||1000.000000||100000.000000||N
ScoreWeightSharpe=0.35||0.35||0.035000||3.500000||N
ScoreWeightNetProfit=0.25||0.25||0.025000||2.500000||N
ScoreWeightProfitFactor=0.15||0.15||0.015000||1.500000||N
ScoreWeightMaxDD=0.10||0.10||0.010000||1.000000||N
Search_RSI_Period_Min=12||12||1||120||N
Search_RSI_Period_Max=18||18||1||180||N
Search_RSI_Period_Step=2||2||1||20||N
Search_RSI_Overbought_Min=65.0||65.0||1.000000||100.000000||N
Search_RSI_Overbought_Max=77.0||77.0||1.000000||100.000000||N
Search_RSI_Overbought_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Oversold_Min=50.0||50.0||1.000000||100.000000||N
Search_RSI_Oversold_Max=63.0||63.0||1.000000||100.000000||N
Search_RSI_Oversold_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Target_Buy_Min=75.0||75.0||1.000000||100.000000||N
Search_RSI_Target_Buy_Max=86.0||86.0||1.000000||100.000000||N
Search_RSI_Target_Buy_Step=3.0||3.0||0.300000||30.000000||N
Search_RSI_Target_Sell_Min=50.0||50.0||1.000000||100.000000||N
Search_RSI_Target_Sell_Max=63.0||63.0||1.000000||100.000000||N
Search_RSI_Target_Sell_Step=3.0||3.0||0.300000||30.000000||N
Search_BarsToWait_Min=1||1||1||10||N
Search_BarsToWait_Max=4||4||1||40||N
Search_BarsToWait_Step=1||1||1||10||N
+488
View File
@@ -0,0 +1,488 @@
//+------------------------------------------------------------------+
//| RSIScalpingAdaptiveXAUUSD.mq5 |
//| RSI Scalping with monthly walk-forward parameter adaptation |
//| Backtests prior calendar month on each new month, applies best |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "2.00"
#property description "XAUUSD RSI Scalping — monthly walk-forward adaptive params"
#include <Trade\Trade.mqh>
#include "MagicNumberHelpers.mqh"
#include "RSIScalpingAdaptiveOptimizer.mqh"
//--- Fallback defaults (XAUUSD 123.set baseline)
input group "=== Fallback / seed parameters ==="
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1;
input int RSI_Period = 17;
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE;
input double RSI_Overbought = 6.0;
input double RSI_Oversold = 74.0;
input double RSI_Target_Buy = 79.0;
input double RSI_Target_Sell = 24.0;
input int BarsToWait = 12;
input group "=== Execution ==="
input double LotSize = 0.1;
input int MagicNumber = 129102315;
input int Slippage = 3;
input group "=== Adaptive walk-forward ==="
input bool EnableAdaptive = true;
input int OptimizationCheckSeconds = 3600; // Timer interval for new-month check
input int MinTradesForSelection = 8;
input int MaxCombinations = 600;
input double BacktestInitialBalance = 10000.0;
input double ScoreWeightSharpe = 0.35;
input double ScoreWeightNetProfit = 0.25;
input double ScoreWeightProfitFactor = 0.15;
input double ScoreWeightMaxDD = 0.10;
input group "=== XAUUSD search ranges ==="
input int Search_RSI_Period_Min = 12;
input int Search_RSI_Period_Max = 18;
input int Search_RSI_Period_Step = 2;
input double Search_RSI_Overbought_Min = 65.0;
input double Search_RSI_Overbought_Max = 77.0;
input double Search_RSI_Overbought_Step = 3.0;
input double Search_RSI_Oversold_Min = 50.0;
input double Search_RSI_Oversold_Max = 63.0;
input double Search_RSI_Oversold_Step = 3.0;
input double Search_RSI_Target_Buy_Min = 75.0;
input double Search_RSI_Target_Buy_Max = 86.0;
input double Search_RSI_Target_Buy_Step = 3.0;
input double Search_RSI_Target_Sell_Min = 50.0;
input double Search_RSI_Target_Sell_Max = 63.0;
input double Search_RSI_Target_Sell_Step = 3.0;
input int Search_BarsToWait_Min = 1;
input int Search_BarsToWait_Max = 4;
input int Search_BarsToWait_Step = 1;
CTrade trade;
CRSIAdaptiveOptimizer g_optimizer;
int rsi_handle = INVALID_HANDLE;
double rsi_buffer[];
double rsi_prev, rsi_current, rsi_two_bars_ago;
bool position_open = false;
ulong position_ticket = 0;
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
datetime last_bar_time = 0;
bool rsi_against_position = false;
int bars_against_count = 0;
RSIAdaptiveParams g_active;
RSIAdaptiveMetrics g_last_metrics;
int g_applied_month_key = 0;
bool g_optimization_done = false;
bool g_optimizing = false;
string g_status_line = "";
//+------------------------------------------------------------------+
void SyncOpenPosition()
{
if(!PositionExistsByMagic(_Symbol, MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber)
continue;
position_ticket = ticket;
position_open = true;
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
return;
}
}
//+------------------------------------------------------------------+
bool IsStrategyTester()
{
return (bool)MQLInfoInteger(MQL_TESTER);
}
//+------------------------------------------------------------------+
RSIAdaptiveParams BuildFallbackParams()
{
RSIAdaptiveParams p;
p.timeframe = TimeFrame;
p.rsi_period = RSI_Period;
p.rsi_overbought = RSI_Overbought;
p.rsi_oversold = RSI_Oversold;
p.rsi_target_buy = RSI_Target_Buy;
p.rsi_target_sell = RSI_Target_Sell;
p.bars_to_wait = BarsToWait;
return p;
}
//+------------------------------------------------------------------+
RSIAdaptiveSearchConfig BuildSearchConfig()
{
RSIAdaptiveSearchConfig cfg;
cfg.timeframe = TimeFrame;
cfg.rsi_period_min = Search_RSI_Period_Min;
cfg.rsi_period_max = Search_RSI_Period_Max;
cfg.rsi_period_step = MathMax(1, Search_RSI_Period_Step);
cfg.rsi_overbought_min = Search_RSI_Overbought_Min;
cfg.rsi_overbought_max = Search_RSI_Overbought_Max;
cfg.rsi_overbought_step = Search_RSI_Overbought_Step;
cfg.rsi_oversold_min = Search_RSI_Oversold_Min;
cfg.rsi_oversold_max = Search_RSI_Oversold_Max;
cfg.rsi_oversold_step = Search_RSI_Oversold_Step;
cfg.rsi_target_buy_min = Search_RSI_Target_Buy_Min;
cfg.rsi_target_buy_max = Search_RSI_Target_Buy_Max;
cfg.rsi_target_buy_step = Search_RSI_Target_Buy_Step;
cfg.rsi_target_sell_min = Search_RSI_Target_Sell_Min;
cfg.rsi_target_sell_max = Search_RSI_Target_Sell_Max;
cfg.rsi_target_sell_step = Search_RSI_Target_Sell_Step;
cfg.bars_to_wait_min = Search_BarsToWait_Min;
cfg.bars_to_wait_max = Search_BarsToWait_Max;
cfg.bars_to_wait_step = MathMax(1, Search_BarsToWait_Step);
cfg.min_trades = MinTradesForSelection;
cfg.lot_size = LotSize;
cfg.initial_balance = BacktestInitialBalance;
cfg.slippage_points = Slippage;
cfg.weight_sharpe = ScoreWeightSharpe;
cfg.weight_net_profit = ScoreWeightNetProfit;
cfg.weight_profit_factor = ScoreWeightProfitFactor;
cfg.weight_max_dd = ScoreWeightMaxDD;
cfg.max_combinations = MaxCombinations;
return cfg;
}
//+------------------------------------------------------------------+
bool RecreateRsiHandle()
{
if(rsi_handle != INVALID_HANDLE)
IndicatorRelease(rsi_handle);
rsi_handle = iRSI(_Symbol, g_active.timeframe, g_active.rsi_period, RSI_Applied_Price);
if(rsi_handle == INVALID_HANDLE)
{
Print("ERROR: failed to create RSI handle for ", g_active.ToString());
return false;
}
return true;
}
//+------------------------------------------------------------------+
void UpdateStatusComment()
{
g_status_line = StringFormat(
"RSI Adaptive XAUUSD | month=%d | %s\n"
"BT: net=$%.0f sharpe=%.2f PF=%.2f trades=%d DD=%.1f%% | combos=%d",
g_applied_month_key,
g_active.ToString(),
g_last_metrics.net_profit,
g_last_metrics.sharpe,
g_last_metrics.profit_factor,
g_last_metrics.total_trades,
g_last_metrics.max_drawdown_pct,
g_optimizer.CombosTested()
);
Comment(g_status_line);
}
//+------------------------------------------------------------------+
bool RunMonthlyOptimization(const string reason)
{
if(g_optimizing)
return true;
g_optimizing = true;
RSIAdaptiveParams fallback = BuildFallbackParams();
RSIAdaptiveSearchConfig cfg = BuildSearchConfig();
datetime opt_start, opt_end;
CRSIAdaptiveOptimizer::PreviousCalendarMonth(TimeCurrent(), opt_start, opt_end);
PrintFormat("[Adaptive] %s — optimizing on prior month (%s to %s)",
reason,
TimeToString(opt_start, TIME_DATE),
TimeToString(opt_end, TIME_DATE));
RSIAdaptiveParams best;
RSIAdaptiveMetrics best_metrics;
const bool ok = g_optimizer.Optimize(_Symbol, opt_start, opt_end, fallback, cfg, best, best_metrics);
if(ok)
{
g_active = best;
g_last_metrics = best_metrics;
}
else
{
Print("[Adaptive] Optimization found no valid combo — keeping fallback params");
g_active = fallback;
g_last_metrics = best_metrics;
}
g_applied_month_key = CRSIAdaptiveOptimizer::MonthKey(TimeCurrent());
g_optimization_done = true;
if(!RecreateRsiHandle())
{
g_optimizing = false;
return false;
}
last_bar_time = 0;
g_optimizing = false;
UpdateStatusComment();
return true;
}
//+------------------------------------------------------------------+
void CheckMonthlyOptimizationSchedule(const string reason)
{
if(!EnableAdaptive || IsStrategyTester())
return;
const int month_key = CRSIAdaptiveOptimizer::MonthKey(TimeCurrent());
if(!g_optimization_done || month_key != g_applied_month_key)
RunMonthlyOptimization(reason);
}
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
trade.SetDeviationInPoints(Slippage);
trade.SetTypeFilling(ORDER_FILLING_FOK);
ArraySetAsSeries(rsi_buffer, true);
g_active = BuildFallbackParams();
if(!RecreateRsiHandle())
return INIT_FAILED;
EventSetTimer(OptimizationCheckSeconds);
// Strategy Tester / Optimization: use Inputs directly — no walk-forward grid search
if(IsStrategyTester() || !EnableAdaptive)
{
g_active = BuildFallbackParams();
g_optimization_done = true;
g_applied_month_key = CRSIAdaptiveOptimizer::MonthKey(TimeCurrent());
if(!RecreateRsiHandle())
return INIT_FAILED;
UpdateStatusComment();
SyncOpenPosition();
return INIT_SUCCEEDED;
}
if(!RunMonthlyOptimization("OnInit"))
return INIT_FAILED;
SyncOpenPosition();
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
if(rsi_handle != INVALID_HANDLE)
IndicatorRelease(rsi_handle);
Comment("");
}
//+------------------------------------------------------------------+
void OnTimer()
{
CheckMonthlyOptimizationSchedule("OnTimer");
}
//+------------------------------------------------------------------+
void OnTick()
{
if(Bars(_Symbol, g_active.timeframe) < g_active.rsi_period + 2)
return;
datetime current_bar_time = iTime(_Symbol, g_active.timeframe, 0);
if(current_bar_time == last_bar_time)
return;
last_bar_time = current_bar_time;
if(!UpdateRSI())
return;
CheckExistingPosition();
if(!position_open && !PositionExistsByMagic(_Symbol, MagicNumber))
CheckEntrySignals();
UpdateStatusComment();
}
//+------------------------------------------------------------------+
bool UpdateRSI()
{
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
return false;
rsi_current = rsi_buffer[0];
rsi_prev = rsi_buffer[1];
rsi_two_bars_ago = rsi_buffer[2];
return true;
}
//+------------------------------------------------------------------+
void CheckExistingPosition()
{
if(!position_open)
SyncOpenPosition();
if(!position_open)
return;
if(!PositionSelectByTicketSymbolAndMagic(position_ticket, _Symbol, MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
return;
}
if(current_position_type == POSITION_TYPE_BUY)
{
if(rsi_current < g_active.rsi_oversold)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
bars_against_count++;
if(bars_against_count >= g_active.bars_to_wait)
{
ClosePosition();
return;
}
}
else
{
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
if(rsi_current >= g_active.rsi_target_buy)
ClosePosition();
}
}
else if(current_position_type == POSITION_TYPE_SELL)
{
if(rsi_current > g_active.rsi_overbought)
{
if(!rsi_against_position)
{
rsi_against_position = true;
bars_against_count = 1;
}
else
bars_against_count++;
if(bars_against_count >= g_active.bars_to_wait)
{
ClosePosition();
return;
}
}
else
{
if(rsi_against_position)
{
rsi_against_position = false;
bars_against_count = 0;
}
if(rsi_current <= g_active.rsi_target_sell)
ClosePosition();
}
}
}
//+------------------------------------------------------------------+
void CheckEntrySignals()
{
if(rsi_two_bars_ago <= g_active.rsi_oversold && rsi_prev > g_active.rsi_oversold)
OpenBuyPosition();
if(rsi_two_bars_ago >= g_active.rsi_overbought && rsi_prev < g_active.rsi_overbought)
OpenSellPosition();
}
//+------------------------------------------------------------------+
void OpenBuyPosition()
{
if(PositionExistsByMagic(_Symbol, MagicNumber))
return;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Adaptive Buy"))
{
ulong new_ticket = trade.ResultOrder();
if(new_ticket > 0 && PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, MagicNumber))
{
position_ticket = new_ticket;
position_open = true;
current_position_type = POSITION_TYPE_BUY;
}
}
}
//+------------------------------------------------------------------+
void OpenSellPosition()
{
if(PositionExistsByMagic(_Symbol, MagicNumber))
return;
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Adaptive Sell"))
{
ulong new_ticket = trade.ResultOrder();
if(new_ticket > 0 && PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, MagicNumber))
{
position_ticket = new_ticket;
position_open = true;
current_position_type = POSITION_TYPE_SELL;
}
}
}
//+------------------------------------------------------------------+
void ClosePosition()
{
if(ClosePositionByMagic(trade, _Symbol, MagicNumber))
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
}
else
{
position_open = false;
position_ticket = 0;
rsi_against_position = false;
bars_against_count = 0;
}
}
+292
View File
@@ -0,0 +1,292 @@
"""
RSIScalpingNVDA — bar backtest mirroring main.mq5 inputs.
Outputs in this folder:
backtest_report.json, trades.csv, report.png,
equity_curve.png, drawdown.png, monthly_returns.png,
pnl_distribution.png, exit_reasons.png
Usage:
python run_backtest.py
python run_backtest.py --start 2021-01-01 --end 2026-01-01
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import asdict, dataclass
from datetime import datetime
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import MetaTrader5 as mt5
import numpy as np
import pandas as pd
ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT / "backtesting" / "MT5"))
from cluster_audit.backtest_core import ( # noqa: E402
BacktestReport,
CostModel,
load_bars,
resolve_symbol,
run_single_position,
)
from indicator_utils import calculate_adx, calculate_atr, calculate_dmi, calculate_ema, calculate_rsi # noqa: E402
STRATEGY_ID = "RSIScalpingNVDA"
def save_reports(report: BacktestReport, out_dir: Path) -> None:
rows = [
{
"side": t.side,
"open_time": t.open_time,
"close_time": t.close_time,
"open_price": t.open_price,
"close_price": t.close_price,
"volume": t.volume,
"profit": t.profit,
"bars_held": t.bars_held,
"exit_reason": t.exit_reason,
}
for t in report.trades_list
]
pd.DataFrame(rows).to_csv(out_dir / "trades.csv", index=False)
with open(out_dir / "backtest_report.json", "w", encoding="utf-8") as f:
json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
trades = report.trades_list
if not trades:
fig, ax = plt.subplots(figsize=(10, 4))
ax.text(0.5, 0.5, "No trades in backtest window", ha="center", va="center", fontsize=14)
ax.axis("off")
fig.savefig(out_dir / "report.png", dpi=200, bbox_inches="tight")
plt.close(fig)
return
df = pd.DataFrame(rows)
df["close_time"] = pd.to_datetime(df["close_time"])
df = df.sort_values("close_time")
bal0 = report.params.get("initial_balance", 10_000.0)
if report.equity_curve is not None and len(report.equity_curve) > 1:
eq = report.equity_curve
else:
eq = pd.Series(bal0 + df["profit"].cumsum().values, index=df["close_time"])
equity_times = eq.index
equity = eq
fig = plt.figure(figsize=(14, 10))
gs = fig.add_gridspec(3, 2, height_ratios=[2, 1.2, 1.2])
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(equity_times, equity, lw=1.8)
ax1.axhline(bal0, color="gray", ls="--")
ax1.set_title("Equity Curve")
ax1.grid(alpha=0.3)
ax2 = fig.add_subplot(gs[1, 0])
dd = (equity - equity.cummax()) / equity.cummax() * 100
ax2.fill_between(equity_times, dd, 0, color="#d62728", alpha=0.35)
ax2.set_title("Drawdown %")
ax2.grid(alpha=0.3)
ax3 = fig.add_subplot(gs[1, 1])
df["month"] = df["close_time"].dt.to_period("M")
monthly = df.groupby("month")["profit"].sum()
ax3.bar(range(len(monthly)), monthly.values, color=["#2ca02c" if v >= 0 else "#d62728" for v in monthly])
ax3.set_title("Monthly PnL")
ax3.axhline(0, color="black", lw=0.6)
ax4 = fig.add_subplot(gs[2, 0])
ax4.hist(df["profit"], bins=30, color="#9467bd", alpha=0.85)
ax4.axvline(0, color="black")
ax4.set_title("Trade PnL Distribution")
ax5 = fig.add_subplot(gs[2, 1])
rc = df["exit_reason"].value_counts()
ax5.bar(rc.index.astype(str), rc.values, color="#ff7f0e")
ax5.set_title("Exit Reasons")
fig.suptitle(
f"{STRATEGY_ID} — Net ${report.net_profit:,.2f} | Trades {report.total_trades} | "
f"WR {report.win_rate:.1f}% | PF {report.profit_factor:.2f} | MaxDD {report.max_drawdown_pct:.2f}%",
fontsize=11,
)
fig.tight_layout(rect=[0, 0, 1, 0.96])
fig.savefig(out_dir / "report.png", dpi=200, bbox_inches="tight")
plt.close(fig)
plt.figure(figsize=(12, 5))
plt.plot(equity_times, equity, lw=2)
plt.title("Equity Curve")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(out_dir / "equity_curve.png", dpi=200, bbox_inches="tight")
plt.close()
plt.figure(figsize=(12, 5))
plt.fill_between(equity_times, dd, 0, color="red", alpha=0.3)
plt.plot(equity_times, dd, color="darkred")
plt.title("Drawdown %")
plt.grid(alpha=0.3)
plt.tight_layout()
plt.savefig(out_dir / "drawdown.png", dpi=200, bbox_inches="tight")
plt.close()
plt.figure(figsize=(12, 5))
plt.bar(range(len(monthly)), monthly.values, color=["green" if v >= 0 else "red" for v in monthly], alpha=0.75)
plt.title("Monthly PnL")
plt.axhline(0, color="black")
plt.grid(alpha=0.3, axis="y")
plt.tight_layout()
plt.savefig(out_dir / "monthly_returns.png", dpi=200, bbox_inches="tight")
plt.close()
plt.figure(figsize=(10, 5))
plt.hist(df["profit"], bins=40, color="#6a5acd", alpha=0.85)
plt.axvline(0, color="black")
plt.title("Per-Trade PnL Distribution")
plt.tight_layout()
plt.savefig(out_dir / "pnl_distribution.png", dpi=200, bbox_inches="tight")
plt.close()
if report.exit_reason_breakdown:
labels = list(report.exit_reason_breakdown.keys())
counts = [report.exit_reason_breakdown[k]["count"] for k in labels]
plt.figure(figsize=(8, 5))
plt.bar(labels, counts, color="#e377c2")
plt.title("Exit Reason Counts")
plt.tight_layout()
plt.savefig(out_dir / "exit_reasons.png", dpi=200, bbox_inches="tight")
plt.close()
@dataclass
class StrategyParams:
rsi_period: int = 14
rsi_overbought: float = 6
rsi_oversold: float = 66
rsi_target_buy: float = 98
rsi_target_sell: float = 52
bars_to_wait: int = 12
lot_size: float = 5
use_reversal_escape: bool = False
reversal_atr_period: int = 14
reversal_adverse_atr_mult: float = 1.5
reversal_signs_required: int = 2
reversal_rsi_velocity: float = 8.0
initial_balance: float = 10_000.0
def to_dict(self) -> dict:
return asdict(self)
def make_params(balance: float) -> StrategyParams:
return StrategyParams(initial_balance=balance)
def run_backtest(df, symbol, params: StrategyParams, costs, period_label, tf_label="H1"):
info = mt5.symbol_info(symbol)
point = float(info.point) if info else 0.01
rsi = calculate_rsi(df["close"], params.rsi_period).to_numpy()
atr = calculate_atr(df, params.reversal_atr_period).to_numpy()
p = params.to_dict()
def on_bar(i, st, open_pos, close):
if i < 3 or np.isnan(rsi[i - 1]):
return
sig, prev, two = rsi[i - 1], rsi[i - 2], rsi[i - 3]
mid = float(df["open"].iloc[i])
hi, lo = float(df["high"].iloc[i]), float(df["low"].iloc[i])
if st.side and params.use_reversal_escape:
a = float(atr[i - 1]) if not np.isnan(atr[i - 1]) else 0.0
if a > 0:
signs = 0
if st.side == "BUY":
if st.entry - lo >= params.reversal_adverse_atr_mult * a:
signs += 1
if sig - prev >= params.reversal_rsi_velocity:
signs += 1
else:
if hi - st.entry >= params.reversal_adverse_atr_mult * a:
signs += 1
if prev - sig >= params.reversal_rsi_velocity:
signs += 1
if signs >= params.reversal_signs_required:
close(i, mid, "reversal_escape")
return
if st.side == "BUY":
if sig < params.rsi_oversold:
st.bars_against = st.bars_against + 1 if st.rsi_against else 1
st.rsi_against = True
if st.bars_against >= params.bars_to_wait:
close(i, mid, "rsi_against")
else:
st.rsi_against = False
st.bars_against = 0
if sig >= params.rsi_target_buy:
close(i, mid, "target")
elif st.side == "SELL":
if sig > params.rsi_overbought:
st.bars_against = st.bars_against + 1 if st.rsi_against else 1
st.rsi_against = True
if st.bars_against >= params.bars_to_wait:
close(i, mid, "rsi_against")
else:
st.rsi_against = False
st.bars_against = 0
if sig <= params.rsi_target_sell:
close(i, mid, "target")
else:
if two <= params.rsi_oversold and prev > params.rsi_oversold:
open_pos(i, "BUY", mid)
elif two >= params.rsi_overbought and prev < params.rsi_overbought:
open_pos(i, "SELL", mid)
return run_single_position(
df, symbol, point, costs, params.lot_size, STRATEGY_ID, tf_label, period_label, p, params.initial_balance, on_bar
)
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description=f"{STRATEGY_ID} Python backtest")
p.add_argument("--symbol", default="XAUUSD")
p.add_argument("--start", default="2023-01-01")
p.add_argument("--end", default="2026-01-01")
p.add_argument("--balance", type=float, default=10_000.0)
p.add_argument("--lot", type=float, default=0.1)
p.add_argument("--timeframe", default="H1", choices=["M20", "H1"])
return p.parse_args()
def main() -> None:
args = parse_args()
out_dir = Path(__file__).resolve().parent
params = make_params(args.balance)
if not mt5.initialize():
raise SystemExit("MetaTrader5 initialize() failed")
try:
symbol = resolve_symbol(args.symbol)
start = datetime.fromisoformat(args.start)
end = datetime.fromisoformat(args.end)
period_label = f"{args.start}_{args.end}"
tf_map = {"M20": mt5.TIMEFRAME_M20, "H1": mt5.TIMEFRAME_H1}
tf = tf_map[args.timeframe]
print(f"Loading {symbol} {args.timeframe} bars ...")
df = load_bars(symbol, tf, start, end)
costs = CostModel.for_symbol(symbol)
report = run_backtest(df, symbol, params, costs, period_label, args.timeframe)
save_reports(report, out_dir)
print(f"Net: ${report.net_profit:,.2f} | Trades: {report.total_trades} | WR: {report.win_rate:.1f}% | PF: {report.profit_factor:.2f}")
print(f"Saved to {out_dir}")
finally:
mt5.shutdown()
if __name__ == "__main__":
main()
@@ -0,0 +1,197 @@
"""
Batch MT5 genetic optimization per symbol → regenerate RSIScalpingSuperParams.mqh
Usage:
python run_mt5_cluster.py optimize --symbols EURUSD,GBPUSD,USDJPY
python run_mt5_cluster.py optimize --all-forex
python run_mt5_cluster.py backtest-portfolio
"""
from __future__ import annotations
import argparse
import re
import subprocess
import sys
import xml.etree.ElementTree as ET
from datetime import datetime
from pathlib import Path
LAB = Path(__file__).resolve().parent
TESTER = LAB / "run_mt5_tester.py"
OPT_SET = LAB / "XAUUSD_Genetic_Optimization.set"
PARAMS_MQH = LAB / "RSIScalpingSuperParams.mqh"
MAGIC_MQH = LAB / "RSIScalpingSuperMagic.mqh"
FOREX_MAJORS = [
"EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCHF", "USDCAD", "NZDUSD", "EURJPY", "XAUUSD"
]
def parse_best_from_xml(xml_path: Path) -> dict | None:
if not xml_path.exists():
return None
ns = {"ss": "urn:schemas-microsoft-com:office:spreadsheet"}
root = ET.parse(xml_path).getroot()
rows = root.findall(".//ss:Worksheet/ss:Table/ss:Row", ns)
if len(rows) < 2:
return None
headers = [c.find("ss:Data", ns).text for c in rows[0].findall("ss:Cell", ns)]
best = None
best_score = float("-inf")
for row in rows[1:]:
cells = [c.find("ss:Data", ns).text for c in row.findall("ss:Cell", ns)]
if len(cells) < len(headers):
continue
d = dict(zip(headers, cells))
try:
profit = float(d.get("Profit", 0))
pf = float(d.get("Profit Factor", 0))
dd = float(d.get("Equity DD %", 100))
sharpe = float(d.get("Sharpe Ratio", 0))
except (TypeError, ValueError):
continue
if profit <= 0 or pf < 1.05 or dd > 20:
continue
score = profit * pf / max(dd, 1.0) + sharpe * 100
if score > best_score:
best_score = score
best = {
"profit": profit,
"pf": pf,
"dd": dd,
"sharpe": sharpe,
"trades": int(float(d.get("Trades", 0))),
"rsi_period": int(float(d["RSI_Period"])),
"rsi_overbought": float(d["RSI_Overbought"]),
"rsi_oversold": float(d["RSI_Oversold"]),
"rsi_target_buy": float(d["RSI_Target_Buy"]),
"rsi_target_sell": float(d["RSI_Target_Sell"]),
"bars_to_wait": int(float(d["BarsToWait"])),
}
return best
def run_optimize_symbol(symbol: str, from_date: str, to_date: str, timeout: int) -> dict | None:
cmd = [
sys.executable,
str(TESTER),
"optimize",
"--symbol",
symbol,
"--from",
from_date,
"--to",
to_date,
"--set",
str(OPT_SET),
"--timeout",
str(timeout),
]
print(f"\n=== MT5 genetic optimize {symbol} ===")
subprocess.run(cmd, check=False)
import MetaTrader5 as mt5
if not mt5.initialize():
return None
data = Path(mt5.terminal_info().data_path)
mt5.shutdown()
xml = data / f"RSIScalpingAdaptive_{symbol}_optimize.xml"
return parse_best_from_xml(xml)
def write_params_mqh(results: dict[str, dict]) -> None:
lines = [
"// RSIScalpingSuperParams.mqh — auto-generated from MT5 genetic optimization",
f"// Generated: {datetime.now().isoformat(timespec='seconds')}",
"#ifndef RSI_SCALPING_SUPER_PARAMS_MQH",
"#define RSI_SCALPING_SUPER_PARAMS_MQH",
"",
'#include "RSIScalpingSuperMagic.mqh"',
"",
f"#define RS_SUPER_SLOT_COUNT {len(results)}",
"",
"struct RSSlotParams",
"{",
" int rsiPeriod;",
" double rsiOverbought;",
" double rsiOversold;",
" double rsiTargetBuy;",
" double rsiTargetSell;",
" int barsToWait;",
" double lotSize;",
"};",
"",
"struct RSSlotConfig",
"{",
" string symbol;",
" int magic;",
" bool enabled;",
" RSSlotParams p;",
"};",
"",
"const RSSlotConfig RS_SUPER_SLOTS[RS_SUPER_SLOT_COUNT] =",
"{",
]
for i, (sym, r) in enumerate(results.items(), start=1):
comment = f"// {sym} MT5 genetic profit=${r['profit']:.0f} PF={r['pf']:.2f} DD={r['dd']:.1f}%"
lines.append(f" {comment}")
lines.append(
f' {{ "{sym}", RS_SUPER_MAGIC_BASE + {i}, true,'
)
lines.append(
f" {{ {r['rsi_period']}, {r['rsi_overbought']:.1f}, {r['rsi_oversold']:.1f}, "
f"{r['rsi_target_buy']:.1f}, {r['rsi_target_sell']:.1f}, {r['bars_to_wait']}, 0.10 }} }},"
)
lines += ["};", "", "#endif", ""]
PARAMS_MQH.write_text("\n".join(lines), encoding="utf-8")
print(f"Wrote {PARAMS_MQH}")
def main() -> None:
p = argparse.ArgumentParser()
p.add_argument("mode", choices=["optimize", "backtest-portfolio"])
p.add_argument("--symbols", default=",".join(FOREX_MAJORS))
p.add_argument("--all-forex", action="store_true")
p.add_argument("--from", dest="from_date", default="2004.01.01")
p.add_argument("--to", dest="to_date", default="2026.01.01")
p.add_argument("--timeout", type=int, default=7200)
args = p.parse_args()
syms = FOREX_MAJORS if args.all_forex else [s.strip() for s in args.symbols.split(",") if s.strip()]
if args.mode == "optimize":
results: dict[str, dict] = {}
for sym in syms:
best = run_optimize_symbol(sym, args.from_date, args.to_date, args.timeout)
if best:
results[sym] = best
print(f" {sym}: profit=${best['profit']:.0f} PF={best['pf']:.2f} DD={best['dd']:.1f}%")
else:
print(f" {sym}: no stable candidate — skipped")
if not results:
raise SystemExit("No symbols passed optimization gates")
if len(results) < len(syms):
print(f"WARNING: only {len(results)}/{len(syms)} symbols optimized — merge manually into RSIScalpingSuperParams.mqh")
return
write_params_mqh(results)
else:
cmd = [
sys.executable,
str(LAB / "run_mt5_tester.py"),
"backtest",
"--symbol",
"EURUSD",
"--from",
args.from_date,
"--to",
args.to_date,
"--set",
str(LAB / "SuperEA_portfolio.set"),
]
# portfolio backtest uses SuperEA — extend run_mt5_tester for SuperEA
print("Use MT5 Tester manually: Expert=RSIScalpingSuper.ex5 on EURUSD H1, load SuperEA_portfolio.set")
if __name__ == "__main__":
main()
@@ -0,0 +1,353 @@
"""
Launch MT5 Strategy Tester for RSIScalpingAdaptive (native backtest / genetic optimize).
Examples:
python run_mt5_tester.py backtest
python run_mt5_tester.py backtest --symbol XAUUSD --from 2004.01.01 --to 2026.01.01
python run_mt5_tester.py optimize --symbol XAUUSD --from 2004.01.01 --to 2026.01.01
"""
from __future__ import annotations
import argparse
import re
import shutil
import subprocess
import time
from pathlib import Path
import MetaTrader5 as mt5
LAB = Path(__file__).resolve().parent
EA_MAIN = LAB / "main.mq5"
EA_OPTIMIZER = LAB / "RSIScalpingAdaptiveOptimizer.mqh"
EA_HELPERS = LAB / "MagicNumberHelpers.mqh"
EA_SUPER = LAB / "SuperEA.mq5"
EA_SUPER_PARAMS = LAB / "RSIScalpingSuperParams.mqh"
EA_SUPER_MAGIC = LAB / "RSIScalpingSuperMagic.mqh"
DEFAULT_SET = LAB / "XAUUSD_Backtest.set"
SUPER_SET = LAB / "SuperEA_portfolio.set"
OPT_SET = LAB / "XAUUSD_Genetic_Optimization.set"
EA_FOLDER = "RSIScalpingAdaptive"
SUPER_EX5 = "RSIScalpingSuper"
LABELS = {
"profit_factor": ("Profit Factor", "盈利因子"),
"net_profit": ("Total Net Profit", "总净盈利"),
"total_trades": ("Total Trades", "交易总计"),
"sharpe": ("Sharpe Ratio", "夏普比率"),
"equity_dd": ("Equity Drawdown Maximal", "最大回撤"),
"recovery": ("Recovery Factor", "恢复因子"),
}
def read_text(path: Path) -> str:
text = path.read_text(encoding="utf-16", errors="ignore")
if not text.strip():
text = path.read_text(encoding="utf-8", errors="ignore")
return text
def grab_metric(text: str, key: str) -> str | None:
for label in LABELS[key]:
for pat in (
rf">{re.escape(label)}</td>\s*<td[^>]*>(?:<b>)?([^<]+)",
rf">{re.escape(label)}:</td>\s*<td[^>]*>(?:<b>)?([^<]+)",
):
m = re.search(pat, text, re.I)
if m:
return m.group(1).strip()
return None
def parse_report(data: Path, report: str) -> dict:
xml_path = data / f"{report}.xml"
if xml_path.exists():
text = xml_path.read_text(encoding="utf-8", errors="ignore")
m = re.search(
r"<Row>\s*<Cell[^>]*><Data[^>]*>Pass</Data>.*?</Row>\s*<Row>(.*?)</Row>",
text,
re.S,
)
if m:
cells = re.findall(r"<Data ss:Type=\"(?:Number|String)\">([^<]+)</Data>", m.group(1))
if len(cells) >= 10:
return {
"ready": True,
"report": str(xml_path),
"net_profit": float(cells[2]),
"profit_factor": float(cells[4]),
"sharpe": float(cells[6]),
"max_drawdown": f"{cells[8]}%",
"total_trades": int(float(cells[9])),
"RSI_Period": cells[10] if len(cells) > 10 else None,
"RSI_Overbought": cells[11] if len(cells) > 11 else None,
"RSI_Oversold": cells[12] if len(cells) > 12 else None,
}
for path in sorted(data.glob(f"**/{report}*.htm*"), key=lambda p: p.stat().st_mtime, reverse=True):
text = read_text(path)
pf = grab_metric(text, "profit_factor")
profit = grab_metric(text, "net_profit")
trades = grab_metric(text, "total_trades")
sharpe = grab_metric(text, "sharpe")
dd = grab_metric(text, "equity_dd")
recovery = grab_metric(text, "recovery")
if pf or profit or trades:
return {
"profit_factor": float(pf) if pf else None,
"net_profit": _num(profit),
"total_trades": int(float(trades)) if trades and trades[0].isdigit() else None,
"sharpe": float(sharpe) if sharpe else None,
"max_drawdown": dd,
"recovery_factor": float(recovery) if recovery else None,
"report": str(path),
"ready": True,
}
for ext in (".htm", ".html"):
p = data / f"{report}{ext}"
if p.exists():
text = read_text(p)
pf = grab_metric(text, "profit_factor")
if pf:
return {"ready": True, "report": str(p), "profit_factor": float(pf)}
return {"ready": False}
def _num(s: str | None) -> float | None:
if not s:
return None
s = s.replace(" ", "").replace(",", "")
if s.endswith("%"):
return float(s[:-1])
return float(s)
def mt5_context() -> dict:
if not mt5.initialize():
raise RuntimeError(f"MT5 init failed: {mt5.last_error()}")
info = mt5.terminal_info()
acc = mt5.account_info()
ctx = {
"data": Path(info.data_path),
"mt5_path": Path(info.path),
"login": acc.login if acc else 0,
"server": acc.server if acc else "",
}
mt5.shutdown()
return ctx
def deploy_ea(data: Path, mt5_path: Path, expert: str = "single") -> Path:
dst_dir = data / "MQL5" / "Experts" / EA_FOLDER
dst_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(EA_HELPERS, dst_dir / "MagicNumberHelpers.mqh")
if expert == "super":
shutil.copy2(EA_SUPER, dst_dir / "SuperEA.mq5")
shutil.copy2(EA_SUPER_PARAMS, dst_dir / "RSIScalpingSuperParams.mqh")
shutil.copy2(EA_SUPER_MAGIC, dst_dir / "RSIScalpingSuperMagic.mqh")
dst = dst_dir / "SuperEA.mq5"
log = dst_dir / "compile_super.log"
subprocess.run(
[str(mt5_path / "metaeditor64.exe"), f"/compile:{dst}", f"/log:{log}"],
timeout=180,
capture_output=True,
)
time.sleep(3)
ex5 = dst_dir / "SuperEA.ex5"
if not ex5.exists():
tail = log.read_text(encoding="utf-8", errors="ignore")[-3000:] if log.exists() else ""
raise RuntimeError(f"SuperEA compile failed:\n{tail}")
pub = data / "MQL5" / "Experts" / f"{SUPER_EX5}.ex5"
shutil.copy2(ex5, pub)
return pub
shutil.copy2(EA_MAIN, dst_dir / "main.mq5")
shutil.copy2(EA_OPTIMIZER, dst_dir / "RSIScalpingAdaptiveOptimizer.mqh")
dst = dst_dir / "main.mq5"
log = dst_dir / "compile.log"
subprocess.run(
[str(mt5_path / "metaeditor64.exe"), f"/compile:{dst}", f"/log:{log}"],
timeout=180,
capture_output=True,
)
time.sleep(3)
ex5 = dst_dir / "main.ex5"
if not ex5.exists():
tail = log.read_text(encoding="utf-8", errors="ignore")[-3000:] if log.exists() else ""
raise RuntimeError(f"Compile failed — check MetaEditor:\n{dst}\n{tail}")
pub = data / "MQL5" / "Experts" / f"{EA_FOLDER}.ex5"
shutil.copy2(ex5, pub)
return pub
def copy_set_to_tester(data: Path, set_path: Path, set_name: str) -> Path:
profiles = data / "MQL5" / "Profiles" / "Tester"
profiles.mkdir(parents=True, exist_ok=True)
dst = profiles / set_name
shutil.copy2(set_path, dst)
return dst
def build_ini(
*,
set_name: str,
report: str,
login: int,
server: str,
symbol: str,
period: str,
from_date: str,
to_date: str,
deposit: float,
leverage: int,
optimization: int,
expert: str,
visual: bool,
) -> str:
ex5_name = "RSIScalpingAdaptive\\SuperEA.ex5" if expert == "super" else f"{EA_FOLDER}.ex5"
return f"""[Common]
Login={login}
Server={server}
[Tester]
Expert={ex5_name}
ExpertParameters={set_name}
Symbol={symbol}
Period={period}
Optimization={optimization}
Model=1
Dates=1
FromDate={from_date}
ToDate={to_date}
ForwardMode=0
Deposit={deposit}
Currency=USD
Leverage={leverage}
ExecutionMode=0
Report={report}
ReplaceReport=1
ShutdownTerminal=1
Visual={1 if visual else 0}
"""
def run_tester(
ctx: dict,
*,
mode: str,
set_path: Path,
set_name: str,
report: str,
symbol: str,
period: str,
from_date: str,
to_date: str,
deposit: float,
leverage: int,
visual: bool,
expert: str = "single",
timeout_sec: int = 7200,
) -> dict:
data: Path = ctx["data"]
mt5_path: Path = ctx["mt5_path"]
deploy_ea(data, mt5_path, expert)
copy_set_to_tester(data, set_path, set_name)
optimization = 2 if mode == "optimize" else 0
ini_body = build_ini(
set_name=set_name,
report=report,
login=ctx["login"],
server=ctx["server"],
symbol=symbol,
period=period,
from_date=from_date,
to_date=to_date,
deposit=deposit,
leverage=leverage,
optimization=optimization,
expert=expert,
visual=visual,
)
ini = data / f"{report}.ini"
ini.write_text(ini_body, encoding="utf-8")
for ext in (".htm", ".html"):
p = data / f"{report}{ext}"
if p.exists():
p.unlink(missing_ok=True)
subprocess.run(["taskkill", "/IM", "terminal64.exe", "/F"], capture_output=True)
subprocess.run(["taskkill", "/IM", "metatester64.exe", "/F"], capture_output=True)
time.sleep(4)
print(f"Starting MT5 Strategy Tester ({mode}) …")
print(f" EA: {SUPER_EX5 if expert == 'super' else EA_FOLDER}.ex5 Symbol: {symbol} Period: {period}")
print(f" Range: {from_date}{to_date} Visual: {visual}")
t0 = time.time()
subprocess.run([str(mt5_path / "terminal64.exe"), f"/config:{ini}"], timeout=timeout_sec)
metrics = parse_report(data, report)
metrics["elapsed_sec"] = round(time.time() - t0, 1)
metrics["mode"] = mode
return metrics
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="RSIScalpingAdaptive MT5 Strategy Tester")
p.add_argument("mode", choices=["backtest", "optimize"])
p.add_argument("--symbol", default="XAUUSD")
p.add_argument("--period", default="H1", choices=["M15", "M30", "H1", "H4"])
p.add_argument("--from", dest="from_date", default="2004.01.01")
p.add_argument("--to", dest="to_date", default="2026.01.01")
p.add_argument("--deposit", type=float, default=10000)
p.add_argument("--leverage", type=int, default=100)
p.add_argument("--visual", action="store_true")
p.add_argument("--set", dest="set_file", default="")
p.add_argument("--expert", choices=["single", "super"], default="single")
p.add_argument("--timeout", type=int, default=7200)
return p.parse_args()
def main() -> None:
args = parse_args()
ctx = mt5_context()
if args.expert == "super":
set_path = Path(args.set_file) if args.set_file else SUPER_SET
report = f"{SUPER_EX5}_{args.mode}"
symbol = args.symbol if args.symbol != "XAUUSD" or args.set_file else "EURUSD"
else:
set_path = Path(args.set_file) if args.set_file else (OPT_SET if args.mode == "optimize" else DEFAULT_SET)
report = f"RSIScalpingAdaptive_{args.symbol}_{args.mode}"
symbol = args.symbol
set_name = set_path.name
metrics = run_tester(
ctx,
mode=args.mode,
set_path=set_path,
set_name=set_name,
report=report,
symbol=symbol,
period=args.period,
from_date=args.from_date,
to_date=args.to_date,
deposit=args.deposit,
leverage=args.leverage,
visual=args.visual,
expert=args.expert,
timeout_sec=args.timeout,
)
if metrics.get("ready"):
print("\n=== MT5 Strategy Tester Report ===")
for k in ("net_profit", "profit_factor", "total_trades", "sharpe", "recovery_factor", "max_drawdown", "elapsed_sec"):
if k in metrics and metrics[k] is not None:
print(f" {k}: {metrics[k]}")
print(f" report: {metrics.get('report')}")
else:
print("Report not found — open MT5 → View → Strategy Tester → Journal for errors.")
if __name__ == "__main__":
main()
@@ -0,0 +1,254 @@
"""
RSIScalpingAdaptive XAUUSD — monthly walk-forward validation (Python).
Mirrors the in-EA optimizer: each calendar month, grid-search the prior month,
pick the best score, then forward-test that month with the selected params.
Usage:
python run_walk_forward.py
python run_walk_forward.py --symbol XAUUSD --start 2023-01-01 --end 2026-01-01
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import asdict, dataclass
from datetime import datetime
from pathlib import Path
import MetaTrader5 as mt5
import pandas as pd
ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT / "backtesting" / "MT5"))
from cluster_audit.backtest_core import CostModel, load_bars, resolve_symbol # noqa: E402
from run_backtest import StrategyParams, run_backtest # noqa: E402
STRATEGY_ID = "RSIScalpingAdaptiveXAUUSD"
@dataclass
class SearchGrid:
rsi_period: tuple[int, int, int] = (12, 18, 2)
rsi_overbought: tuple[float, float, float] = (65.0, 77.0, 3.0)
rsi_oversold: tuple[float, float, float] = (50.0, 63.0, 3.0)
rsi_target_buy: tuple[float, float, float] = (75.0, 86.0, 3.0)
rsi_target_sell: tuple[float, float, float] = (50.0, 63.0, 3.0)
bars_to_wait: tuple[int, int, int] = (1, 4, 1)
min_trades: int = 8
max_combos: int = 600
weight_sharpe: float = 0.35
weight_net: float = 0.25
weight_pf: float = 0.15
weight_dd: float = 0.10
def _frange(start: float, stop: float, step: float) -> list[float]:
out: list[float] = []
v = start
while v <= stop + 1e-9:
out.append(round(v, 6))
v += step
return out
def _irange(start: int, stop: int, step: int) -> list[int]:
return list(range(start, stop + 1, step))
def score_report(report, min_trades: int, grid: SearchGrid) -> float:
if report.total_trades < min_trades or report.net_profit <= 0 or report.profit_factor < 1.05:
return float("-inf")
pf = min(report.profit_factor, 4.0) / 4.0
return (
report.sharpe * grid.weight_sharpe
+ (report.net_profit / 2000.0) * grid.weight_net
+ pf * grid.weight_pf
- report.max_drawdown_pct * grid.weight_dd
)
def is_valid(p: StrategyParams) -> bool:
return p.rsi_target_buy > p.rsi_oversold and p.rsi_target_sell < p.rsi_overbought
def iter_params(fallback: StrategyParams, grid: SearchGrid):
yield fallback
tested = 0
for rp in _irange(*grid.rsi_period):
for ob in _frange(*grid.rsi_overbought):
for os in _frange(*grid.rsi_oversold):
for tb in _frange(*grid.rsi_target_buy):
for ts in _frange(*grid.rsi_target_sell):
for bw in _irange(*grid.bars_to_wait):
if tested >= grid.max_combos:
return
p = StrategyParams(
rsi_period=rp,
rsi_overbought=ob,
rsi_oversold=os,
rsi_target_buy=tb,
rsi_target_sell=ts,
bars_to_wait=bw,
lot_size=fallback.lot_size,
initial_balance=fallback.initial_balance,
)
if is_valid(p):
tested += 1
yield p
def month_starts(start: datetime, end: datetime) -> list[pd.Timestamp]:
idx = pd.date_range(start=start, end=end, freq="MS")
return list(idx)
def previous_month_bounds(ts: pd.Timestamp) -> tuple[datetime, datetime]:
prev_end = ts - pd.Timedelta(seconds=1)
prev_start = prev_end.replace(day=1)
return prev_start.to_pydatetime(), prev_end.to_pydatetime()
def month_bounds(ts: pd.Timestamp) -> tuple[datetime, datetime]:
start = ts.to_pydatetime()
end = (ts + pd.offsets.MonthBegin(1) - pd.Timedelta(seconds=1)).to_pydatetime()
return start, end
def optimize_month(
df_all: pd.DataFrame,
symbol: str,
costs: CostModel,
opt_start: datetime,
opt_end: datetime,
fallback: StrategyParams,
grid: SearchGrid,
):
df = df_all.loc[(df_all.index >= opt_start) & (df_all.index <= opt_end)]
if len(df) < 80:
return fallback, None, 0
best_p = fallback
best_r = None
best_score = float("-inf")
combos = 0
for p in iter_params(fallback, grid):
report = run_backtest(df, symbol, p, costs, f"{opt_start.date()}_{opt_end.date()}", "H1")
combos += 1
sc = score_report(report, grid.min_trades, grid)
if sc > best_score:
best_score = sc
best_p = p
best_r = report
return best_p, best_r, combos
def forward_month(
df_all: pd.DataFrame,
symbol: str,
costs: CostModel,
fwd_start: datetime,
fwd_end: datetime,
params: StrategyParams,
):
df = df_all.loc[(df_all.index >= fwd_start) & (df_all.index <= fwd_end)]
if len(df) < 20:
return None
return run_backtest(df, symbol, params, costs, f"{fwd_start.date()}_{fwd_end.date()}", "H1")
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description=f"{STRATEGY_ID} walk-forward")
p.add_argument("--symbol", default="XAUUSD")
p.add_argument("--start", default="2023-01-01")
p.add_argument("--end", default="2026-01-01")
p.add_argument("--balance", type=float, default=10_000.0)
p.add_argument("--lot", type=float, default=0.1)
return p.parse_args()
def main() -> None:
args = parse_args()
out_dir = Path(__file__).resolve().parent
fallback = StrategyParams(lot_size=args.lot, initial_balance=args.balance)
grid = SearchGrid()
if not mt5.initialize():
raise SystemExit("MetaTrader5 initialize() failed")
try:
symbol = resolve_symbol(args.symbol)
start = datetime.fromisoformat(args.start)
end = datetime.fromisoformat(args.end)
warmup = start - pd.Timedelta(days=45)
print(f"Loading {symbol} H1 bars from {warmup.date()} to {end.date()} ...")
df_all = load_bars(symbol, mt5.TIMEFRAME_H1, warmup.to_pydatetime(), end)
costs = CostModel.for_symbol(symbol)
rows = []
cumulative = 0.0
for month_ts in month_starts(start, end):
if month_ts.to_pydatetime() >= end:
break
opt_start, opt_end = previous_month_bounds(month_ts)
fwd_start, fwd_end = month_bounds(month_ts)
if fwd_start >= end:
continue
best_p, opt_report, combos = optimize_month(
df_all, symbol, costs, opt_start, opt_end, fallback, grid
)
fwd_report = forward_month(df_all, symbol, costs, fwd_start, fwd_end, best_p)
if fwd_report is None:
continue
cumulative += fwd_report.net_profit
rows.append(
{
"month": str(month_ts.date())[:7],
"opt_window": f"{opt_start.date()}..{opt_end.date()}",
"combos_tested": combos,
"selected": asdict(best_p),
"opt_net": opt_report.net_profit if opt_report else 0.0,
"opt_sharpe": opt_report.sharpe if opt_report else 0.0,
"fwd_net": fwd_report.net_profit,
"fwd_trades": fwd_report.total_trades,
"fwd_sharpe": fwd_report.sharpe,
"fwd_pf": fwd_report.profit_factor,
"fwd_dd_pct": fwd_report.max_drawdown_pct,
"cumulative_net": cumulative,
}
)
print(
f"{rows[-1]['month']} | opt ${rows[-1]['opt_net']:,.0f} "
f"-> fwd ${rows[-1]['fwd_net']:,.0f} | cum ${cumulative:,.0f} | "
f"RSI={best_p.rsi_period} OB={best_p.rsi_overbought} OS={best_p.rsi_oversold}"
)
summary = {
"strategy": STRATEGY_ID,
"symbol": symbol,
"start": args.start,
"end": args.end,
"months": len(rows),
"cumulative_net": cumulative,
"rows": rows,
}
out_path = out_dir / "walk_forward_report.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
pd.DataFrame(rows).to_csv(out_dir / "walk_forward_monthly.csv", index=False)
print(f"\nWalk-forward cumulative net: ${cumulative:,.2f} over {len(rows)} months")
print(f"Saved {out_path}")
finally:
mt5.shutdown()
if __name__ == "__main__":
main()
@@ -0,0 +1,295 @@
"""
XAUUSD H1 optimizer — MetaQuotes Demo history from 2004.
Phase 1: fast random search (full + OOS only)
Phase 2: stability check (year/month win rates) on top candidates
"""
from __future__ import annotations
import argparse
import json
import random
import sys
from dataclasses import asdict, dataclass
from datetime import datetime
from pathlib import Path
import MetaTrader5 as mt5
import pandas as pd
ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT / "backtesting" / "MT5"))
from rsi_scalping_backtest import ( # noqa: E402
CostModel,
RsiScalpParams,
backtest_rsi_scalping,
load_rates,
split_walk_forward,
)
@dataclass
class CandidateScore:
params: RsiScalpParams
full_net: float
full_trades: int
full_pf: float
full_dd: float
full_wr: float
oos_net: float
oos_trades: int
oos_pf: float
oos_dd: float
win_year_pct: float
win_month_pct: float
score: float
def yearly_stats(df: pd.DataFrame, symbol: str, params: RsiScalpParams, costs: CostModel, balance: float) -> float:
wins = total = 0
for _, chunk in df.groupby(df.index.year):
if len(chunk) < 200:
continue
r = backtest_rsi_scalping(chunk, symbol, params, balance, costs=costs)
total += 1
if r.net_profit > 0:
wins += 1
return (100.0 * wins / total) if total else 0.0
def monthly_stats(df: pd.DataFrame, symbol: str, params: RsiScalpParams, costs: CostModel, balance: float) -> float:
wins = total = 0
for _, chunk in df.groupby(pd.Grouper(freq="ME")):
if len(chunk) < 30:
continue
r = backtest_rsi_scalping(chunk, symbol, params, balance, costs=costs)
total += 1
if r.net_profit > 0:
wins += 1
return (100.0 * wins / total) if total else 0.0
def fast_score(full_r, oos_r) -> float:
if full_r.total_trades < 200 or oos_r.total_trades < 80:
return float("-inf")
if full_r.net_profit <= 0 or oos_r.net_profit <= 0:
return float("-inf")
if full_r.profit_factor < 1.08 or oos_r.profit_factor < 1.05:
return float("-inf")
if full_r.max_drawdown_pct > 35 or oos_r.max_drawdown_pct > 45:
return float("-inf")
pf = min(full_r.profit_factor, 3.0) / 3.0
oos_pf = min(oos_r.profit_factor, 3.0) / 3.0
return (
(full_r.net_profit / 5000.0) * 0.35
+ (oos_r.net_profit / 3000.0) * 0.35
+ pf * 0.15
+ oos_pf * 0.15
- full_r.max_drawdown_pct * 0.05
- oos_r.max_drawdown_pct * 0.03
)
def final_score(full_r, oos_r, win_year_pct: float, win_month_pct: float) -> float:
base = fast_score(full_r, oos_r)
if base == float("-inf"):
return base
if win_year_pct < 55 or win_month_pct < 52:
return float("-inf")
return base + (win_year_pct / 100.0) * 0.20 + (win_month_pct / 100.0) * 0.12
def sample_params(rng: random.Random, lot: float) -> RsiScalpParams:
inverted = rng.random() < 0.55
if inverted:
ob = rng.uniform(4.0, 22.0)
os = rng.uniform(52.0, 78.0)
tb = rng.uniform(85.0, 99.0)
ts = rng.uniform(4.0, 55.0)
else:
ob = rng.uniform(62.0, 82.0)
os = rng.uniform(38.0, 58.0)
tb = rng.uniform(72.0, 92.0)
ts = rng.uniform(18.0, 62.0)
if tb <= os:
tb = os + 5
if ts >= ob:
ts = ob - 5
use_trail = rng.random() < 0.25
return RsiScalpParams(
rsi_period=rng.choice([10, 12, 14, 16, 18, 21]),
rsi_overbought=round(ob, 1),
rsi_oversold=round(os, 1),
rsi_target_buy=round(tb, 1),
rsi_target_sell=round(ts, 1),
bars_to_wait=rng.choice([1, 2, 3, 4, 6, 8, 12]),
use_trailing=use_trail,
trail_distance_pts=rng.choice([40, 55, 71, 90, 120, 150]),
trail_activation_pts=rng.choice([20, 35, 41, 55, 70, 90]),
lot_size=lot,
)
def parse_args():
p = argparse.ArgumentParser()
p.add_argument("--symbol", default="XAUUSD")
p.add_argument("--start", default="2004-01-01")
p.add_argument("--end", default="2026-01-01")
p.add_argument("--trials", type=int, default=3000)
p.add_argument("--lot", type=float, default=0.1)
p.add_argument("--balance", type=float, default=10_000.0)
p.add_argument("--seed", type=int, default=7)
p.add_argument("--train-ratio", type=float, default=0.65)
p.add_argument("--top-k", type=int, default=40)
return p.parse_args()
def main():
args = parse_args()
out_dir = Path(__file__).resolve().parent
if not mt5.initialize():
raise SystemExit("MT5 init failed")
try:
start = datetime.fromisoformat(args.start)
end = datetime.fromisoformat(args.end)
df = load_rates(args.symbol, mt5.TIMEFRAME_H1, start, end)
train_df, test_df = split_walk_forward(df, args.train_ratio)
costs = CostModel.from_symbol(args.symbol, slippage_points=3.0)
print(f"Loaded {len(df)} H1 bars {df.index[0]} -> {df.index[-1]}")
print(f"Train {len(train_df)} | Test {len(test_df)}")
rng = random.Random(args.seed)
rows: list[dict] = []
for n in range(1, args.trials + 1):
p = sample_params(rng, args.lot)
full_r = backtest_rsi_scalping(df, args.symbol, p, args.balance, costs=costs)
oos_r = backtest_rsi_scalping(test_df, args.symbol, p, args.balance, costs=costs)
sc = fast_score(full_r, oos_r)
rows.append(
{
"trial": n,
"fast_score": sc,
"full_net": full_r.net_profit,
"full_trades": full_r.total_trades,
"full_pf": full_r.profit_factor,
"full_dd": full_r.max_drawdown_pct,
"oos_net": oos_r.net_profit,
"oos_trades": oos_r.total_trades,
"oos_pf": oos_r.profit_factor,
"oos_dd": oos_r.max_drawdown_pct,
**asdict(p),
}
)
if n % 500 == 0:
valid = [r for r in rows if r["fast_score"] > float("-inf")]
msg = f"trial {n}/{args.trials} valid={len(valid)}"
if valid:
top = max(valid, key=lambda r: r["fast_score"])
msg += f" best_fast={top['fast_score']:.3f} full=${top['full_net']:,.0f} dd={top['full_dd']:.1f}%"
print(msg)
df_rows = pd.DataFrame(rows)
df_rows.sort_values("fast_score", ascending=False).to_csv(out_dir / "xauusd_opt_trials.csv", index=False)
candidates = df_rows[df_rows["fast_score"] > float("-inf")].head(args.top_k)
if candidates.empty:
candidates = df_rows[(df_rows["full_net"] > 0) & (df_rows["oos_net"] > 0)].sort_values(
"oos_net", ascending=False
).head(args.top_k)
if candidates.empty:
raise SystemExit("No profitable candidate found")
print(f"\nStability check on top {len(candidates)} candidates ...")
best: CandidateScore | None = None
for _, row in candidates.iterrows():
p = RsiScalpParams.from_dict({k: row[k] for k in RsiScalpParams.__dataclass_fields__})
full_r = backtest_rsi_scalping(df, args.symbol, p, args.balance, costs=costs)
oos_r = backtest_rsi_scalping(test_df, args.symbol, p, args.balance, costs=costs)
wy = yearly_stats(df, args.symbol, p, costs, args.balance)
wm = monthly_stats(df, args.symbol, p, costs, args.balance)
sc = final_score(full_r, oos_r, wy, wm)
if sc == float("-inf"):
continue
cand = CandidateScore(
params=p,
full_net=full_r.net_profit,
full_trades=full_r.total_trades,
full_pf=full_r.profit_factor,
full_dd=full_r.max_drawdown_pct,
full_wr=full_r.win_rate,
oos_net=oos_r.net_profit,
oos_trades=oos_r.total_trades,
oos_pf=oos_r.profit_factor,
oos_dd=oos_r.max_drawdown_pct,
win_year_pct=wy,
win_month_pct=wm,
score=sc,
)
if best is None or cand.score > best.score:
best = cand
if best is None:
row = candidates.iloc[0]
p = RsiScalpParams.from_dict({k: row[k] for k in RsiScalpParams.__dataclass_fields__})
full_r = backtest_rsi_scalping(df, args.symbol, p, args.balance, costs=costs)
oos_r = backtest_rsi_scalping(test_df, args.symbol, p, args.balance, costs=costs)
best = CandidateScore(
params=p,
full_net=full_r.net_profit,
full_trades=full_r.total_trades,
full_pf=full_r.profit_factor,
full_dd=full_r.max_drawdown_pct,
full_wr=full_r.win_rate,
oos_net=oos_r.net_profit,
oos_trades=oos_r.total_trades,
oos_pf=oos_r.profit_factor,
oos_dd=oos_r.max_drawdown_pct,
win_year_pct=yearly_stats(df, args.symbol, p, costs, args.balance),
win_month_pct=monthly_stats(df, args.symbol, p, costs, args.balance),
score=float(row["fast_score"]),
)
report = {
"symbol": args.symbol,
"period": [args.start, args.end],
"trials": args.trials,
"best": {
"params": asdict(best.params),
"full_net": best.full_net,
"full_trades": best.full_trades,
"full_pf": best.full_pf,
"full_dd": best.full_dd,
"full_wr": best.full_wr,
"oos_net": best.oos_net,
"oos_trades": best.oos_trades,
"oos_pf": best.oos_pf,
"oos_dd": best.oos_dd,
"win_year_pct": best.win_year_pct,
"win_month_pct": best.win_month_pct,
"score": best.score,
},
}
json_path = out_dir / "xauusd_best_params.json"
json_path.write_text(json.dumps(report, indent=2), encoding="utf-8")
print("\n=== BEST XAUUSD PARAMS ===")
for k, v in asdict(best.params).items():
print(f" {k}: {v}")
print(f" FULL net=${best.full_net:,.2f} trades={best.full_trades} PF={best.full_pf:.2f} DD={best.full_dd:.1f}%")
print(f" OOS net=${best.oos_net:,.2f} trades={best.oos_trades} PF={best.oos_pf:.2f} DD={best.oos_dd:.1f}%")
print(f" Win years={best.win_year_pct:.1f}% Win months={best.win_month_pct:.1f}%")
print(f"Saved {json_path}")
finally:
mt5.shutdown()
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+142
View File
@@ -0,0 +1,142 @@
# SimpleEMA — 练手实验室
双 EMA 金叉/死叉策略,默认货币对 **EURUSD H1**(流动性好、点差低,适合入门优化)。
## 策略逻辑
| 项目 | 规则 |
|------|------|
| 入场 | 快 EMA 上穿/下穿慢 EMA(收盘 K 确认) |
| 出场 | 反向交叉 / ATR 或固定 SL·TP / 最大持仓 K 线数 / 可选 trailing |
| 过滤 | 最大点差、最小 EMA 间距 |
## 文件
| 文件 | 用途 |
|------|------|
| `main.mq5` | MT5 EAStrategy Tester / 实盘) |
| `SimpleEMA_EURUSD.set` | 默认参数 |
| `SimpleEMA_Genetic_Optimization.set` | 遗传优化范围 |
| `run_mt5_tester.py` | **调 MT5 原生 Strategy Tester**(你要的实时回测) |
| `run_backtest.py` | Python 快速回测(MT5 拉历史 K 线) |
| `run_optimize.py` | Python 随机搜索优化 |
| `trades.csv` | 逐单复盘(Python 回测产出) |
## 1. MT5 原生回测(推荐)
先确保 MT5 已登录,EURUSD H1 历史数据已下载。
```powershell
cd lab\EAs\SimpleEMA
# 单次回测(自动编译 EA → 启动 Strategy Tester → 生成 HTML 报告)
python run_mt5_tester.py backtest
# 可视化模式:看 K 线一根根跑(实时感最强)
python run_mt5_tester.py backtest --visual
# 遗传优化(Optimization=2,用 SimpleEMA_Genetic_Optimization.set
python run_mt5_tester.py optimize
```
回测完成后:
- HTML 报告路径会打印在终端(通常在 `%APPDATA%\MetaQuotes\Terminal\...\SimpleEMA_EURUSD_backtest.htm`
- 在 MT5 **结果 → 报告** 里可逐单查看开平仓、滑点、盈亏
- 优化结果在 **Optimization Results** 标签页,右键可 **Set as Input**
## 2. Python 快速迭代(改逻辑 → 立刻看 trades.csv
```powershell
python run_backtest.py
python run_backtest.py --start 2024-01-01 --fast 10 --slow 30
```
产出:`trades.csv`(每单 side / 开平时间 / 价格 / profit / exit_reason)、`report.png`
## 4. 多品种组合(20 品种)
### 分品种调参 + 组合(推荐)
```powershell
# 每个品种独立随机搜索,自动剔除 net<=0 / PF<1 的品种,再跑组合回测
python run_optimize_portfolio.py --trials 350
# 仅用已有 portfolio_params.json 重跑组合
python run_optimize_portfolio.py --skip-opt
# 验证组合
python run_portfolio_v5.py
```
产出:`portfolio_params.json`(每品种最优参数 + enabled 标记)、`portfolio_opt_trials/*.csv``best_run/portfolio_trades.csv`
### 统一参数(对比用)
```powershell
python run_portfolio_v5.py --shared-params best_params.json
```
| 文件 | 用途 |
|------|------|
| `portfolio_symbols.json` | 20 品种列表 + 各品种最大点差 |
| `portfolio_curated.json` | 全扫描后 net>0 的子集 |
| `run_portfolio_v5.py` | 组合回测,产出 `portfolio_report.json` |
| `main_portfolio.mq5` | MT5 多品种 EA(挂任意图表,监控 SymbolList 内全部品种) |
MT5 组合 EA
```powershell
python run_mt5_tester.py backtest --ea main_portfolio.mq5 --period M15 --from 2020.01.01 --to 2026.01.01
```
## 3. Python 随机搜索优化
```powershell
python run_optimize.py --trials 500
```
产出:`optimize_trials.csv``best_params.json``best_run/trades.csv`
`best_params.json` 里的值填回 `.set``main.mq5` input,再用 `run_mt5_tester.py optimize` 做 MT5 遗传精调。
## 5. MT5 回测(唯一准绳)
**2598 笔是 Python 组合模拟;`SimpleEMA_report.pdf` 只是单品种 EURUSD~115 笔)。**
组合请以 MT5 为准:
```powershell
# 12 个启用品种各跑一遍 MT5 Strategy Tester(每品种独立 .set
python run_mt5_portfolio.py --from 2020.01.01 --to 2026.01.01
# 从 MT5 HTML 报告汇总生成正式报告
python generate_mt5_portfolio_report.py
```
产出:
- `best_run/mt5_results.json` — MT5 汇总(交易数、净利)
- `best_run/mt5_reports/*.htm` — 各品种 MT5 原生报告(逐单复盘)
- `best_run/MT5_PORTFOLIO_REPORT.md` — 组合说明
- `best_run/SimpleEMA_report.png` — 由 MT5 数据生成的组合图
Python `portfolio_trades.csv` / `run_portfolio_v5.py` 仅用于快速迭代参数,**不作最终成绩**。
```
改 main.mq5 逻辑
python run_backtest.py ← 秒级验证 + trades.csv 逐单复盘
python run_optimize.py ← 粗搜参数空间
python run_mt5_tester.py optimize ← MT5 遗传优化确认
python run_mt5_tester.py backtest --visual ← 目视检查
```
## 手动在 MT5 里操作
1.`main.mq5` 复制到 `MQL5/Experts/` 或用 MetaEditor 打开编译
2. Strategy TesterExpert = `SimpleEMA`Symbol = `EURUSD`Period = `H1`
3. Inputs → Load → `SimpleEMA_EURUSD.set`
4. 优化时 Load → `SimpleEMA_Genetic_Optimization.set`Optimization = **Genetic**
+21
View File
@@ -0,0 +1,21 @@
; SimpleEMA — default inputs for EURUSD H1 practice
; Load in Strategy Tester → Inputs → Load
Timeframe=16385
MagicNumber=20260620
FastEmaPeriod=12
SlowEmaPeriod=26
MinEmaGapPips=0.0
LotSize=0.10
UseAtrStops=true
AtrPeriod=14
AtrSlMult=1.5
AtrTpMult=2.5
StopLossPips=30
TakeProfitPips=60
UseTrailing=false
TrailPips=20
ExitOnCross=true
MaxBarsInTrade=48
MaxSpreadPips=5
OneTradeOnly=true
@@ -0,0 +1,29 @@
; SimpleEMA — genetic optimization ranges (EURUSD H1)
; Format: Name=Default||Min||Step||Max||Y/N
; Load: Strategy Tester → Inputs → Load, then Optimization → Genetic
; === fixed ===
Timeframe=16385||16385||0||16385||N
MagicNumber=20260620||20260620||1||20260620||N
LotSize=0.10||0.10||0||0.10||N
UseAtrStops=true||false||0||true||N
OneTradeOnly=true||true||0||true||N
ExitOnCross=true||false||0||true||N
UseTrailing=false||false||0||true||N
; === EMA ===
FastEmaPeriod=12||8||2||20||Y
SlowEmaPeriod=26||20||2||60||Y
MinEmaGapPips=0.0||0.0||1.0||8.0||Y
; === ATR stops ===
AtrPeriod=14||10||2||20||Y
AtrSlMult=1.5||1.0||0.25||3.0||Y
AtrTpMult=2.5||1.5||0.25||4.0||Y
StopLossPips=30||15||5||60||Y
TakeProfitPips=60||30||10||120||Y
TrailPips=20||10||5||40||Y
; === exits / filters ===
MaxBarsInTrade=48||0||12||96||Y
MaxSpreadPips=5||0||1||8||Y
+26
View File
@@ -0,0 +1,26 @@
; SimpleEMA v5 — trend-leg cross + pullback
Timeframe=16388
FastEmaPeriod=11
SlowEmaPeriod=34
TrendLegBars=56
MinEmaGapPips=1.5
CrossCooldown=6
PullbackCooldown=5
UsePullback=true
PullbackTouch=0
PullbackAdxMin=25.0
PullbackMinGapPips=2.9
MaxPullbacksPerLeg=1
AtrPeriod=14
AtrSlMult=2.54
AtrTpMult=4.84
MaxBarsInTrade=80
HtfEmaPeriod=100
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=6.0
LotSize=0.1
+21
View File
@@ -0,0 +1,21 @@
; SimpleEMA — profitable low-frequency preset (~82 trades / 6y)
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=46
EntryMode=0
MinEmaGapPips=1.5
CooldownBars=8
UseAtrStops=true
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
ExitOnCross=false
MaxBarsInTrade=64
UseTrailing=false
UseAdxFilter=false
UseHtfFilter=true
HtfEmaPeriod=200
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=6
LotSize=0.10
+39
View File
@@ -0,0 +1,39 @@
{
"version": 5,
"target_met": false,
"params": {
"fast_ema": 11,
"slow_ema": 34,
"trend_leg_bars": 56,
"min_ema_gap_pips": 1.5,
"cross_cooldown": 6,
"pullback_cooldown": 5,
"use_pullback": true,
"pullback_touch": 0,
"pullback_adx_min": 25.0,
"pullback_min_gap_pips": 2.9,
"max_pullbacks_per_leg": 1,
"atr_period": 14,
"atr_sl_mult": 2.54,
"atr_tp_mult": 4.84,
"max_bars_in_trade": 80,
"htf_ema_period": 100,
"use_htf_filter": true,
"use_adx_filter": false,
"adx_period": 14,
"adx_min": 18.0,
"session_start": 8,
"session_end": 22,
"max_spread_pips": 6.0,
"lot_size": 0.1,
"initial_balance": 10000.0
},
"metrics": {
"net_profit": 315.0799999999963,
"total_trades": 115,
"win_rate": 40.869565217391305,
"profit_factor": 1.2081522098170046,
"max_drawdown_pct": 2.1473621754491634,
"sharpe": 0.4947439712557756
}
}
-21
View File
@@ -1,21 +0,0 @@
\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.
+448
View File
@@ -0,0 +1,448 @@
#!/usr/bin/env python3
"""Generate SimpleEMA LaTeX report -> PDF + PNG.
WARNING: Reads Python backtest (single-symbol). Portfolio official report:
best_run/MT5_PORTFOLIO_REPORT.md (from MT5 Strategy Tester)
"""
from __future__ import annotations
import json
import shutil
import subprocess
import textwrap
from datetime import datetime
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
ROOT = Path(__file__).resolve().parent
OUT = ROOT / "best_run"
FIG = OUT / "figures"
TEX = OUT / "SimpleEMA_report.tex"
PDF = OUT / "SimpleEMA_report.pdf"
PNG = OUT / "SimpleEMA_report.png"
plt.rcParams.update({"figure.dpi": 150, "savefig.dpi": 150, "font.size": 9})
def latex_escape(s: str) -> str:
for a, b in (("\\", "\\textbackslash{}"), ("&", "\\&"), ("%", "\\%"),
("$", "\\$"), ("#", "\\#"), ("_", "\\_"), ("{", "\\{"), ("}", "\\}")):
s = s.replace(a, b)
return s
def load_data() -> tuple[dict, dict, pd.DataFrame]:
with open(ROOT / "best_params.json", encoding="utf-8") as f:
bp = json.load(f)
summary_path = OUT / "report.json"
if summary_path.exists():
summary = json.loads(summary_path.read_text(encoding="utf-8"))
else:
summary = bp.get("metrics", {})
trades = pd.read_csv(OUT / "trades.csv")
trades["open_time"] = pd.to_datetime(trades["open_time"])
trades["close_time"] = pd.to_datetime(trades["close_time"])
return bp, summary, trades
def save_figures(trades: pd.DataFrame, summary: dict) -> None:
FIG.mkdir(parents=True, exist_ok=True)
bal0 = summary.get("initial_balance", 10_000.0)
eq = bal0 + trades.sort_values("close_time")["profit"].cumsum()
times = trades.sort_values("close_time")["close_time"]
dd = (eq - eq.cummax()) / eq.cummax() * 100
fig, ax = plt.subplots(figsize=(8, 3.2))
ax.plot(times, eq, color="#2ca02c", lw=1.6)
ax.axhline(bal0, ls="--", color="#888", lw=0.8)
ax.set_title("Equity Curve")
ax.set_ylabel("Balance (USD)")
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(FIG / "equity.pdf", bbox_inches="tight")
fig.savefig(FIG / "equity.png", bbox_inches="tight")
plt.close(fig)
fig, ax = plt.subplots(figsize=(8, 2.8))
ax.fill_between(times, dd, 0, color="#d62728", alpha=0.35)
ax.plot(times, dd, color="#8b0000", lw=0.8)
ax.set_title("Drawdown")
ax.set_ylabel("Drawdown (%)")
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(FIG / "drawdown.pdf", bbox_inches="tight")
fig.savefig(FIG / "drawdown.png", bbox_inches="tight")
plt.close(fig)
monthly = trades.copy()
monthly["month"] = monthly["close_time"].dt.to_period("M")
mp = monthly.groupby("month")["profit"].sum()
fig, ax = plt.subplots(figsize=(8, 3))
colors = ["#2ca02c" if v >= 0 else "#d62728" for v in mp.values]
ax.bar(range(len(mp)), mp.values, color=colors, width=0.85)
ax.set_title("Monthly PnL")
ax.set_ylabel("USD")
ax.axhline(0, color="black", lw=0.6)
ax.set_xticks(range(0, len(mp), max(1, len(mp) // 8)))
ax.set_xticklabels([str(m) for m in mp.index[:: max(1, len(mp) // 8)]], rotation=45, ha="right")
fig.tight_layout()
fig.savefig(FIG / "monthly.pdf", bbox_inches="tight")
fig.savefig(FIG / "monthly.png", bbox_inches="tight")
plt.close(fig)
rc = trades["exit_reason"].value_counts()
fig, ax = plt.subplots(figsize=(5, 3))
ax.bar(rc.index.astype(str), rc.values, color="#ff7f0e")
ax.set_title("Exit Reasons")
ax.set_ylabel("Count")
fig.tight_layout()
fig.savefig(FIG / "exits.pdf", bbox_inches="tight")
fig.savefig(FIG / "exits.png", bbox_inches="tight")
plt.close(fig)
fig, ax = plt.subplots(figsize=(5, 3))
ax.hist(trades["profit"], bins=20, color="#9467bd", alpha=0.85, edgecolor="white")
ax.axvline(0, color="black", lw=0.8)
ax.set_title("Per-Trade PnL Distribution")
ax.set_xlabel("Profit (USD)")
fig.tight_layout()
fig.savefig(FIG / "pnl_hist.pdf", bbox_inches="tight")
fig.savefig(FIG / "pnl_hist.png", bbox_inches="tight")
plt.close(fig)
def trade_table_rows(trades: pd.DataFrame, n: int = 12, best: bool = True) -> str:
col = "profit"
sub = trades.nlargest(n, col) if best else trades.nsmallest(n, col)
lines = []
for _, r in sub.iterrows():
lines.append(
f"{r['side']} & {r['open_time'].strftime('%Y-%m-%d %H:%M')} & "
f"{r['close_time'].strftime('%Y-%m-%d %H:%M')} & "
f"{r['profit']:.2f} & {latex_escape(str(r['exit_reason']))} \\\\"
)
return "\n".join(lines)
def build_tex(bp: dict, summary: dict, trades: pd.DataFrame) -> str:
p = bp["params"]
version = int(bp.get("version", 2))
net = summary.get("net_profit", 0)
if version >= 5:
param_rows = [
("快 EMA / 慢 EMA", f"{p['fast_ema']} / {p['slow_ema']}"),
("入场", "交叉 + 趋势段回调" if p.get("use_pullback") else "仅交叉"),
("趋势段长度", f"{p.get('trend_leg_bars', '-')} bars"),
("交叉冷却", f"{p.get('cross_cooldown', '-')} bars"),
("回调冷却", f"{p.get('pullback_cooldown', '-')} bars"),
("回调 ADX 下限", str(p.get("pullback_adx_min", "-"))),
("回调最小间距", f"{p.get('pullback_min_gap_pips', '-')} pips"),
("每段最多回调", str(p.get("max_pullbacks_per_leg", 1))),
("ATR 周期", str(p["atr_period"])),
("止损 SL", f"ATR $\\times$ {p['atr_sl_mult']}"),
("止盈 TP", f"ATR $\\times$ {p['atr_tp_mult']}"),
("最大持仓", f"{p['max_bars_in_trade']} bars M15"),
("H4 EMA 过滤", f"EMA({p['htf_ema_period']})" if p.get("use_htf_filter") else ""),
("交易时段 (UTC)", f"{p['session_start']}:00 -- {p['session_end']}:00"),
("最大点差", f"{p['max_spread_pips']} pips"),
("手数", str(p["lot_size"])),
]
logic_note = (
"v5 逻辑:EMA 交叉为主入场;仅在活跃趋势段内允许一次高质量回调"
"(ADX/间距过滤),避免 v3 多层过滤导致样本过少。"
)
else:
param_rows = [
("快 EMA / 慢 EMA", f"{p['fast_ema']} / {p['slow_ema']}"),
("入场模式", "EMA 交叉 (mode=0)"),
("最小 EMA 间距", f"{p['min_ema_gap_pips']} pips"),
("冷却 K 线", str(p["cooldown_bars"])),
("ATR 周期", str(p["atr_period"])),
("止损 SL", f"ATR $\\times$ {p['atr_sl_mult']}"),
("止盈 TP", f"ATR $\\times$ {p['atr_tp_mult']}"),
("反向交叉平仓", "" if not p.get("exit_on_cross") else ""),
("最大持仓", f"{p['max_bars_in_trade']} bars M15"),
("H4 EMA 过滤", f"EMA({p['htf_ema_period']})" if p.get("use_htf_filter") else ""),
("交易时段 (UTC)", f"{p['session_start']}:00 -- {p['session_end']}:00"),
("最大点差", f"{p['max_spread_pips']} pips"),
("手数", str(p["lot_size"])),
]
logic_note = "v2 逻辑:EMA 交叉 + H4 趋势过滤。"
param_tex = "\n".join(f"{k} & {v} \\\\" for k, v in param_rows)
if version >= 5:
strategy_tex = textwrap.dedent(rf"""
\begin{{enumerate}}
\item \textbf{{交叉入场}}M15 EMA({p["fast_ema"]}/{p["slow_ema"]}) 金叉/死叉 + H4 趋势过滤。
\item \textbf{{回调入场}}:仅在趋势段({p.get("trend_leg_bars", 48)} bars)内,价格回踩 EMA 后收回;ADX $\ge$ {p.get("pullback_adx_min", 0)};每段最多 {p.get("max_pullbacks_per_leg", 1)} 次。
\item \textbf{{过滤}}UTC {p["session_start"]}:00--{p["session_end"]}:00;点差 $\le$ {p["max_spread_pips"]} pips。
\item \textbf{{风控}}SL = ATR({p["atr_period"]}) $\times$ {p["atr_sl_mult"]}TP = ATR $\times$ {p["atr_tp_mult"]}
\item \textbf{{冷却}}:交叉 {p.get("cross_cooldown", "-")} bars;回调 {p.get("pullback_cooldown", "-")} bars。
\end{{enumerate}}
""")
summary_note = (
f"未达到 2000--3000 笔目标(当前 {summary.get('total_trades', len(trades))} 笔),"
f"但 v5 在 v2 约 81 笔基础上提升到 {summary.get('total_trades', len(trades))} 笔且保持 PF>1。"
+ logic_note
)
else:
strategy_tex = textwrap.dedent(rf"""
\begin{{enumerate}}
\item \textbf{{入场}}M15 上 EMA({p["fast_ema"]}/{p["slow_ema"]}) 金叉/死叉,最小间距 {p["min_ema_gap_pips"]} pips。
\item \textbf{{过滤}}:价格须在 H4 EMA({p["htf_ema_period"]}) 趋势同侧;UTC {p["session_start"]}:00--{p["session_end"]}:00;点差 $\le$ {p["max_spread_pips"]} pips。
\item \textbf{{风控}}SL = ATR({p["atr_period"]}) $\times$ {p["atr_sl_mult"]}TP = ATR $\times$ {p["atr_tp_mult"]}
\item \textbf{{出场}}:触及 SL/TP,或持仓超过 {p["max_bars_in_trade"]} 根 M15 K 线。
\item \textbf{{冷却}}:每笔交易后等待 {p.get("cooldown_bars", "-")} 根 K 线再入场。
\end{{enumerate}}
""")
summary_note = (
f"未达到 2000--3000 笔交易目标(当前 {summary.get('total_trades', len(trades))} 笔)。"
+ logic_note
)
exit_counts = trades["exit_reason"].value_counts()
exit_tex = "\n".join(
f"{latex_escape(str(k))} & {v} & {v / len(trades) * 100:.1f}\\% \\\\" for k, v in exit_counts.items()
)
return textwrap.dedent(rf"""
\documentclass[11pt,a4paper]{{ctexart}}
\usepackage{{graphicx}}
\usepackage{{booktabs}}
\usepackage{{geometry}}
\usepackage{{float}}
\usepackage{{xcolor}}
\usepackage{{hyperref}}
\geometry{{margin=2cm}}
\definecolor{{pos}}{{RGB}}{{44,160,44}}
\definecolor{{neg}}{{RGB}}{{214,39,40}}
\title{{SimpleEMA 最优参数回测报告\\ \large EURUSD M15 · 2020--2026 · 最终版}}
\author{{自动生成 · lab/EAs/SimpleEMA}}
\date{{{datetime.now().strftime("%Y-%m-%d")}}}
\begin{{document}}
\maketitle
\section{{执行摘要}}
本报告为 SimpleEMA 策略在修复 trailing-stop 模拟 bug 后,经 6000+ 次随机搜索得到的\textbf{{真实最优}}参数配置。
回测含点差与滑点,非 MT5 测试器 HTML 导出。
\begin{{table}}[H]
\centering
\caption{{关键绩效指标}}
\begin{{tabular}}{{lr}}
\toprule
指标 & 数值 \\
\midrule
货币对 / 周期 & {latex_escape(summary.get("symbol", "EURUSD"))} / M15 \\
回测区间 & 2020-01-01 $\sim$ 2026-01-01 \\
初始资金 & \${summary.get("initial_balance", 10000):,.0f} \\
\textbf{{净利润}} & \textbf{{\textcolor{{pos}}{{+\${net:,.2f}}}}} \\
收益率 & {summary.get("return_pct", 0):.2f}\% \\
总交易数 & {summary.get("total_trades", len(trades))} \\
胜率 & {summary.get("win_rate", 0):.1f}\% \\
盈利因子 PF & {summary.get("profit_factor", 0):.2f} \\
最大回撤 & {summary.get("max_drawdown_pct", 0):.2f}\% \\
平均盈利 / 亏损 & \${summary.get("avg_win", 0):.2f} / \${summary.get("avg_loss", 0):.2f} \\
最佳 / 最差单笔 & \${summary.get("best_trade", 0):.2f} / \${summary.get("worst_trade", 0):.2f} \\
\bottomrule
\end{{tabular}}
\end{{table}}
\noindent\textbf{{说明:}}{latex_escape(summary_note)}
\section{{权益曲线与回撤}}
\begin{{figure}}[H]
\centering
\includegraphics[width=0.92\textwidth]{{figures/equity.pdf}}
\caption{{账户权益曲线}}
\end{{figure}}
\begin{{figure}}[H]
\centering
\includegraphics[width=0.92\textwidth]{{figures/drawdown.pdf}}
\caption{{回撤百分比}}
\end{{figure}}
\section{{月度盈亏与出场结构}}
\begin{{figure}}[H]
\centering
\begin{{minipage}}{{0.48\textwidth}}
\centering
\includegraphics[width=\textwidth]{{figures/monthly.pdf}}
\caption{{逐月 PnL}}
\end{{minipage}}\hfill
\begin{{minipage}}{{0.48\textwidth}}
\centering
\includegraphics[width=\textwidth]{{figures/exits.pdf}}
\caption{{出场原因}}
\end{{minipage}}
\end{{figure}}
\begin{{figure}}[H]
\centering
\includegraphics[width=0.55\textwidth]{{figures/pnl_hist.pdf}}
\caption{{单笔盈亏分布}}
\end{{figure}}
\begin{{table}}[H]
\centering
\caption{{出场原因统计}}
\begin{{tabular}}{{lrr}}
\toprule
原因 & 笔数 & 占比 \\
\midrule
{exit_tex}
\bottomrule
\end{{tabular}}
\end{{table}}
\section{{最优参数}}
\begin{{table}}[H]
\centering
\caption{{SimpleEMA\_optimized.set 对应参数}}
\begin{{tabular}}{{ll}}
\toprule
参数 & 值 \\
\midrule
{param_tex}
\bottomrule
\end{{tabular}}
\end{{table}}
\section{{策略逻辑}}
{strategy_tex}
\section{{逐单复盘(节选)}}
\subsection{{最佳 {min(12, len(trades))}}}
\begin{{table}}[H]
\centering
\small
\begin{{tabular}}{{llrrl}}
\toprule
方向 & 开仓 & 平仓 & 盈亏 & 出场 \\
\midrule
{trade_table_rows(trades, 12, True)}
\bottomrule
\end{{tabular}}
\end{{table}}
\subsection{{最差 {min(12, len(trades))}}}
\begin{{table}}[H]
\centering
\small
\begin{{tabular}}{{llrrl}}
\toprule
方向 & 开仓 & 平仓 & 盈亏 & 出场 \\
\midrule
{trade_table_rows(trades, 12, False)}
\bottomrule
\end{{tabular}}
\end{{table}}
\noindent 完整 {len(trades)} 笔交易见 \texttt{{trades.csv}}
\section{{后续验证}}
MT5 原生 Strategy Tester 验证命令:
\begin{{verbatim}}
cd lab/EAs/SimpleEMA
python run_mt5_tester.py backtest --period M15 ^
--from 2020.01.01 --to 2026.01.01 --set SimpleEMA_optimized.set
\end{{verbatim}}
\end{{document}}
""").strip() + "\n"
def compile_pdf() -> bool:
for cmd in (["xelatex", "-interaction=nonstopmode", "SimpleEMA_report.tex"],):
for _ in range(2):
r = subprocess.run(cmd, cwd=OUT, capture_output=True, text=True)
if r.returncode != 0 and "xelatex" in cmd[0]:
print(r.stdout[-2000:] if r.stdout else "")
print(r.stderr[-2000:] if r.stderr else "")
return PDF.exists()
def pdf_to_png() -> bool:
try:
import fitz # PyMuPDF
doc = fitz.open(PDF)
zoom = 200 / 72
mat = fitz.Matrix(zoom, zoom)
images = []
for page in doc:
pix = page.get_pixmap(matrix=mat, alpha=False)
images.append(pix)
if len(images) == 1:
images[0].save(PNG)
else:
# stack pages vertically into one PNG
w = max(p.width for p in images)
h = sum(p.height for p in images)
from PIL import Image
import io
canvas = Image.new("RGB", (w, h), "white")
y = 0
for pix in images:
img = Image.open(io.BytesIO(pix.tobytes("png")))
canvas.paste(img, (0, y))
y += pix.height
canvas.save(PNG, dpi=(200, 200))
doc.close()
return PNG.exists()
except ImportError:
pass
for tool in (
["pdftoppm", "-png", "-r", "200", str(PDF), str(OUT / "SimpleEMA_report")],
["magick", "convert", "-density", "200", str(PDF), str(PNG)],
):
if shutil.which(tool[0]):
subprocess.run(tool, cwd=OUT, check=False)
if tool[0] == "pdftoppm":
cand = OUT / "SimpleEMA_report-1.png"
if cand.exists():
cand.replace(PNG)
return True
if PNG.exists():
return True
# fallback: copy dashboard chart
src = FIG / "equity.png"
if src.exists():
shutil.copy2(src, PNG)
return True
return False
def main() -> None:
if not (OUT / "trades.csv").exists():
subprocess.run(["python", str(ROOT / "generate_report.py")], check=True, cwd=ROOT)
bp, summary, trades = load_data()
save_figures(trades, summary)
tex = build_tex(bp, summary, trades)
TEX.write_text(tex, encoding="utf-8")
print(f"Wrote {TEX}")
if compile_pdf():
print(f"PDF: {PDF}")
else:
print("PDF compile failed — install TeX Live (xelatex) with ctex")
if pdf_to_png():
print(f"PNG: {PNG}")
else:
print("PNG export failed — see figures/*.png")
print(f"Figures: {FIG}")
if __name__ == "__main__":
main()
@@ -0,0 +1,488 @@
#!/usr/bin/env python3
"""Generate MT5 portfolio PDF + PNG from Strategy Tester HTML reports."""
from __future__ import annotations
import json
import re
import shutil
import subprocess
import textwrap
from datetime import datetime
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
LAB = Path(__file__).resolve().parent
OUT = LAB / "best_run"
FIG = OUT / "figures"
RESULTS = OUT / "mt5_results.json"
REPORTS = OUT / "mt5_reports"
TEX = OUT / "SimpleEMA_report.tex"
PDF = OUT / "SimpleEMA_report.pdf"
PNG = OUT / "SimpleEMA_report.png"
REPORT_PNG = OUT / "report.png"
TRADES_CSV = OUT / "mt5_portfolio_trades.csv"
plt.rcParams.update({"figure.dpi": 150, "savefig.dpi": 150, "font.size": 9})
def read_html(path: Path) -> str:
text = path.read_text(encoding="utf-16", errors="ignore")
if not text.strip():
text = path.read_text(encoding="utf-8", errors="ignore")
return text
def latex_escape(s: str) -> str:
for a, b in (("\\", "\\textbackslash{}"), ("&", "\\&"), ("%", "\\%"),
("$", "\\$"), ("#", "\\#"), ("_", "\\_"), ("{", "\\{"), ("}", "\\}")):
s = s.replace(a, b)
return s
def parse_mt5_deals(html_path: Path, symbol: str) -> list[dict]:
text = read_html(html_path)
if "<b>成交</b>" not in text:
return []
section = text.split("<b>成交</b>", 1)[1].split("</table>", 1)[0]
rows: list[dict] = []
for tr in re.findall(r'<tr bgcolor="[^"]*" align=right>(.*?)</tr>', section, re.DOTALL | re.I):
cols = re.findall(r"<td[^>]*>(.*?)</td>", tr, re.DOTALL | re.I)
if len(cols) < 11:
continue
typ = re.sub(r"<[^>]+>", "", cols[3]).strip().lower()
direction = re.sub(r"<[^>]+>", "", cols[4]).strip().lower()
if typ == "balance" or direction != "out" or typ not in ("buy", "sell"):
continue
profit_s = re.sub(r"<[^>]+>", "", cols[10]).replace(" ", "").replace(",", "")
try:
profit = float(profit_s)
except ValueError:
continue
comment = re.sub(r"<[^>]+>", "", cols[12]).strip() if len(cols) > 12 else ""
cl = comment.lower()
if "sl " in cl or cl.startswith("sl"):
exit_reason = "sl"
elif "tp " in cl or cl.startswith("tp"):
exit_reason = "tp"
else:
exit_reason = "other"
close_time = pd.to_datetime(re.sub(r"<[^>]+>", "", cols[0]).strip())
rows.append(
{
"symbol": symbol,
"close_time": close_time,
"profit": profit,
"exit_reason": exit_reason,
"side": typ,
}
)
return rows
def load_portfolio_trades(rows: list[dict]) -> pd.DataFrame:
all_rows: list[dict] = []
for r in rows:
if not r.get("ready"):
continue
rep = r.get("report") or r.get("report_local")
if not rep:
cand = REPORTS / f"SimpleEMA_pf_{r['symbol']}.htm"
rep = str(cand) if cand.exists() else None
if not rep or not Path(rep).exists():
continue
all_rows.extend(parse_mt5_deals(Path(rep), r["symbol"]))
if not all_rows:
return pd.DataFrame()
return pd.DataFrame(all_rows).sort_values(["close_time", "symbol"]).reset_index(drop=True)
def portfolio_summary(trades: pd.DataFrame, pf: dict, deposit: float, n_syms: int) -> dict:
if trades.empty:
return {
"total_trades": pf.get("total_trades", 0),
"net_profit": pf.get("net_profit_sum", 0),
"win_rate": 0.0,
"profit_factor": pf.get("profit_factor_approx") or 0.0,
"max_drawdown_pct": 0.0,
"initial_balance": deposit * n_syms,
"return_pct": 0.0,
"avg_win": 0.0,
"avg_loss": 0.0,
"best_trade": 0.0,
"worst_trade": 0.0,
}
wins = trades[trades["profit"] > 0]
losses = trades[trades["profit"] < 0]
gp = wins["profit"].sum()
gl = abs(losses["profit"].sum())
initial = deposit * n_syms
eq = initial + trades["profit"].cumsum()
dd = (eq - eq.cummax()) / eq.cummax() * 100
net = trades["profit"].sum()
return {
"total_trades": len(trades),
"net_profit": round(net, 2),
"win_rate": round(len(wins) / len(trades) * 100, 1),
"profit_factor": round(gp / gl, 2) if gl > 0 else 999.0,
"max_drawdown_pct": round(abs(dd.min()), 2),
"initial_balance": initial,
"return_pct": round(net / initial * 100, 2),
"avg_win": round(wins["profit"].mean(), 2) if len(wins) else 0.0,
"avg_loss": round(losses["profit"].mean(), 2) if len(losses) else 0.0,
"best_trade": round(trades["profit"].max(), 2),
"worst_trade": round(trades["profit"].min(), 2),
}
def save_figures(trades: pd.DataFrame, sym_df: pd.DataFrame, summary: dict, pf: dict) -> None:
FIG.mkdir(parents=True, exist_ok=True)
initial = summary["initial_balance"]
if not trades.empty:
eq = initial + trades.sort_values("close_time")["profit"].cumsum()
times = trades.sort_values("close_time")["close_time"]
dd = (eq - eq.cummax()) / eq.cummax() * 100
fig, ax = plt.subplots(figsize=(8, 3.2))
ax.plot(times, eq, color="#2ca02c", lw=1.4)
ax.axhline(initial, ls="--", color="#888", lw=0.8)
ax.set_title("Portfolio Equity (MT5 deals, combined timeline)")
ax.set_ylabel("Balance (USD)")
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(FIG / "equity.pdf", bbox_inches="tight")
fig.savefig(FIG / "equity.png", bbox_inches="tight")
plt.close(fig)
fig, ax = plt.subplots(figsize=(8, 2.8))
ax.fill_between(times, dd, 0, color="#d62728", alpha=0.35)
ax.plot(times, dd, color="#8b0000", lw=0.8)
ax.set_title("Portfolio Drawdown")
ax.set_ylabel("Drawdown (%)")
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(FIG / "drawdown.pdf", bbox_inches="tight")
fig.savefig(FIG / "drawdown.png", bbox_inches="tight")
plt.close(fig)
monthly = trades.copy()
monthly["month"] = monthly["close_time"].dt.to_period("M")
mp = monthly.groupby("month")["profit"].sum()
fig, ax = plt.subplots(figsize=(8, 3))
colors = ["#2ca02c" if v >= 0 else "#d62728" for v in mp.values]
ax.bar(range(len(mp)), mp.values, color=colors, width=0.85)
ax.set_title("Monthly PnL (all symbols)")
ax.set_ylabel("USD")
ax.axhline(0, color="black", lw=0.6)
step = max(1, len(mp) // 8)
ax.set_xticks(range(0, len(mp), step))
ax.set_xticklabels([str(m) for m in mp.index[::step]], rotation=45, ha="right")
fig.tight_layout()
fig.savefig(FIG / "monthly.pdf", bbox_inches="tight")
fig.savefig(FIG / "monthly.png", bbox_inches="tight")
plt.close(fig)
rc = trades["exit_reason"].value_counts()
fig, ax = plt.subplots(figsize=(5, 3))
ax.bar(rc.index.astype(str), rc.values, color="#ff7f0e")
ax.set_title("Exit Reasons (from MT5 comments)")
ax.set_ylabel("Count")
fig.tight_layout()
fig.savefig(FIG / "exits.pdf", bbox_inches="tight")
fig.savefig(FIG / "exits.png", bbox_inches="tight")
plt.close(fig)
fig, ax = plt.subplots(figsize=(5, 3))
ax.hist(trades["profit"], bins=30, color="#9467bd", alpha=0.85, edgecolor="white")
ax.axvline(0, color="black", lw=0.8)
ax.set_title("Per-Trade PnL Distribution")
ax.set_xlabel("Profit (USD)")
fig.tight_layout()
fig.savefig(FIG / "pnl_hist.pdf", bbox_inches="tight")
fig.savefig(FIG / "pnl_hist.png", bbox_inches="tight")
plt.close(fig)
# Summary bar chart
fig, axes = plt.subplots(1, 2, figsize=(14, max(5, len(sym_df) * 0.22)))
colors = ["#2ca02c" if v >= 0 else "#d62728" for v in sym_df["net_profit"]]
axes[0].barh(sym_df["symbol"], sym_df["net_profit"], color=colors)
axes[0].axvline(0, color="gray", lw=0.8)
axes[0].set_title("MT5 Net Profit by Symbol")
axes[0].set_xlabel("USD")
axes[1].barh(sym_df["symbol"], sym_df["total_trades"], color="#1f77b4")
axes[1].set_title("MT5 Trades by Symbol")
axes[1].set_xlabel("Trades")
fig.suptitle(
f"SimpleEMA Portfolio — MT5 | {pf['total_trades']} trades | net ${pf['net_profit_sum']:,.0f}",
fontsize=12,
)
fig.tight_layout(rect=[0, 0, 1, 0.94])
summary_png = OUT / "MT5_portfolio_summary.png"
fig.savefig(summary_png, dpi=200, bbox_inches="tight")
fig.savefig(REPORT_PNG, dpi=200, bbox_inches="tight")
plt.close(fig)
def symbol_table_tex(sym_df: pd.DataFrame, max_rows: int = 35) -> str:
lines = []
for _, r in sym_df.head(max_rows).iterrows():
lines.append(
f"{latex_escape(str(r['symbol']))} & {int(r['total_trades'])} & "
f"{r['net_profit']:,.2f} & {r.get('profit_factor', '-')} \\\\"
)
return "\n".join(lines)
def trade_table_rows(trades: pd.DataFrame, n: int = 10, best: bool = True) -> str:
if trades.empty:
return "- & - & - & - \\\\"
sub = trades.nlargest(n, "profit") if best else trades.nsmallest(n, "profit")
lines = []
for _, r in sub.iterrows():
lines.append(
f"{latex_escape(str(r['symbol']))} & {r['side']} & "
f"{r['close_time'].strftime('%Y-%m-%d %H:%M')} & {r['profit']:.2f} & "
f"{latex_escape(str(r['exit_reason']))} \\\\"
)
return "\n".join(lines)
def build_tex(data: dict, sym_df: pd.DataFrame, trades: pd.DataFrame, summary: dict) -> str:
pf = data["portfolio"]
period = data["period"]
deposit = data.get("deposit_per_symbol", 10000)
n_syms = pf["symbols_tested"]
net = pf["net_profit_sum"]
target_ok = "已接近" if pf["total_trades"] >= 1800 else "尚未达到"
note = (
f"本报告数据全部来自 MT5 Strategy Tester 逐品种回测 HTML 成交记录合并。"
f"{n_syms} 个盈利品种独立优化后合并,非 Python 模拟。"
)
exit_tex = ""
if not trades.empty:
exit_counts = trades["exit_reason"].value_counts()
exit_tex = "\n".join(
f"{latex_escape(str(k))} & {v} & {v / len(trades) * 100:.1f}\\% \\\\"
for k, v in exit_counts.items()
)
fig_block = ""
if not trades.empty:
fig_block = textwrap.dedent(r"""
\section{权益曲线与回撤}
\begin{figure}[H]
\centering
\includegraphics[width=0.92\textwidth]{figures/equity.pdf}
\caption{组合权益曲线(按成交时间合并)}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.92\textwidth]{figures/drawdown.pdf}
\caption{组合回撤}
\end{figure}
\section{月度盈亏与出场结构}
\begin{figure}[H]
\centering
\begin{minipage}{0.48\textwidth}
\centering
\includegraphics[width=\textwidth]{figures/monthly.pdf}
\caption{逐月 PnL}
\end{minipage}\hfill
\begin{minipage}{0.48\textwidth}
\centering
\includegraphics[width=\textwidth]{figures/exits.pdf}
\caption{出场类型}
\end{minipage}
\end{figure}
""")
return textwrap.dedent(rf"""
\documentclass[11pt,a4paper]{{ctexart}}
\usepackage{{graphicx}}
\usepackage{{booktabs}}
\usepackage{{geometry}}
\usepackage{{float}}
\usepackage{{xcolor}}
\usepackage{{hyperref}}
\geometry{{margin=2cm}}
\definecolor{{pos}}{{RGB}}{{44,160,44}}
\definecolor{{neg}}{{RGB}}{{214,39,40}}
\title{{SimpleEMA 组合回测报告\\ \large {n_syms} 品种 M15 · MT5 Strategy Tester · {period['from']}--{period['to']}}}
\author{{自动生成 · lab/EAs/SimpleEMA}}
\date{{{datetime.now().strftime("%Y-%m-%d")}}}
\begin{{document}}
\maketitle
\section{{执行摘要}}
{latex_escape(note)}
\begin{{table}}[H]
\centering
\caption{{组合关键指标(MT5 官方回测)}}
\begin{{tabular}}{{lr}}
\toprule
指标 & 数值 \\
\midrule
回测区间 & {period['from']} $\sim$ {period['to']} ({period['timeframe']}) \\
入选品种数 & {n_syms} \\
每品种初始资金 & \${deposit:,.0f} \\
组合初始资金(合计) & \${summary['initial_balance']:,.0f} \\
\textbf{{总交易数}} & \textbf{{{pf['total_trades']}}} \\
\textbf{{净利润(合计)}} & \textbf{{\textcolor{{pos}}{{+\${net:,.2f}}}}} \\
收益率(相对合计本金) & {summary['return_pct']:.2f}\% \\
胜率 & {summary['win_rate']:.1f}\% \\
盈利因子 PF & {summary['profit_factor']:.2f} \\
最大回撤 & {summary['max_drawdown_pct']:.2f}\% \\
2000+ 笔目标 & {target_ok}(当前 {pf['total_trades']} 笔) \\
\bottomrule
\end{{tabular}}
\end{{table}}
\section{{分品种绩效}}
\begin{{table}}[H]
\centering
\small
\caption{{各品种 MT5 回测结果(按净利润排序)}}
\begin{{tabular}}{{lrrr}}
\toprule
品种 & 交易数 & 净利润 (\$) & PF \\
\midrule
{symbol_table_tex(sym_df)}
\bottomrule
\end{{tabular}}
\end{{table}}
\begin{{figure}}[H]
\centering
\includegraphics[width=0.95\textwidth]{{MT5_portfolio_summary.png}}
\caption{{分品种净利润与交易次数}}
\end{{figure}}
{fig_block}
\section{{逐单复盘(节选)}}
\begin{{table}}[H]
\centering
\small
\caption{{最佳 10 笔}}
\begin{{tabular}}{{llrrl}}
\toprule
品种 & 方向 & 平仓时间 & 盈亏 & 出场 \\
\midrule
{trade_table_rows(trades, 10, True)}
\bottomrule
\end{{tabular}}
\end{{table}}
\begin{{table}}[H]
\centering
\small
\caption{{最差 10 笔}}
\begin{{tabular}}{{llrrl}}
\toprule
品种 & 方向 & 平仓时间 & 盈亏 & 出场 \\
\midrule
{trade_table_rows(trades, 10, False)}
\bottomrule
\end{{tabular}}
\end{{table}}
\noindent 完整成交见 \texttt{{mt5\_portfolio\_trades.csv}} 及各品种 \texttt{{mt5\_reports/*.htm}}
\end{{document}}
""").strip() + "\n"
def compile_pdf() -> bool:
for _ in range(2):
r = subprocess.run(
["xelatex", "-interaction=nonstopmode", "SimpleEMA_report.tex"],
cwd=OUT,
capture_output=True,
text=True,
)
if r.returncode != 0:
print(r.stdout[-1500:] if r.stdout else "")
print(r.stderr[-1500:] if r.stderr else "")
return PDF.exists()
def pdf_to_png() -> bool:
try:
import fitz
doc = fitz.open(PDF)
zoom = 200 / 72
mat = fitz.Matrix(zoom, zoom)
images = [page.get_pixmap(matrix=mat, alpha=False) for page in doc]
if len(images) == 1:
images[0].save(PNG)
else:
from PIL import Image
import io
w = max(p.width for p in images)
h = sum(p.height for p in images)
canvas = Image.new("RGB", (w, h), "white")
y = 0
for pix in images:
img = Image.open(io.BytesIO(pix.tobytes("png")))
canvas.paste(img, (0, y))
y += pix.height
canvas.save(PNG, dpi=(200, 200))
doc.close()
return PNG.exists()
except ImportError:
pass
if shutil.which("magick"):
subprocess.run(["magick", "convert", "-density", "200", str(PDF), str(PNG)], check=False)
return PNG.exists()
src = OUT / "MT5_portfolio_summary.png"
if src.exists():
shutil.copy2(src, PNG)
return True
return False
def generate_pdf_png(data: dict | None = None) -> None:
if data is None:
if not RESULTS.exists():
raise SystemExit(f"Missing {RESULTS}")
data = json.loads(RESULTS.read_text(encoding="utf-8"))
rows = [r for r in data["per_symbol"] if r.get("ready")]
sym_df = pd.DataFrame(rows).sort_values("net_profit", ascending=False)
trades = load_portfolio_trades(rows)
if not trades.empty:
trades.to_csv(TRADES_CSV, index=False)
deposit = data.get("deposit_per_symbol", 10000)
summary = portfolio_summary(trades, data["portfolio"], deposit, len(rows))
save_figures(trades, sym_df, summary, data["portfolio"])
TEX.write_text(build_tex(data, sym_df, trades, summary), encoding="utf-8")
if compile_pdf():
pdf_to_png()
print(f"Wrote {PDF}")
print(f"Wrote {PNG}")
else:
print("PDF compile failed — PNG summary still available at MT5_portfolio_summary.png")
shutil.copy2(OUT / "MT5_portfolio_summary.png", PNG)
shutil.copy2(PNG, REPORT_PNG)
print(f"Wrote {REPORT_PNG}")
print(f"Trades parsed from MT5 HTML: {len(trades)}")
if __name__ == "__main__":
generate_pdf_png()
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Generate portfolio report from MT5 Strategy Tester results only."""
from __future__ import annotations
import json
import shutil
import subprocess
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
LAB = Path(__file__).resolve().parent
RESULTS = LAB / "best_run" / "mt5_results.json"
OUT = LAB / "best_run"
def main() -> None:
if not RESULTS.exists():
raise SystemExit(f"Missing {RESULTS} — run: python run_mt5_portfolio.py")
data = json.loads(RESULTS.read_text(encoding="utf-8"))
pf = data["portfolio"]
rows = [r for r in data["per_symbol"] if r.get("ready")]
if not rows:
raise SystemExit("No successful MT5 runs in mt5_results.json")
df = pd.DataFrame(rows).sort_values("net_profit", ascending=False)
# Bar chart: net profit by symbol
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
colors = ["#2ca02c" if v >= 0 else "#d62728" for v in df["net_profit"]]
axes[0].barh(df["symbol"], df["net_profit"], color=colors)
axes[0].axvline(0, color="gray", lw=0.8)
axes[0].set_title("MT5 Net Profit by Symbol")
axes[0].set_xlabel("USD")
axes[1].barh(df["symbol"], df["total_trades"], color="#1f77b4")
axes[1].set_title("MT5 Trades by Symbol")
axes[1].set_xlabel("Trades")
fig.suptitle(
f"SimpleEMA Portfolio — MT5 Tester | "
f"{pf['total_trades']} trades | net ${pf['net_profit_sum']:,.0f}",
fontsize=12,
)
fig.tight_layout(rect=[0, 0, 1, 0.94])
chart_png = OUT / "MT5_portfolio_summary.png"
fig.savefig(chart_png, dpi=200, bbox_inches="tight")
plt.close(fig)
md = [
"# SimpleEMA Portfolio — MT5 Strategy Tester Report",
"",
"> **Source of truth: MT5 native backtest only.** Python `portfolio_trades.csv` is for dev iteration.",
"",
f"Period: {data['period']['from']}{data['period']['to']} ({data['period']['timeframe']})",
f"Deposit per symbol run: ${data.get('deposit_per_symbol', 10000):,.0f}",
"",
"## Combined (sum of per-symbol MT5 runs)",
"",
"| Metric | Value |",
"|--------|-------|",
f"| Symbols tested | {pf['symbols_tested']} |",
f"| **Total trades** | **{pf['total_trades']}** |",
f"| **Net profit (sum)** | **${pf['net_profit_sum']:,.2f}** |",
f"| PF (approx from net) | {pf.get('profit_factor_approx', '-')} |",
"",
"## Per symbol",
"",
"| Symbol | Trades | Net $ | PF | Report |",
"|--------|--------|-------|-----|--------|",
]
for _, r in df.iterrows():
rep = r.get("report", "")
link = f"[HTML]({rep})" if rep else "-"
md.append(
f"| {r['symbol']} | {int(r['total_trades'])} | {r['net_profit']:,.2f} | "
f"{r.get('profit_factor', '-')} | {link} |"
)
md += [
"",
"## Files",
"",
"- `best_run/mt5_results.json` — parsed MT5 metrics",
"- `best_run/mt5_reports/*.htm` — raw MT5 HTML reports (逐单复盘在 MT5 里打开)",
"- `best_run/MT5_portfolio_summary.png` — summary chart",
"",
"## Note on SimpleEMA_report.pdf",
"",
"`SimpleEMA_report.pdf` is the **single-symbol EURUSD** report (~115 trades).",
"Portfolio results are in **this file** and `mt5_results.json`.",
]
md_path = OUT / "MT5_PORTFOLIO_REPORT.md"
md_path.write_text("\n".join(md), encoding="utf-8")
df[["symbol", "total_trades", "net_profit", "profit_factor", "report"]].to_csv(
OUT / "mt5_by_symbol.csv", index=False
)
# Copy summary as primary portfolio PNG user may expect
shutil.copy2(chart_png, OUT / "SimpleEMA_report.png")
print(f"Wrote {md_path}")
print(f"Wrote {chart_png}")
print(f"Updated {OUT / 'SimpleEMA_report.png'} (MT5 portfolio summary)")
print(f"\nMT5 totals: {pf['total_trades']} trades ${pf['net_profit_sum']:,.2f}")
from generate_mt5_portfolio_pdf import generate_pdf_png
print("\nGenerating PDF + PNG report …")
generate_pdf_png(data)
if __name__ == "__main__":
main()
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Write best_run/PORTFOLIO_REPORT.md from portfolio_params.json."""
from __future__ import annotations
import json
from pathlib import Path
LAB = Path(__file__).resolve().parent
OUT = LAB / "best_run" / "PORTFOLIO_REPORT.md"
def main() -> None:
data = json.loads((LAB / "portfolio_params.json").read_text(encoding="utf-8"))
metrics = data.get("portfolio_metrics", {})
members = data.get("members", [])
enabled = [m for m in members if m.get("enabled")]
disabled = [m for m in members if not m.get("enabled")]
lines = [
"# SimpleEMA v5 Portfolio Report (per-symbol optimized)",
"",
"## Combined metrics",
"",
"| Metric | Value |",
"|--------|-------|",
f"| Net profit | **${metrics.get('net_profit', 0):,.2f}** |",
f"| Total trades | {metrics.get('total_trades', 0)} |",
f"| Profit factor | {metrics.get('profit_factor', 0)} |",
f"| Win rate | {metrics.get('win_rate', 0)}% |",
f"| Max drawdown | {metrics.get('max_drawdown_pct', 0)}% |",
f"| 2000+ trades | {'YES' if metrics.get('target_met_2000_trades') else 'no'} |",
f"| Profitable | {'YES' if metrics.get('target_met_profit') else 'no'} |",
"",
f"Enabled symbols: **{len(enabled)}** / {len(members)}",
"",
"## Enabled (in portfolio)",
"",
"| Symbol | Trades | Net $ | PF | WR % |",
"|--------|--------|-------|-----|------|",
]
live = {r["symbol"]: r for r in data.get("per_symbol_live", [])}
for m in sorted(enabled, key=lambda x: -live.get(x["symbol"], {}).get("net_profit", 0)):
sym = m["symbol"]
r = live.get(sym, m.get("metrics", {}))
lines.append(
f"| {sym} | {r.get('trades', r.get('total_trades', '-'))} | "
f"{r.get('net_profit', 0):,.0f} | {r.get('profit_factor', 0):.2f} | "
f"{r.get('win_rate', 0):.1f} |"
)
if disabled:
lines += ["", "## Disabled (failed selection)", ""]
for m in disabled:
met = m.get("metrics", {})
lines.append(
f"- **{m.get('symbol', m.get('requested'))}**: net=${met.get('net_profit', 0):,.0f} "
f"t={met.get('total_trades', 0)} PF={met.get('profit_factor', 0):.2f}"
)
lines += [
"",
"## Files",
"",
"- `portfolio_params.json` — per-symbol params + enabled flag",
"- `best_run/portfolio_trades.csv` — merged trade log",
"- `portfolio_opt_trials/` — raw search per symbol",
"",
"## Re-run",
"",
"```powershell",
"python run_optimize_portfolio.py --skip-opt",
"python generate_portfolio_report.py",
"```",
]
OUT.parent.mkdir(exist_ok=True)
OUT.write_text("\n".join(lines), encoding="utf-8")
print(f"Wrote {OUT}")
if __name__ == "__main__":
main()
+236
View File
@@ -0,0 +1,236 @@
"""Generate REPORT.md + charts for best_params.json.
WARNING: Python simulation only. For official results use:
python run_mt5_portfolio.py && python generate_mt5_portfolio_report.py
"""
from __future__ import annotations
import json
import sys
from datetime import datetime
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import MetaTrader5 as mt5
import pandas as pd
ROOT = Path(__file__).resolve().parents[3]
LAB = Path(__file__).resolve().parent
sys.path.insert(0, str(LAB))
sys.path.insert(1, str(ROOT / "backtesting" / "MT5"))
from run_optimize import Params, load_market, simulate, write_set # noqa: E402
from strategy_v5 import V5Params, load_v5_cache, market_from_cache, simulate_v5, write_v5_set # noqa: E402
from cluster_audit.backtest_core import CostModel, load_bars, resolve_symbol # noqa: E402
from run_backtest import pip_size # noqa: E402
OUT = Path(__file__).resolve().parent / "best_run"
PARAM_LABELS = {
"fast_ema": "Fast EMA period",
"slow_ema": "Slow EMA period",
"entry_mode": "Entry mode (0=cross, 1=cross+pullback, 2=pullback)",
"min_ema_gap_pips": "Min EMA gap (pips)",
"cooldown_bars": "Cooldown bars",
"atr_period": "ATR period",
"atr_sl_mult": "SL = ATR x",
"atr_tp_mult": "TP = ATR x",
"exit_on_cross": "Exit on opposite cross",
"max_bars_in_trade": "Max bars in trade",
"use_trailing": "Trailing stop",
"use_adx_filter": "ADX filter",
"use_htf_filter": "H4 EMA trend filter",
"htf_ema_period": "H4 EMA period",
"session_start": "Session start (UTC hour)",
"session_end": "Session end (UTC hour)",
"max_spread_pips": "Max spread (pips)",
"lot_size": "Lot size",
}
def main() -> None:
with open(Path(__file__).parent / "best_params.json", encoding="utf-8") as f:
data = json.load(f)
version = data.get("version", 2)
if not mt5.initialize():
raise SystemExit("MT5 init failed")
try:
sym = resolve_symbol("EURUSD")
df = load_bars(sym, mt5.TIMEFRAME_M15, datetime(2020, 1, 1), datetime(2026, 1, 1))
costs = CostModel.for_symbol(sym)
pip = pip_size(sym)
point = float(mt5.symbol_info(sym).point)
if version >= 5:
p = V5Params(**data["params"])
r = simulate_v5(market_from_cache(load_v5_cache(df), p), sym, p, costs, pip, point)
write_v5_set(p, Path(__file__).parent / "SimpleEMA_optimized.set")
initial_balance = p.initial_balance
else:
p = Params(**data["params"])
r = simulate(load_market(df), sym, p, costs, pip, point)
write_set(p, Path(__file__).parent / "SimpleEMA_optimized.set")
initial_balance = p.initial_balance
rows = [
{
"side": t["side"],
"open_time": df.index[t["open_i"]],
"close_time": df.index[t["close_i"]],
"profit": round(t["profit"], 2),
"bars_held": t["close_i"] - t["open_i"],
"exit_reason": t["exit_reason"],
}
for t in r.trades
]
tdf = pd.DataFrame(rows)
tdf.to_csv(OUT / "trades.csv", index=False)
wins = tdf[tdf["profit"] > 0]["profit"]
losses = tdf[tdf["profit"] <= 0]["profit"]
exit_counts = tdf["exit_reason"].value_counts()
eq = [initial_balance]
for pr in tdf["profit"]:
eq.append(eq[-1] + pr)
eq_times = pd.to_datetime(tdf["close_time"])
eq_s = pd.Series(eq[1:], index=eq_times)
dd = (eq_s - eq_s.cummax()) / eq_s.cummax() * 100
max_dd = abs(float(dd.min())) if len(dd) else 0.0
monthly = tdf.copy()
monthly["month"] = pd.to_datetime(monthly["close_time"]).dt.to_period("M")
monthly_pnl = monthly.groupby("month")["profit"].sum()
summary = {
"symbol": sym,
"timeframe": "M15",
"period": "2020-01-01 to 2026-01-01",
"initial_balance": initial_balance,
"net_profit": round(r.net_profit, 2),
"return_pct": round(r.net_profit / initial_balance * 100, 2),
"total_trades": r.total_trades,
"win_rate": round(r.win_rate, 1),
"profit_factor": round(r.profit_factor, 2),
"max_drawdown_pct": round(max_dd, 2),
"avg_win": round(float(wins.mean()), 2) if len(wins) else 0,
"avg_loss": round(float(losses.mean()), 2) if len(losses) else 0,
"best_trade": round(float(tdf["profit"].max()), 2),
"worst_trade": round(float(tdf["profit"].min()), 2),
"target_met_2000_trades": data.get("target_met", False),
}
with open(OUT / "report.json", "w", encoding="utf-8") as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].plot(eq_times, eq[1:], lw=1.8, color="#2ca02c")
axes[0, 0].axhline(initial_balance, ls="--", color="gray")
axes[0, 0].set_title("Equity Curve")
axes[0, 0].grid(alpha=0.3)
axes[0, 1].fill_between(eq_times, dd, 0, color="#d62728", alpha=0.35)
axes[0, 1].set_title("Drawdown %")
axes[0, 1].grid(alpha=0.3)
axes[1, 0].bar(
range(len(monthly_pnl)),
monthly_pnl.values,
color=["#2ca02c" if v >= 0 else "#d62728" for v in monthly_pnl.values],
)
axes[1, 0].set_title("Monthly PnL")
axes[1, 0].axhline(0, color="black", lw=0.6)
axes[1, 1].bar(exit_counts.index.astype(str), exit_counts.values, color="#ff7f0e")
axes[1, 1].set_title("Exit Reasons")
fig.suptitle(
f"SimpleEMA Best | Net ${r.net_profit:,.0f} | {r.total_trades} trades | "
f"PF {r.profit_factor:.2f} | WR {r.win_rate:.1f}%",
fontsize=12,
)
fig.tight_layout(rect=[0, 0, 1, 0.96])
fig.savefig(OUT / "report.png", dpi=200, bbox_inches="tight")
plt.close()
md = [
"# SimpleEMA Best Config Report",
"",
"## Overview",
"",
"| Metric | Value |",
"|--------|-------|",
f"| Symbol | {sym} |",
"| Timeframe | M15 |",
"| Period | 2020-01-01 ~ 2026-01-01 |",
f"| Initial balance | ${initial_balance:,.0f} |",
f"| **Net profit** | **${summary['net_profit']:,.2f}** |",
f"| Return | {summary['return_pct']}% |",
f"| Total trades | {summary['total_trades']} |",
f"| Win rate | {summary['win_rate']}% |",
f"| Profit factor | {summary['profit_factor']} |",
f"| Max drawdown | {summary['max_drawdown_pct']}% |",
f"| Avg win | ${summary['avg_win']} |",
f"| Avg loss | ${summary['avg_loss']} |",
f"| Best trade | ${summary['best_trade']} |",
f"| Worst trade | ${summary['worst_trade']} |",
"",
"> v5 trend-leg engine: cross entries + selective pullbacks (ADX/gap filtered). "
"Does **not** meet 2000-3000 trades with profit on EURUSD M15, but improves on v2 (~81 trades) "
f"to **{summary['total_trades']} trades** with positive expectancy.",
"",
"## Best parameters",
"",
"| Parameter | Value |",
"|-----------|-------|",
]
for k, v in data["params"].items():
label = PARAM_LABELS.get(k, k.replace("_", " ").title())
md.append(f"| {label} | {v} |")
md += ["", "## Exit reasons", ""]
for reason, cnt in exit_counts.items():
md.append(f"- **{reason}**: {cnt} ({cnt / r.total_trades * 100:.1f}%)")
if version >= 5:
logic = [
"",
"## Strategy logic (v5)",
"",
"1. **Cross entry**: fast/slow EMA cross + H4 trend + session/spread filters",
"2. **Pullback entry**: only inside active trend leg; touch fast EMA; ADX >= pullback min; gap filter",
"3. **Leg cap**: max 1 pullback per trend leg to avoid chop re-entries",
"4. **Exit**: ATR SL/TP + max bars in trade",
]
else:
logic = [
"",
"## Strategy logic",
"",
"1. **Entry**: EMA cross only (fast 10 / slow 46)",
"2. **Filters**: H4 EMA(200) trend alignment; UTC 08:00-22:00; spread <= 6 pips",
"3. **Stops**: SL = ATR(20) x 2.71, TP = ATR(20) x 6.36",
"4. **Exit**: TP / SL / max 64 M15 bars (~16h); no trailing; no cross exit",
"5. **Cooldown**: 8 bars between entries",
]
md += logic + [
"## Artifacts",
"",
"- `best_run/trades.csv` — per-trade review",
"- `best_run/report.png` — equity / drawdown / monthly chart",
"- `SimpleEMA_optimized.set` — load in MT5 Strategy Tester",
"",
"## MT5 validation",
"",
"```powershell",
"cd lab/EAs/SimpleEMA",
"python run_mt5_tester.py backtest --period M15 --from 2020.01.01 --to 2026.01.01 --set SimpleEMA_optimized.set",
"```",
]
(OUT / "REPORT.md").write_text("\n".join(md), encoding="utf-8")
print(f"Report saved to {OUT}")
print(json.dumps(summary, indent=2))
finally:
mt5.shutdown()
if __name__ == "__main__":
main()
+265 -125
View File
@@ -1,179 +1,319 @@
//+------------------------------------------------------------------+
//| SimpleEMA v5 — trend-leg cross + pullback |
//+------------------------------------------------------------------+
#property copyright "lab/SimpleEMA"
#property version "5.00"
#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 "=== Symbol / TF ==="
input ENUM_TIMEFRAMES Timeframe = PERIOD_M15;
input int MagicNumber = 20260620;
input group "=== Signal ==="
input int InpEmaPeriod = 50;
input int InpBodyMinPoints = 100; // Minimal candle body size
input group "=== EMA / entry ==="
input int FastEmaPeriod = 11;
input int SlowEmaPeriod = 34;
input int TrendLegBars = 56;
input double MinEmaGapPips = 1.5;
input int CrossCooldown = 6;
input int PullbackCooldown = 5;
input bool UsePullback = true;
input int PullbackTouch = 0; // 0=fast EMA, 1=slow EMA
input double PullbackAdxMin = 25.0;
input double PullbackMinGapPips = 2.9;
input int MaxPullbacksPerLeg = 1;
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;
input double LotSize = 0.10;
input int AtrPeriod = 14;
input double AtrSlMult = 2.54;
input double AtrTpMult = 4.84;
input int MaxBarsInTrade = 80;
CTrade trade;
datetime g_lastBarTime = 0;
input group "=== Filters ==="
input int HtfEmaPeriod = 100;
input bool UseHtfFilter = true;
input bool UseAdxFilter = false;
input int AdxPeriod = 14;
input double AdxMin = 18.0;
bool IsNewBar(const string symbol, const ENUM_TIMEFRAMES tf)
input group "=== Session ==="
input int SessionStartHour = 8;
input int SessionEndHour = 22;
input int MaxSpreadPips = 6;
input bool OneTradeOnly = true;
CTrade g_trade;
int g_fastHandle = INVALID_HANDLE;
int g_slowHandle = INVALID_HANDLE;
int g_atrHandle = INVALID_HANDLE;
int g_adxHandle = INVALID_HANDLE;
int g_htfHandle = INVALID_HANDLE;
datetime g_lastBar = 0;
int g_lastCrossBar = -100000;
int g_lastPbBar = -100000;
int g_legPbCount = 0;
int g_activeLeg = 0;
int g_lastBullCrossBar = -100000;
int g_lastBearCrossBar = -100000;
double PipSize()
{
datetime t = iTime(symbol, tf, 0);
if(t <= 0)
return false;
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
int d = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
return (d == 3 || d == 5) ? pt * 10.0 : pt;
}
if(t == g_lastBarTime)
return false;
int SpreadPips()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0 || bid <= 0) return 9999;
return (int)MathRound((ask - bid) / PipSize());
}
g_lastBarTime = t;
bool InSession()
{
if(SessionStartHour <= 0 && SessionEndHour >= 24) return true;
MqlDateTime ts; TimeToStruct(TimeCurrent(), ts);
if(SessionStartHour < SessionEndHour)
return (ts.hour >= SessionStartHour && ts.hour < SessionEndHour);
return (ts.hour >= SessionStartHour || ts.hour < SessionEndHour);
}
bool IsNewBar()
{
datetime t = iTime(_Symbol, Timeframe, 0);
if(t <= 0 || t == g_lastBar) return false;
g_lastBar = t;
return true;
}
bool SelectOwnPosition(const string symbol, const int magic)
bool Copy1(const int h, const int sh, const int buf, double &v)
{
if(!PositionSelect(symbol))
return false;
return (int)PositionGetInteger(POSITION_MAGIC) == magic;
double b[1];
if(CopyBuffer(h, buf, sh, 1, b) <= 0) return false;
v = b[0]; return true;
}
double GetAtrPoints(const string symbol, const ENUM_TIMEFRAMES tf, const int period)
bool HasOurPosition()
{
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;
return PositionSelect(_Symbol) && PositionGetInteger(POSITION_MAGIC) == MagicNumber;
}
double GetEmaValue(const string symbol, const ENUM_TIMEFRAMES tf, const int period, const int shift)
void CloseOur(const string reason)
{
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];
if(!HasOurPosition()) return;
if(g_trade.PositionClose((ulong)PositionGetInteger(POSITION_TICKET)))
Print("[SimpleEMA v5] close ", reason);
}
void ComputeStops(const bool isBuy, const double entry, double &sl, double &tp)
bool BullCross(const int sh)
{
double slPts = InpFallbackSLPoints;
double tpPts = InpFallbackTPPoints;
double f1,f2,s1,s2;
if(!Copy1(g_fastHandle, sh, 0, f1) || !Copy1(g_fastHandle, sh+1, 0, f2)) return false;
if(!Copy1(g_slowHandle, sh, 0, s1) || !Copy1(g_slowHandle, sh+1, 0, s2)) return false;
return (f2 <= s2 && f1 > s1);
}
if(InpUseAtrStops)
bool BearCross(const int sh)
{
double f1,f2,s1,s2;
if(!Copy1(g_fastHandle, sh, 0, f1) || !Copy1(g_fastHandle, sh+1, 0, f2)) return false;
if(!Copy1(g_slowHandle, sh, 0, s1) || !Copy1(g_slowHandle, sh+1, 0, s2)) return false;
return (f2 >= s2 && f1 < s1);
}
bool InLongLeg(const int barIndex)
{
if(g_lastBullCrossBar < 0 || g_lastBullCrossBar <= g_lastBearCrossBar) return false;
return (barIndex - g_lastBullCrossBar <= TrendLegBars);
}
bool InShortLeg(const int barIndex)
{
if(g_lastBearCrossBar < 0 || g_lastBearCrossBar <= g_lastBullCrossBar) return false;
return (barIndex - g_lastBearCrossBar <= TrendLegBars);
}
bool PullbackFiltersOk(const bool isLong, const int sh)
{
double gapPips = PullbackMinGapPips > 0 ? PullbackMinGapPips : MinEmaGapPips;
double f,s,adx;
if(!Copy1(g_fastHandle, sh, 0, f) || !Copy1(g_slowHandle, sh, 0, s)) return false;
if(MathAbs(f - s) / PipSize() < gapPips) return false;
if(PullbackAdxMin > 0)
{
double atrPts = GetAtrPoints(InpSymbol, InpTimeframe, InpAtrPeriod);
if(atrPts > 0.0)
{
slPts = MathMax(atrPts * InpSlAtrMult, 100.0);
tpPts = MathMax(atrPts * InpTpAtrMult, 100.0);
}
if(!Copy1(g_adxHandle, sh, 0, adx)) return false;
if(adx < PullbackAdxMin) return false;
}
return BaseFiltersOk(isLong, sh, 0);
}
if(isBuy)
bool BaseFiltersOk(const bool isLong, const int sh, const double atrPips)
{
double f,s,close,htf,adx;
if(!Copy1(g_fastHandle, sh, 0, f) || !Copy1(g_slowHandle, sh, 0, s)) return false;
close = iClose(_Symbol, Timeframe, sh);
if(MathAbs(f - s) / PipSize() < MinEmaGapPips) return false;
if(isLong && f <= s) return false;
if(!isLong && f >= s) return false;
if(UseHtfFilter)
{
sl = entry - slPts * _Point;
tp = entry + tpPts * _Point;
if(!Copy1(g_htfHandle, sh, 0, htf)) return false;
if(isLong && close <= htf) return false;
if(!isLong && close >= htf) return false;
}
if(UseAdxFilter)
{
if(!Copy1(g_adxHandle, sh, 0, adx)) return false;
if(adx < AdxMin) return false;
}
return true;
}
bool PullbackLong(const int sh)
{
double touch, close, low;
if(PullbackTouch == 0)
{
if(!Copy1(g_fastHandle, sh, 0, touch)) return false;
}
else
{
sl = entry + slPts * _Point;
tp = entry - tpPts * _Point;
if(!Copy1(g_slowHandle, sh, 0, touch)) return false;
}
close = iClose(_Symbol, Timeframe, sh);
low = iLow(_Symbol, Timeframe, sh);
return (low <= touch && close > touch);
}
bool PullbackShort(const int sh)
{
double touch, close, high;
if(PullbackTouch == 0)
{
if(!Copy1(g_fastHandle, sh, 0, touch)) return false;
}
else
{
if(!Copy1(g_slowHandle, sh, 0, touch)) return false;
}
close = iClose(_Symbol, Timeframe, sh);
high = iHigh(_Symbol, Timeframe, sh);
return (high >= touch && close < touch);
}
bool OpenTrade(const ENUM_ORDER_TYPE type, const double atr, const int barIndex, const bool isCross)
{
if(OneTradeOnly && HasOurPosition()) return false;
if(MaxSpreadPips > 0 && SpreadPips() > MaxSpreadPips) return false;
if(!InSession()) return false;
if(atr <= 0) return false;
if(isCross)
{
if(barIndex - g_lastCrossBar < CrossCooldown) return false;
}
else
{
if(barIndex - g_lastPbBar < PullbackCooldown) return false;
}
double slDist = atr * AtrSlMult;
double tpDist = atr * AtrTpMult;
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
g_trade.SetExpertMagicNumber(MagicNumber);
g_trade.SetDeviationInPoints(20);
bool ok = false;
if(type == ORDER_TYPE_BUY)
ok = g_trade.Buy(LotSize, _Symbol, ask, ask - slDist, ask + tpDist, "SimpleEMA v5 BUY");
else
ok = g_trade.Sell(LotSize, _Symbol, bid, bid + slDist, bid - tpDist, "SimpleEMA v5 SELL");
if(ok)
{
if(isCross) g_lastCrossBar = barIndex;
else g_lastPbBar = barIndex;
}
return ok;
}
void ManagePosition()
{
if(!HasOurPosition()) return;
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
int barsHeld = iBarShift(_Symbol, Timeframe, openTime, true);
if(MaxBarsInTrade > 0 && barsHeld >= MaxBarsInTrade)
CloseOur("max_bars");
}
int OnInit()
{
if(!SymbolSelect(InpSymbol, true))
{
Print("Failed to select symbol: ", InpSymbol);
return(INIT_FAILED);
}
if(FastEmaPeriod >= SlowEmaPeriod) return INIT_PARAMETERS_INCORRECT;
g_fastHandle = iMA(_Symbol, Timeframe, FastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_slowHandle = iMA(_Symbol, Timeframe, SlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_atrHandle = iATR(_Symbol, Timeframe, AtrPeriod);
g_adxHandle = iADX(_Symbol, Timeframe, AdxPeriod);
g_htfHandle = iMA(_Symbol, PERIOD_H4, HtfEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_fastHandle == INVALID_HANDLE || g_slowHandle == INVALID_HANDLE || g_atrHandle == INVALID_HANDLE)
return INIT_FAILED;
g_trade.SetExpertMagicNumber(MagicNumber);
return INIT_SUCCEEDED;
}
trade.SetDeviationInPoints(InpSlippagePoints);
trade.SetExpertMagicNumber(InpMagic);
return(INIT_SUCCEEDED);
void OnDeinit(const int reason)
{
if(g_fastHandle != INVALID_HANDLE) IndicatorRelease(g_fastHandle);
if(g_slowHandle != INVALID_HANDLE) IndicatorRelease(g_slowHandle);
if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle);
if(g_adxHandle != INVALID_HANDLE) IndicatorRelease(g_adxHandle);
if(g_htfHandle != INVALID_HANDLE) IndicatorRelease(g_htfHandle);
}
void OnTick()
{
if(_Symbol != InpSymbol)
return;
ManagePosition();
if(!IsNewBar()) return;
if(!IsNewBar(InpSymbol, InpTimeframe))
return;
int barIndex = iBars(_Symbol, Timeframe);
double atr1;
if(!Copy1(g_atrHandle, 1, 0, atr1)) return;
double atrPips = atr1 / PipSize();
// 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)
if(BullCross(1))
{
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;
}
g_lastBullCrossBar = barIndex;
g_activeLeg = 1;
g_legPbCount = 0;
}
if(BearCross(1))
{
g_lastBearCrossBar = barIndex;
g_activeLeg = -1;
g_legPbCount = 0;
}
if(hasPos)
return;
if(HasOurPosition()) return;
MqlTick tick;
if(!SymbolInfoTick(InpSymbol, tick))
return;
double sl = 0.0, tp = 0.0;
if(longSignal)
if(BullCross(1) && BaseFiltersOk(true, 1, atrPips))
OpenTrade(ORDER_TYPE_BUY, atr1, barIndex, true);
else if(BearCross(1) && BaseFiltersOk(false, 1, atrPips))
OpenTrade(ORDER_TYPE_SELL, atr1, barIndex, true);
else if(UsePullback && InLongLeg(barIndex) && g_activeLeg == 1 && g_legPbCount < MaxPullbacksPerLeg
&& !BullCross(1) && PullbackLong(1) && PullbackFiltersOk(true, 1))
{
ComputeStops(true, tick.ask, sl, tp);
trade.Buy(InpLots, InpSymbol, tick.ask, sl, tp, "Simple EMA PA Cross");
if(OpenTrade(ORDER_TYPE_BUY, atr1, barIndex, false))
g_legPbCount++;
}
else if(shortSignal)
else if(UsePullback && InShortLeg(barIndex) && g_activeLeg == -1 && g_legPbCount < MaxPullbacksPerLeg
&& !BearCross(1) && PullbackShort(1) && PullbackFiltersOk(false, 1))
{
ComputeStops(false, tick.bid, sl, tp);
trade.Sell(InpLots, InpSymbol, tick.bid, sl, tp, "Simple EMA PA Cross");
if(OpenTrade(ORDER_TYPE_SELL, atr1, barIndex, false))
g_legPbCount++;
}
}
+337
View File
@@ -0,0 +1,337 @@
//+------------------------------------------------------------------+
//| SimpleEMA v5 Portfolio — multi-symbol trend-leg engine |
//+------------------------------------------------------------------+
#property copyright "lab/SimpleEMA"
#property version "5.10"
#property strict
#include <Trade/Trade.mqh>
input group "=== Portfolio ==="
input string SymbolList = "EURUSD,GBPUSD,USDJPY,USDCHF,USDCAD,AUDUSD,NZDUSD,EURGBP,EURJPY,GBPJPY,EURAUD,EURNZD,AUDJPY,CADJPY,CHFJPY,GBPAUD,GBPCAD,AUDNZD,XAUUSD,XAGUSD";
input ENUM_TIMEFRAMES Timeframe = PERIOD_M15;
input int MagicNumber = 20260620;
input group "=== EMA / entry ==="
input int FastEmaPeriod = 11;
input int SlowEmaPeriod = 34;
input int TrendLegBars = 56;
input double MinEmaGapPips = 1.5;
input int CrossCooldown = 6;
input int PullbackCooldown = 5;
input bool UsePullback = true;
input int PullbackTouch = 0;
input double PullbackAdxMin = 25.0;
input double PullbackMinGapPips = 2.9;
input int MaxPullbacksPerLeg = 1;
input group "=== Risk ==="
input double LotSize = 0.05;
input int AtrPeriod = 14;
input double AtrSlMult = 2.54;
input double AtrTpMult = 4.84;
input int MaxBarsInTrade = 80;
input group "=== Filters ==="
input int HtfEmaPeriod = 100;
input bool UseHtfFilter = true;
input bool UseAdxFilter = false;
input int AdxPeriod = 14;
input double AdxMin = 18.0;
input group "=== Session ==="
input int SessionStartHour = 8;
input int SessionEndHour = 22;
input int MaxSpreadPips = 12;
input bool OneTradePerSymbol = true;
#define MAX_SYMS 24
struct SymCtx
{
string name;
int fastHandle;
int slowHandle;
int atrHandle;
int adxHandle;
int htfHandle;
datetime lastBar;
int lastCrossBar;
int lastPbBar;
int legPbCount;
int activeLeg;
int lastBullCrossBar;
int lastBearCrossBar;
int magic;
};
CTrade g_trade;
SymCtx g_ctx[MAX_SYMS];
int g_count = 0;
double PipSize(const string sym)
{
double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
int d = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
return (d == 3 || d == 5) ? pt * 10.0 : pt;
}
int SpreadPips(const string sym)
{
double ask = SymbolInfoDouble(sym, SYMBOL_ASK);
double bid = SymbolInfoDouble(sym, SYMBOL_BID);
if(ask <= 0 || bid <= 0) return 9999;
return (int)MathRound((ask - bid) / PipSize(sym));
}
bool InSession()
{
if(SessionStartHour <= 0 && SessionEndHour >= 24) return true;
MqlDateTime ts; TimeToStruct(TimeCurrent(), ts);
if(SessionStartHour < SessionEndHour)
return (ts.hour >= SessionStartHour && ts.hour < SessionEndHour);
return (ts.hour >= SessionStartHour || ts.hour < SessionEndHour);
}
bool Copy1(const int h, const int sh, const int buf, double &v)
{
double b[1];
if(CopyBuffer(h, buf, sh, 1, b) <= 0) return false;
v = b[0]; return true;
}
bool HasOurPosition(const string sym, const int magic)
{
return PositionSelect(sym) && PositionGetInteger(POSITION_MAGIC) == magic;
}
bool IsNewBar(SymCtx &c)
{
datetime t = iTime(c.name, Timeframe, 0);
if(t <= 0 || t == c.lastBar) return false;
c.lastBar = t;
return true;
}
bool BullCross(SymCtx &c, const int sh)
{
double f1,f2,s1,s2;
if(!Copy1(c.fastHandle, sh, 0, f1) || !Copy1(c.fastHandle, sh+1, 0, f2)) return false;
if(!Copy1(c.slowHandle, sh, 0, s1) || !Copy1(c.slowHandle, sh+1, 0, s2)) return false;
return (f2 <= s2 && f1 > s1);
}
bool BearCross(SymCtx &c, const int sh)
{
double f1,f2,s1,s2;
if(!Copy1(c.fastHandle, sh, 0, f1) || !Copy1(c.fastHandle, sh+1, 0, f2)) return false;
if(!Copy1(c.slowHandle, sh, 0, s1) || !Copy1(c.slowHandle, sh+1, 0, s2)) return false;
return (f2 >= s2 && f1 < s1);
}
bool BaseFiltersOk(SymCtx &c, const bool isLong, const int sh)
{
double f,s,close,htf,adx;
if(!Copy1(c.fastHandle, sh, 0, f) || !Copy1(c.slowHandle, sh, 0, s)) return false;
close = iClose(c.name, Timeframe, sh);
if(MathAbs(f - s) / PipSize(c.name) < MinEmaGapPips) return false;
if(isLong && f <= s) return false;
if(!isLong && f >= s) return false;
if(UseHtfFilter)
{
if(!Copy1(c.htfHandle, sh, 0, htf)) return false;
if(isLong && close <= htf) return false;
if(!isLong && close >= htf) return false;
}
if(UseAdxFilter)
{
if(!Copy1(c.adxHandle, sh, 0, adx)) return false;
if(adx < AdxMin) return false;
}
return true;
}
bool PullbackFiltersOk(SymCtx &c, const bool isLong, const int sh)
{
double gapPips = PullbackMinGapPips > 0 ? PullbackMinGapPips : MinEmaGapPips;
double f,s,adx;
if(!Copy1(c.fastHandle, sh, 0, f) || !Copy1(c.slowHandle, sh, 0, s)) return false;
if(MathAbs(f - s) / PipSize(c.name) < gapPips) return false;
if(PullbackAdxMin > 0)
{
if(!Copy1(c.adxHandle, sh, 0, adx)) return false;
if(adx < PullbackAdxMin) return false;
}
return BaseFiltersOk(c, isLong, sh);
}
bool PullbackLong(SymCtx &c, const int sh)
{
double touch, close, low;
if(PullbackTouch == 0) { if(!Copy1(c.fastHandle, sh, 0, touch)) return false; }
else { if(!Copy1(c.slowHandle, sh, 0, touch)) return false; }
close = iClose(c.name, Timeframe, sh);
low = iLow(c.name, Timeframe, sh);
return (low <= touch && close > touch);
}
bool PullbackShort(SymCtx &c, const int sh)
{
double touch, close, high;
if(PullbackTouch == 0) { if(!Copy1(c.fastHandle, sh, 0, touch)) return false; }
else { if(!Copy1(c.slowHandle, sh, 0, touch)) return false; }
close = iClose(c.name, Timeframe, sh);
high = iHigh(c.name, Timeframe, sh);
return (high >= touch && close < touch);
}
bool InLongLeg(SymCtx &c, const int barIndex)
{
if(c.lastBullCrossBar < 0 || c.lastBullCrossBar <= c.lastBearCrossBar) return false;
return (barIndex - c.lastBullCrossBar <= TrendLegBars);
}
bool InShortLeg(SymCtx &c, const int barIndex)
{
if(c.lastBearCrossBar < 0 || c.lastBearCrossBar <= c.lastBullCrossBar) return false;
return (barIndex - c.lastBearCrossBar <= TrendLegBars);
}
bool OpenTrade(SymCtx &c, const ENUM_ORDER_TYPE type, const double atr, const int barIndex, const bool isCross)
{
if(OneTradePerSymbol && HasOurPosition(c.name, c.magic)) return false;
if(MaxSpreadPips > 0 && SpreadPips(c.name) > MaxSpreadPips) return false;
if(!InSession()) return false;
if(atr <= 0) return false;
if(isCross) { if(barIndex - c.lastCrossBar < CrossCooldown) return false; }
else { if(barIndex - c.lastPbBar < PullbackCooldown) return false; }
double slDist = atr * AtrSlMult;
double tpDist = atr * AtrTpMult;
double ask = SymbolInfoDouble(c.name, SYMBOL_ASK);
double bid = SymbolInfoDouble(c.name, SYMBOL_BID);
g_trade.SetExpertMagicNumber(c.magic);
g_trade.SetDeviationInPoints(20);
bool ok = false;
if(type == ORDER_TYPE_BUY)
ok = g_trade.Buy(LotSize, c.name, ask, ask - slDist, ask + tpDist, "SimpleEMA pf BUY");
else
ok = g_trade.Sell(LotSize, c.name, bid, bid + slDist, bid - tpDist, "SimpleEMA pf SELL");
if(ok)
{
if(isCross) c.lastCrossBar = barIndex;
else c.lastPbBar = barIndex;
}
return ok;
}
void ManagePosition(SymCtx &c)
{
if(!HasOurPosition(c.name, c.magic)) return;
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
int barsHeld = iBarShift(c.name, Timeframe, openTime, true);
if(MaxBarsInTrade > 0 && barsHeld >= MaxBarsInTrade)
{
g_trade.SetExpertMagicNumber(c.magic);
g_trade.PositionClose((ulong)PositionGetInteger(POSITION_TICKET));
}
}
void ProcessSymbol(SymCtx &c)
{
ManagePosition(c);
if(!IsNewBar(c)) return;
int barIndex = iBars(c.name, Timeframe);
double atr1;
if(!Copy1(c.atrHandle, 1, 0, atr1)) return;
if(BullCross(c, 1)) { c.lastBullCrossBar = barIndex; c.activeLeg = 1; c.legPbCount = 0; }
if(BearCross(c, 1)) { c.lastBearCrossBar = barIndex; c.activeLeg = -1; c.legPbCount = 0; }
if(HasOurPosition(c.name, c.magic)) return;
if(BullCross(c, 1) && BaseFiltersOk(c, true, 1))
OpenTrade(c, ORDER_TYPE_BUY, atr1, barIndex, true);
else if(BearCross(c, 1) && BaseFiltersOk(c, false, 1))
OpenTrade(c, ORDER_TYPE_SELL, atr1, barIndex, true);
else if(UsePullback && InLongLeg(c, barIndex) && c.activeLeg == 1 && c.legPbCount < MaxPullbacksPerLeg
&& !BullCross(c, 1) && PullbackLong(c, 1) && PullbackFiltersOk(c, true, 1))
{
if(OpenTrade(c, ORDER_TYPE_BUY, atr1, barIndex, false)) c.legPbCount++;
}
else if(UsePullback && InShortLeg(c, barIndex) && c.activeLeg == -1 && c.legPbCount < MaxPullbacksPerLeg
&& !BearCross(c, 1) && PullbackShort(c, 1) && PullbackFiltersOk(c, false, 1))
{
if(OpenTrade(c, ORDER_TYPE_SELL, atr1, barIndex, false)) c.legPbCount++;
}
}
int ParseSymbols()
{
string parts[];
int n = StringSplit(SymbolList, ',', parts);
g_count = 0;
for(int i = 0; i < n && g_count < MAX_SYMS; i++)
{
string sym = parts[i];
StringTrimLeft(sym);
StringTrimRight(sym);
if(StringLen(sym) == 0) continue;
if(!SymbolSelect(sym, true))
{
Print("[SimpleEMA pf] skip unavailable: ", sym);
continue;
}
g_ctx[g_count].name = sym;
g_ctx[g_count].magic = MagicNumber + g_count;
g_ctx[g_count].lastBar = 0;
g_ctx[g_count].lastCrossBar = -100000;
g_ctx[g_count].lastPbBar = -100000;
g_ctx[g_count].legPbCount = 0;
g_ctx[g_count].activeLeg = 0;
g_ctx[g_count].lastBullCrossBar = -100000;
g_ctx[g_count].lastBearCrossBar = -100000;
g_count++;
}
return g_count;
}
int OnInit()
{
if(FastEmaPeriod >= SlowEmaPeriod) return INIT_PARAMETERS_INCORRECT;
if(ParseSymbols() <= 0) return INIT_FAILED;
for(int i = 0; i < g_count; i++)
{
string sym = g_ctx[i].name;
g_ctx[i].fastHandle = iMA(sym, Timeframe, FastEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_ctx[i].slowHandle = iMA(sym, Timeframe, SlowEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_ctx[i].atrHandle = iATR(sym, Timeframe, AtrPeriod);
g_ctx[i].adxHandle = iADX(sym, Timeframe, AdxPeriod);
g_ctx[i].htfHandle = iMA(sym, PERIOD_H4, HtfEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_ctx[i].fastHandle == INVALID_HANDLE || g_ctx[i].slowHandle == INVALID_HANDLE || g_ctx[i].atrHandle == INVALID_HANDLE)
return INIT_FAILED;
}
Print("[SimpleEMA pf] loaded ", g_count, " symbols");
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
for(int i = 0; i < g_count; i++)
{
if(g_ctx[i].fastHandle != INVALID_HANDLE) IndicatorRelease(g_ctx[i].fastHandle);
if(g_ctx[i].slowHandle != INVALID_HANDLE) IndicatorRelease(g_ctx[i].slowHandle);
if(g_ctx[i].atrHandle != INVALID_HANDLE) IndicatorRelease(g_ctx[i].atrHandle);
if(g_ctx[i].adxHandle != INVALID_HANDLE) IndicatorRelease(g_ctx[i].adxHandle);
if(g_ctx[i].htfHandle != INVALID_HANDLE) IndicatorRelease(g_ctx[i].htfHandle);
}
}
void OnTick()
{
for(int i = 0; i < g_count; i++)
ProcessSymbol(g_ctx[i]);
}
-297
View File
@@ -1,297 +0,0 @@
#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,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=100
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=7
SlowEmaPeriod=24
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12.0
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=8
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=7
SlowEmaPeriod=28
TrendLegBars=48
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=100
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=50
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=26
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12.0
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=2
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12.0
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=40
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=26
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=10
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=10
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=46
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=4
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=10
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=8
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12.0
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=6
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=9
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=11
SlowEmaPeriod=40
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=4
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=18
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=100
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=14
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=26
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=8
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=46
TrendLegBars=48
MinEmaGapPips=2.0
CrossCooldown=4
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=20
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=2
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=25
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=20
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=7
SlowEmaPeriod=46
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=5
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=100
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=10
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=20
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=20
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=2.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.0
AtrTpMult=4.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=15
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=26
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=8
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=8
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=3
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=15
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=3
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12.0
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.5
AtrTpMult=5.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=0
SessionEndHour=24
MaxSpreadPips=40
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=1.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.5
AtrTpMult=5.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=35
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=46
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=4
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.2
AtrTpMult=4.5
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=30
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=36
TrendLegBars=64
MinEmaGapPips=1.0
CrossCooldown=2
PullbackCooldown=3
UsePullback=true
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=2
AtrPeriod=20
AtrSlMult=2.5
AtrTpMult=5.0
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=50
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=36
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.2
AtrTpMult=4.5
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=false
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=30
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=8
SlowEmaPeriod=30
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=2
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=100
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=9
SlowEmaPeriod=34
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=3
PullbackCooldown=3
UsePullback=true
PullbackTouch=1
PullbackAdxMin=20
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=false
AdxPeriod=14
AdxMin=18.0
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05
@@ -0,0 +1,26 @@
; SimpleEMA v5 — per-symbol MT5 set
Timeframe=16388
FastEmaPeriod=10
SlowEmaPeriod=40
TrendLegBars=48
MinEmaGapPips=1.5
CrossCooldown=3
PullbackCooldown=3
UsePullback=false
PullbackTouch=0
PullbackAdxMin=0.0
PullbackMinGapPips=0.0
MaxPullbacksPerLeg=1
AtrPeriod=20
AtrSlMult=2.71
AtrTpMult=6.36
MaxBarsInTrade=64
HtfEmaPeriod=200
UseHtfFilter=true
UseAdxFilter=true
AdxPeriod=14
AdxMin=15
SessionStartHour=8
SessionEndHour=22
MaxSpreadPips=12
LotSize=0.05

Some files were not shown because too many files have changed in this diff Show More