Files
profitable-expert-advisor/lab/EAs/TFXNZDUSD.mq5
T
2026-05-27 14:59:00 +02:00

384 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//+------------------------------------------------------------------+
//| TFXNZDUSD.mq5 |
//| NZDUSD: HTF directional bias + intraday bearish→bullish shift |
//| Mirrors a reactive workflow: higher TFs for bias (D1/W1), |
//| lower TFs (H4M15) for confirmation — long bias / pullback / |
//| reclaim entry. Not predictive; signals on closed bars. |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property link ""
#property version "1.01"
#property description "NZDUSD long-bias EA: D1/W1 trend filter, intraday EMA cross after pullback streak, ATR risk."
#include <Trade/Trade.mqh>
input group "=== Symbol ==="
input string InpSymbol = "NZDUSD"; // Spot FX symbol (broker-specific)
input group "=== Timeframes (thesis) ==="
input ENUM_TIMEFRAMES InpBiasTF = PERIOD_D1; // Directional bias (monthly/weekly/daily idea → D1 default)
input ENUM_TIMEFRAMES InpHigherBiasTF = PERIOD_W1; // Optional second bias filter
input ENUM_TIMEFRAMES InpSignalTF = PERIOD_H4; // Intraday environment shift (H4 or lower)
input group "=== HTF bias (long-only, reactive) ==="
input bool InpUseWeeklyBias = true; // Require W1 close > W1 EMA
input int InpBiasEmaPeriod = 50; // EMA period on bias TFs
input bool InpAllowCounterBias = false; // If false, skip longs when D1 close < D1 EMA
input group "=== Intraday shift (bearish → bullish) ==="
input int InpFastEma = 8;
input int InpSlowEma = 21;
input int InpMinBearishBars = 3; // Min consecutive bars with fast EMA < slow before cross-up
input bool InpRequireBullBody = true; // Bullish closed candle on cross bar
input group "=== Risk ==="
input double InpLots = 0.10;
input int InpMagic = 926001;
input int InpSlippagePoints = 20;
input int InpMaxSpreadPoints = 40;
input bool InpUseAtrStops = true;
input int InpAtrPeriod = 14;
input double InpSlAtrMult = 1.5;
input double InpTpAtrMult = 2.5;
input double InpMinStopPoints = 50;
input int InpMaxPositions = 1;
input group "=== Session (optional) ==="
input bool InpUseSessionFilter = false;
input int InpSessionStartHour = 7; // Server hour start
input int InpSessionEndHour = 20; // Server hour end (exclusive if cross midnight handled below)
CTrade g_trade;
int g_atrSig = INVALID_HANDLE;
int g_emaBiasD1 = INVALID_HANDLE;
int g_emaBiasW1 = INVALID_HANDLE;
int g_emaFastSig = INVALID_HANDLE;
int g_emaSlowSig = INVALID_HANDLE;
/// Effective TFs after sanity check (genetic optimizers often pass invalid ENUM integers).
ENUM_TIMEFRAMES g_effBiasTF = PERIOD_D1;
ENUM_TIMEFRAMES g_effHigherBiasTF = PERIOD_W1;
ENUM_TIMEFRAMES g_effSignalTF = PERIOD_H4;
datetime g_lastSignalBar = 0;
// Maps garbage timeframe integers from optimization to nearest supported standard period.
ENUM_TIMEFRAMES NearestStandardTf(const ENUM_TIMEFRAMES raw)
{
if(PeriodSeconds(raw) > 0)
return raw;
const ENUM_TIMEFRAMES cand[] =
{
PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1
};
const long r = (long)raw;
ENUM_TIMEFRAMES best = PERIOD_H4;
long bestDist = -1;
for(int i = 0; i < ArraySize(cand); i++)
{
if(PeriodSeconds(cand[i]) <= 0)
continue;
const long diff = r - (long)cand[i];
const long d = (diff >= 0 ? diff : -diff);
if(bestDist < 0 || d < bestDist)
{
bestDist = d;
best = cand[i];
}
}
return best;
}
string WorkSymbol()
{
string s = InpSymbol;
StringTrimLeft(s);
StringTrimRight(s);
// .set files sometimes concatenate optimization payload into string inputs (e.g. "NZDUSD||0||...")
const int bar = StringFind(s, "|");
if(bar >= 0)
s = StringSubstr(s, 0, bar);
StringTrimRight(s);
return (StringLen(s) > 0 ? s : _Symbol);
}
bool SessionOk()
{
if(!InpUseSessionFilter)
return true;
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int h = dt.hour;
if(InpSessionStartHour <= InpSessionEndHour)
return (h >= InpSessionStartHour && h < InpSessionEndHour);
return (h >= InpSessionStartHour || h < InpSessionEndHour);
}
double Buf1(const int handle, const int shift)
{
double b[];
ArraySetAsSeries(b, true);
if(CopyBuffer(handle, 0, shift, 1, b) != 1)
return 0.0;
return b[0];
}
bool CopyClose(const string sym, const ENUM_TIMEFRAMES tf, const int shift, double &out)
{
double c[];
ArraySetAsSeries(c, true);
if(CopyClose(sym, tf, shift, 1, c) != 1)
return false;
out = c[0];
return true;
}
bool HtfLongBias(const string sym)
{
double cD1 = 0.0, eD1 = 0.0;
if(!CopyClose(sym, g_effBiasTF, 1, cD1))
return false;
eD1 = Buf1(g_emaBiasD1, 1);
if(eD1 <= 0.0)
return false;
if(!InpAllowCounterBias && cD1 <= eD1)
return false;
if(InpUseWeeklyBias)
{
double cW1 = 0.0, eW1 = 0.0;
if(!CopyClose(sym, g_effHigherBiasTF, 1, cW1))
return false;
eW1 = Buf1(g_emaBiasW1, 1);
if(eW1 <= 0.0)
return false;
if(cW1 <= eW1)
return false;
}
return true;
}
int CountConsecutiveBearishEma(const string sym, const int fromShift, const int maxLookback)
{
double f[], s[];
ArraySetAsSeries(f, true);
ArraySetAsSeries(s, true);
int need = maxLookback + fromShift;
if(CopyBuffer(g_emaFastSig, 0, 0, need, f) < need)
return 0;
if(CopyBuffer(g_emaSlowSig, 0, 0, need, s) < need)
return 0;
int n = 0;
for(int i = fromShift; i < fromShift + maxLookback; i++)
{
if(f[i] <= s[i])
n++;
else
break;
}
return n;
}
bool BullishCrossOnLastClosedBar(const string sym)
{
double f1 = Buf1(g_emaFastSig, 1);
double s1 = Buf1(g_emaSlowSig, 1);
double f2 = Buf1(g_emaFastSig, 2);
double s2 = Buf1(g_emaSlowSig, 2);
if(f1 <= 0.0 || s1 <= 0.0 || f2 <= 0.0 || s2 <= 0.0)
return false;
bool crossedUp = (f1 > s1 && f2 <= s2);
if(!crossedUp)
return false;
int bearStreak = CountConsecutiveBearishEma(sym, 2, 32);
if(bearStreak < InpMinBearishBars)
return false;
if(InpRequireBullBody)
{
MqlRates r[];
ArraySetAsSeries(r, true);
if(CopyRates(sym, g_effSignalTF, 1, 1, r) != 1)
return false;
if(r[0].close <= r[0].open)
return false;
}
return true;
}
double NormalizeVolumeLots(const string sym, double lots)
{
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)
lots = MathFloor(lots / step) * step;
if(lots < minLot)
lots = minLot;
if(lots > maxLot)
lots = maxLot;
return lots;
}
int CountOurPositions(const string sym)
{
int total = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != sym)
continue;
if((int)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
total++;
}
return total;
}
bool SpreadOk(const string sym)
{
long spreadPts = SymbolInfoInteger(sym, SYMBOL_SPREAD);
return ((double)spreadPts <= (double)InpMaxSpreadPoints);
}
void ComputeStopsBuy(const string sym, const double entry, double &sl, double &tp)
{
double ptsSl = InpMinStopPoints;
double ptsTp = InpMinStopPoints * 2.0;
if(InpUseAtrStops && g_atrSig != INVALID_HANDLE)
{
double atr = Buf1(g_atrSig, 1);
if(atr > 0.0)
{
double atrPts = atr / SymbolInfoDouble(sym, SYMBOL_POINT);
ptsSl = MathMax(atrPts * InpSlAtrMult, InpMinStopPoints);
ptsTp = MathMax(atrPts * InpTpAtrMult, InpMinStopPoints);
}
}
double p = SymbolInfoDouble(sym, SYMBOL_POINT);
sl = entry - ptsSl * p;
tp = entry + ptsTp * p;
long stopsLevel = SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL);
double minDist = (double)stopsLevel * p;
if(minDist > 0.0)
{
if(entry - sl < minDist)
sl = entry - minDist;
if(tp - entry < minDist)
tp = entry + minDist;
}
int dg = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS);
sl = NormalizeDouble(sl, dg);
tp = NormalizeDouble(tp, dg);
}
int OnInit()
{
string sym = WorkSymbol();
if(!SymbolSelect(sym, true))
{
Print("TFXNZDUSD: symbol not available: ", sym);
return INIT_FAILED;
}
g_effBiasTF = NearestStandardTf(InpBiasTF);
g_effHigherBiasTF = NearestStandardTf(InpHigherBiasTF);
g_effSignalTF = NearestStandardTf(InpSignalTF);
if(g_effBiasTF != InpBiasTF || g_effHigherBiasTF != InpHigherBiasTF || g_effSignalTF != InpSignalTF)
Print("TFXNZDUSD: resolved TFs — bias ", EnumToString(g_effBiasTF), " (in ", (long)InpBiasTF, ")",
" W1 ", EnumToString(g_effHigherBiasTF), " (in ", (long)InpHigherBiasTF, ")",
" signal ", EnumToString(g_effSignalTF), " (in ", (long)InpSignalTF, ")");
if(InpBiasEmaPeriod < 1 || InpFastEma < 1 || InpSlowEma < 1 || InpAtrPeriod < 1)
{
Print("TFXNZDUSD: EMA/ATR period must be >= 1");
return INIT_PARAMETERS_INCORRECT;
}
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
g_trade.SetTypeFillingBySymbol(sym);
g_emaBiasD1 = iMA(sym, g_effBiasTF, InpBiasEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_emaBiasW1 = iMA(sym, g_effHigherBiasTF, InpBiasEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
g_emaFastSig = iMA(sym, g_effSignalTF, InpFastEma, 0, MODE_EMA, PRICE_CLOSE);
g_emaSlowSig = iMA(sym, g_effSignalTF, InpSlowEma, 0, MODE_EMA, PRICE_CLOSE);
g_atrSig = iATR(sym, g_effSignalTF, InpAtrPeriod);
if(g_emaBiasD1 == INVALID_HANDLE || g_emaFastSig == INVALID_HANDLE || g_emaSlowSig == INVALID_HANDLE ||
g_atrSig == INVALID_HANDLE)
{
Print("TFXNZDUSD: indicator init failed — check InpBiasTF/InpHigherBiasTF/InpSignalTF & symbol history");
return INIT_FAILED;
}
if(InpUseWeeklyBias && g_emaBiasW1 == INVALID_HANDLE)
{
Print("TFXNZDUSD: W1 bias handle failed");
return INIT_FAILED;
}
Print("TFXNZDUSD: ", sym, " eff TFs: bias=", EnumToString(g_effBiasTF), " higher=", EnumToString(g_effHigherBiasTF),
" signal=", EnumToString(g_effSignalTF));
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(g_emaBiasD1 != INVALID_HANDLE) IndicatorRelease(g_emaBiasD1);
if(g_emaBiasW1 != INVALID_HANDLE) IndicatorRelease(g_emaBiasW1);
if(g_emaFastSig != INVALID_HANDLE) IndicatorRelease(g_emaFastSig);
if(g_emaSlowSig != INVALID_HANDLE) IndicatorRelease(g_emaSlowSig);
if(g_atrSig != INVALID_HANDLE) IndicatorRelease(g_atrSig);
}
void OnTick()
{
string sym = WorkSymbol();
datetime barOpen = iTime(sym, g_effSignalTF, 0);
if(barOpen == 0)
return;
if(barOpen == g_lastSignalBar)
return;
datetime prevBar = iTime(sym, g_effSignalTF, 1);
if(prevBar == 0)
return;
g_lastSignalBar = barOpen;
if(!SessionOk())
return;
if(!SpreadOk(sym))
return;
if(CountOurPositions(sym) >= InpMaxPositions)
return;
if(!HtfLongBias(sym))
return;
if(!BullishCrossOnLastClosedBar(sym))
return;
MqlTick tick;
if(!SymbolInfoTick(sym, tick))
return;
double lots = NormalizeVolumeLots(sym, InpLots);
double sl = 0.0, tp = 0.0;
ComputeStopsBuy(sym, tick.ask, sl, tp);
if(!g_trade.Buy(lots, sym, tick.ask, sl, tp, "TFX NZDUSD shift"))
Print("TFXNZDUSD Buy failed ret=", g_trade.ResultRetcode(), " ", g_trade.ResultRetcodeDescription());
}
//+------------------------------------------------------------------+