diff --git a/ai/xauusd_h1/README.md b/ai/xauusd_h1/README.md new file mode 100644 index 0000000..ba1da78 --- /dev/null +++ b/ai/xauusd_h1/README.md @@ -0,0 +1,46 @@ +# XAUUSD H1 — ONNX action model + +Same pipeline as **`../xauusd_m15`**, but **H1** bars, **H1-scaled label windows** (~wall-clock parity with M15 defaults), and **`XAUUSD_H1_ActionEA.mq5`**. + +## Label scaling (vs M15) + +| M15 (bars) | Wall time | H1 (bars) | +|------------|-----------|-----------| +| horizon 32 | ~8 h | 8 | +| local 24 | ~6 h | 6 | +| pullback 20| ~5 h | 5 | + +## Setup + +1. MT5: **XAUUSD** visible; download **H1** history. +2. Python: + +```bash +cd ai/xauusd_h1 +pip install -r requirements.txt +python main.py +``` + +Env: `XAU_SYMBOL`, **`XAU_H1_LOOKBACK`** (default **48**, must match EA **InpLookback**), `XAU_EPOCHS`, `XAU_BATCH`, `SESSION_HOUR_OFFSET`. + +3. Copy **`models/XAUUSD_H1_action.onnx`** next to **`XAUUSD_H1_ActionEA.mq5`** (for `#resource` embed) or adjust include path per your workflow. +4. Compile EA on **H1** chart; paste **24** floats into **InpFeatMinStr** / **InpFeatMaxStr** from training stdout. + +## Files + +| File | Role | +|------|------| +| `main.py` | MT5 H1 fetch, train, `XAUUSD_H1_action.onnx` + meta | +| `labeling.py` | `compute_action_labels` (H1 default horizons) | +| `features.py` | 24-dim features (same order as M15 EA) | +| `XAUUSD_H1_ActionEA.mq5` | Inference + trading | +| `XAUUSD_H1_ActionEA_optimize.set` | Tester optimization skeleton | + +Feature semantics: **`../xauusd_m15/FRONTLINE_RSI_INTEGRATION.md`**. + +## ONNX + +- Input: `[1, lookback, 24]` float32, row **0** = newest bar. +- Output: `[1, 5]` softmax. + +Research tooling — not investment advice. diff --git a/ai/xauusd_h1/XAUUSD_H1_ActionEA.mq5 b/ai/xauusd_h1/XAUUSD_H1_ActionEA.mq5 new file mode 100644 index 0000000..851c578 --- /dev/null +++ b/ai/xauusd_h1/XAUUSD_H1_ActionEA.mq5 @@ -0,0 +1,372 @@ +//+------------------------------------------------------------------+ +//| XAUUSD_H1_ActionEA.mq5 | +//| ONNX softmax [5]: HOLD, BUY, SELL_SHORT, CLOSE_LONG, CLOSE_SHORT | +//| 24 features: base 13 + RSI/frontline (see ../xauusd_m15 doc) | +//| Train: ai/xauusd_h1/main.py → XAUUSD_H1_action.onnx | +//| Exits: model CLOSE_* + optional InpTakeProfitATR; adverse ATR | +//+------------------------------------------------------------------+ +#property copyright "Profitable EA Project" +#property version "1.00" + +#include + +#resource "XAUUSD_H1_action.onnx" as uchar ExtModel[] + +#define FEAT_COUNT 24 + +input group "Model" +input int InpLookback = 48; +// 0 = legacy: p(BUY)>=InpProbBuy etc.; 1 = directional beats HOLD (5-class softmax) +input int InpEntryMode = 1; +input double InpProbBuy = 0.18; +input double InpProbSell = 0.18; +input double InpMinBeatHold = 0.0; +input int InpExitMode = 2; +input double InpProbCloseL = 0.18; +input double InpProbCloseS = 0.18; +input double InpMinCloseBeatHold = 0.0; + +input group "Session (match Python SESSION_HOUR_OFFSET)" +input int InpSessionHourOffset = 0; + +input group "Scaler: paste 24 floats each from python main.py" +input string InpFeatMinStr = ""; +input string InpFeatMaxStr = ""; + +input group "Risk" +input double InpLotSize = 0.01; +input int InpMagic = 902016; +input int InpSlippage = 30; +input double InpMaxAdverseATR = 2.0; +input double InpTakeProfitATR = 0.0; + +double g_feat_min[FEAT_COUNT]; +double g_feat_max[FEAT_COUNT]; + +CTrade trade; +long g_onnx = INVALID_HANDLE; +datetime g_last_bar = 0; + +void InitDefaultScalerBounds() +{ + double def_min[FEAT_COUNT] = { + 0,0,0,0,0,0,-0.05,-0.05,0,-0.02,1.0,0,0.1, + 0,0,-1,-0.2,-0.2,0,0,0,0,0,0 + }; + double def_max[FEAT_COUNT] = { + 5000,5000,5000,5000,1,1,0.05,0.05,0.05,0.02,1.02,1,5.0, + 1,1,1,0.2,0.2,1,1,1,1,1,1 + }; + for(int i = 0; i < FEAT_COUNT; i++) + { + g_feat_min[i] = def_min[i]; + g_feat_max[i] = def_max[i]; + } +} + +bool ParseFeatCsv(const string s, double &arr[]) +{ + if(StringLen(s) < 3) return false; + string parts[]; + int n = StringSplit(s, ',', parts); + if(n != FEAT_COUNT) return false; + for(int i = 0; i < FEAT_COUNT; i++) + arr[i] = StringToDouble(parts[i]); + return true; +} + +int OnInit() +{ + InitDefaultScalerBounds(); + trade.SetExpertMagicNumber(InpMagic); + trade.SetDeviationInPoints(InpSlippage); + trade.SetTypeFilling(ORDER_FILLING_IOC); + + if(StringLen(InpFeatMinStr) > 0 && ParseFeatCsv(InpFeatMinStr, g_feat_min)) + Print("Loaded InpFeatMinStr (24)"); + if(StringLen(InpFeatMaxStr) > 0 && ParseFeatCsv(InpFeatMaxStr, g_feat_max)) + Print("Loaded InpFeatMaxStr (24)"); + + g_onnx = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS); + if(g_onnx == INVALID_HANDLE) + { + Print("OnnxCreateFromBuffer failed ", GetLastError()); + return INIT_FAILED; + } + + const long inShape[] = {1, InpLookback, FEAT_COUNT}; + if(!OnnxSetInputShape(g_onnx, 0, inShape)) + { + Print("OnnxSetInputShape failed ", GetLastError()); + OnnxRelease(g_onnx); + return INIT_FAILED; + } + const long outShape[] = {1, 5}; + if(!OnnxSetOutputShape(g_onnx, 0, outShape)) + { + Print("OnnxSetOutputShape failed ", GetLastError()); + OnnxRelease(g_onnx); + return INIT_FAILED; + } + return INIT_SUCCEEDED; +} + +void OnDeinit(const int r) +{ + if(g_onnx != INVALID_HANDLE) OnnxRelease(g_onnx); +} + +double AtrNow() +{ + double b[]; + ArraySetAsSeries(b, true); + int h = iATR(_Symbol, PERIOD_CURRENT, 14); + if(h == INVALID_HANDLE) return 0; + if(CopyBuffer(h, 0, 0, 2, b) < 1) { IndicatorRelease(h); return 0; } + double v = b[0]; + IndicatorRelease(h); + return v; +} + +bool AdverseExit(const long type, const double open_price) +{ + double atr = AtrNow(); + if(atr <= 0) return false; + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(type == POSITION_TYPE_BUY) + { + double adv = (open_price - bid) / atr; + return adv >= InpMaxAdverseATR; + } + double adv = (ask - open_price) / atr; + return adv >= InpMaxAdverseATR; +} + +bool ProfitExit(const long type, const double open_price) +{ + if(InpTakeProfitATR <= 0.0) return false; + double atr = AtrNow(); + if(atr <= 0.0) return false; + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(type == POSITION_TYPE_BUY) + return (bid - open_price) >= InpTakeProfitATR * atr; + return (open_price - ask) >= InpTakeProfitATR * atr; +} + +bool ModelCloseLong(const double p0, const double p1, const double p3) +{ + if(InpExitMode == 0) + return (p3 >= InpProbCloseL); + if(InpExitMode == 1) + return (p3 > p0 + InpMinCloseBeatHold && p3 > p1); + return (p3 > p0 + InpMinCloseBeatHold); +} + +bool ModelCloseShort(const double p0, const double p2, const double p4) +{ + if(InpExitMode == 0) + return (p4 >= InpProbCloseS); + if(InpExitMode == 1) + return (p4 > p0 + InpMinCloseBeatHold && p4 > p2); + return (p4 > p0 + InpMinCloseBeatHold); +} + +void ScaleFeatures(const float &raw[], float &out[]) +{ + for(int f = 0; f < FEAT_COUNT; f++) + { + double den = g_feat_max[f] - g_feat_min[f]; + if(den < 1e-12) den = 1e-12; + double x = (double)raw[f] - g_feat_min[f]; + out[f] = (float)MathMax(0.0, MathMin(1.0, x / den)); + } +} + +bool PrepareMatrix(matrixf &M) +{ + int L = InpLookback; + double open[], high[], low[], close[]; + long vol[]; + datetime bt[]; + ArraySetAsSeries(open, true); + ArraySetAsSeries(high, true); + ArraySetAsSeries(low, true); + ArraySetAsSeries(close, true); + ArraySetAsSeries(vol, true); + ArraySetAsSeries(bt, true); + + int need = L + 55; + if(CopyOpen(_Symbol, PERIOD_CURRENT, 0, need, open) < L) return false; + if(CopyHigh(_Symbol, PERIOD_CURRENT, 0, need, high) < L) return false; + if(CopyLow(_Symbol, PERIOD_CURRENT, 0, need, low) < L) return false; + if(CopyClose(_Symbol, PERIOD_CURRENT, 0, need, close) < L) return false; + if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, need, vol) < L) return false; + if(CopyTime(_Symbol, PERIOD_CURRENT, 0, need, bt) < L) return false; + + double rsi7[], rsi14[], rsi21[], ema20[], ema50[], atr[]; + ArraySetAsSeries(rsi7, true); + ArraySetAsSeries(rsi14, true); + ArraySetAsSeries(rsi21, true); + ArraySetAsSeries(ema20, true); + ArraySetAsSeries(ema50, true); + ArraySetAsSeries(atr, true); + + int h7 = iRSI(_Symbol, PERIOD_CURRENT, 7, PRICE_CLOSE); + int h14 = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); + int h21 = iRSI(_Symbol, PERIOD_CURRENT, 21, PRICE_CLOSE); + int hE20 = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE); + int hE50 = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE); + int hA = iATR(_Symbol, PERIOD_CURRENT, 14); + if(h7 == INVALID_HANDLE || h14 == INVALID_HANDLE || h21 == INVALID_HANDLE || + hE20 == INVALID_HANDLE || hE50 == INVALID_HANDLE || hA == INVALID_HANDLE) + return false; + + if(CopyBuffer(h7, 0, 0, need, rsi7) < L || + CopyBuffer(h14, 0, 0, need, rsi14) < L || + CopyBuffer(h21, 0, 0, need, rsi21) < L || + CopyBuffer(hE20, 0, 0, need, ema20) < L || + CopyBuffer(hE50, 0, 0, need, ema50) < L || + CopyBuffer(hA, 0, 0, need, atr) < L) + { + IndicatorRelease(h7); IndicatorRelease(h14); IndicatorRelease(h21); + IndicatorRelease(hE20); IndicatorRelease(hE50); IndicatorRelease(hA); + return false; + } + IndicatorRelease(h7); IndicatorRelease(h14); IndicatorRelease(h21); + IndicatorRelease(hE20); IndicatorRelease(hE50); IndicatorRelease(hA); + + M.Resize(L, FEAT_COUNT); + const double RSI_OB = 70.0; + const double RSI_OS = 30.0; + + for(int i = 0; i < L; i++) + { + double vma = 0; + int cnt = 0; + for(int k = i; k < i + 20 && k < ArraySize(vol); k++) { vma += (double)vol[k]; cnt++; } + if(cnt < 1) cnt = 1; + vma /= cnt; + + double r0 = rsi14[i]; + double r1 = (i + 1 < ArraySize(rsi14)) ? rsi14[i + 1] : r0; + double r2 = (i + 2 < ArraySize(rsi14)) ? rsi14[i + 2] : r1; + double rv7 = rsi7[i]; + double rv21 = rsi21[i]; + + double spread = (r0 - rv7) / 50.0; + if(spread > 1.0) spread = 1.0; + if(spread < -1.0) spread = -1.0; + double vel = (r0 - r1) / 25.0; + double acc = ((r0 - r1) - (r1 - r2)) / 25.0; + double dist_mid = MathAbs(r0 - 50.0) / 50.0; + double c_ob = (r1 < RSI_OB && r0 >= RSI_OB) ? 1.0 : 0.0; + double c_os = (r1 > RSI_OS && r0 <= RSI_OS) ? 1.0 : 0.0; + double c50u = (r1 < 50.0 && r0 >= 50.0) ? 1.0 : 0.0; + double c50d = (r1 > 50.0 && r0 <= 50.0) ? 1.0 : 0.0; + + MqlDateTime st; + TimeToStruct(bt[i], st); + int hr = (st.hour + InpSessionHourOffset) % 24; + if(hr < 0) hr += 24; + double asian = (hr >= 0 && hr < 8) ? 1.0 : 0.0; + + float raw[FEAT_COUNT]; + raw[0] = (float)open[i]; + raw[1] = (float)high[i]; + raw[2] = (float)low[i]; + raw[3] = (float)close[i]; + raw[4] = (float)((double)vol[i] / 1000000.0); + raw[5] = (float)(r0 / 100.0); + raw[6] = (float)((ema20[i] - close[i]) / close[i]); + raw[7] = (float)((ema50[i] - close[i]) / close[i]); + raw[8] = (float)(atr[i] / close[i]); + double pc = (i < L - 1) ? (close[i] - close[i + 1]) / close[i + 1] : 0.0; + raw[9] = (float)pc; + raw[10] = (float)(high[i] / low[i]); + raw[11] = (float)(vma / 1000000.0); + raw[12] = (float)(vma > 0 ? (double)vol[i] / vma : 1.0); + raw[13] = (float)(rv7 / 100.0); + raw[14] = (float)(rv21 / 100.0); + raw[15] = (float)spread; + raw[16] = (float)vel; + raw[17] = (float)acc; + raw[18] = (float)dist_mid; + raw[19] = (float)c_ob; + raw[20] = (float)c_os; + raw[21] = (float)c50u; + raw[22] = (float)c50d; + raw[23] = (float)asian; + + float sc[FEAT_COUNT]; + ScaleFeatures(raw, sc); + for(int j = 0; j < FEAT_COUNT; j++) + M[i][j] = sc[j]; + } + return true; +} + +void OnTick() +{ + datetime t = iTime(_Symbol, PERIOD_CURRENT, 0); + if(t == g_last_bar) return; + g_last_bar = t; + + matrixf Min; + if(!PrepareMatrix(Min)) + { + Print("PrepareMatrix failed"); + return; + } + + vectorf out; + out.Resize(5); + if(!OnnxRun(g_onnx, ONNX_NO_CONVERSION, Min, out)) + { + Print("OnnxRun failed ", GetLastError()); + return; + } + + double p0 = out[0], p1 = out[1], p2 = out[2], p3 = out[3], p4 = out[4]; + Print("ONNX H1 HOLD=", p0, " BUY=", p1, " SELL=", p2, " CL=", p3, " CS=", p4); + + if(!PositionSelect(_Symbol)) + { + if(InpEntryMode == 1) + { + double dir = MathMax(p1, p2); + if(dir <= p0 + InpMinBeatHold) + return; + if(p1 >= p2 && p1 > p0 + InpMinBeatHold) + trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "AI H1 BUY"); + else if(p2 > p1 && p2 > p0 + InpMinBeatHold) + trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "AI H1 SELL"); + } + else + { + if(p1 >= InpProbBuy && p1 >= p2) + trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "AI H1 BUY"); + else if(p2 >= InpProbSell && p2 > p1) + trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "AI H1 SELL"); + } + return; + } + + long typ = (long)PositionGetInteger(POSITION_TYPE); + double opn = PositionGetDouble(POSITION_PRICE_OPEN); + if(AdverseExit(typ, opn)) + { + trade.PositionClose(_Symbol); + return; + } + if(ProfitExit(typ, opn)) + { + trade.PositionClose(_Symbol); + return; + } + if(typ == POSITION_TYPE_BUY && ModelCloseLong(p0, p1, p3)) + trade.PositionClose(_Symbol); + else if(typ == POSITION_TYPE_SELL && ModelCloseShort(p0, p2, p4)) + trade.PositionClose(_Symbol); +} diff --git a/ai/xauusd_h1/XAUUSD_H1_ActionEA_optimize.set b/ai/xauusd_h1/XAUUSD_H1_ActionEA_optimize.set new file mode 100644 index 0000000..95fbd4f --- /dev/null +++ b/ai/xauusd_h1/XAUUSD_H1_ActionEA_optimize.set @@ -0,0 +1,24 @@ +; XAUUSD_H1_ActionEA — optimization preset (match trained InpLookback to ONNX) +; Copy to MetaQuotes\Terminal\\MQL5\Profiles\Tester\ +; +; Model +InpLookback=48||32||8||96||Y +InpEntryMode=1||0||1||1||Y +InpProbBuy=0.18||0.14||0.02||0.26||Y +InpProbSell=0.18||0.14||0.02||0.26||Y +InpMinBeatHold=0.0||0.0||0.01||0.05||Y +InpExitMode=2||0||1||2||Y +InpProbCloseL=0.18||0.14||0.02||0.26||Y +InpProbCloseS=0.18||0.14||0.02||0.26||Y +InpMinCloseBeatHold=0.0||0.0||0.01||0.04||Y +; Session +InpSessionHourOffset=0||-3||1||3||N +; Scaler +InpFeatMinStr= +InpFeatMaxStr= +; Risk +InpLotSize=0.01||0.01||0.001000||0.100000||N +InpMagic=902016||902016||1||9020160||N +InpSlippage=30||30||1||300||N +InpMaxAdverseATR=2.0||1.0||0.25||3.5||Y +InpTakeProfitATR=0.0||0.0||0.25||3.0||Y diff --git a/ai/xauusd_h1/XAUUSD_H1_action.onnx b/ai/xauusd_h1/XAUUSD_H1_action.onnx new file mode 100644 index 0000000..a18858f Binary files /dev/null and b/ai/xauusd_h1/XAUUSD_H1_action.onnx differ diff --git a/ai/xauusd_h1/__pycache__/features.cpython-312.pyc b/ai/xauusd_h1/__pycache__/features.cpython-312.pyc new file mode 100644 index 0000000..8f0d873 Binary files /dev/null and b/ai/xauusd_h1/__pycache__/features.cpython-312.pyc differ diff --git a/ai/xauusd_h1/__pycache__/labeling.cpython-312.pyc b/ai/xauusd_h1/__pycache__/labeling.cpython-312.pyc new file mode 100644 index 0000000..b7fd323 Binary files /dev/null and b/ai/xauusd_h1/__pycache__/labeling.cpython-312.pyc differ diff --git a/ai/xauusd_h1/features.py b/ai/xauusd_h1/features.py new file mode 100644 index 0000000..9762037 --- /dev/null +++ b/ai/xauusd_h1/features.py @@ -0,0 +1,167 @@ +""" +Feature pipeline: base 13 (EA-compatible) + 11 RSI / session features. + +Same 24 dims as XAUUSD M15 EA (see ../xauusd_m15/FRONTLINE_RSI_INTEGRATION.md). +RSI uses Wilder smoothing (ewm alpha=1/period) to align with MT5 iRSI. +""" + +from __future__ import annotations + +import os + +import numpy as np +import pandas as pd + +NUM_BASE_FEATURES = 13 +NUM_RSI_EXTRA = 11 +NUM_FEATURES = NUM_BASE_FEATURES + NUM_RSI_EXTRA # 24 + +RSI_OVERBOUGHT = 70.0 +RSI_OVERSOLD = 30.0 + + +def wilder_rsi(close: pd.Series, period: int) -> np.ndarray: + """Wilder RSI (matches MetaTrader iRSI closely).""" + delta = close.diff() + gain = delta.clip(lower=0.0) + loss = (-delta).clip(lower=0.0) + avg_g = gain.ewm(alpha=1.0 / period, min_periods=period, adjust=False).mean() + avg_l = loss.ewm(alpha=1.0 / period, min_periods=period, adjust=False).mean() + rs = avg_g / avg_l.replace(0, np.nan) + rsi = 100.0 - (100.0 / (1.0 + rs)) + return rsi.fillna(50.0).to_numpy(dtype=np.float64) + + +def prepare_features_full( + df: pd.DataFrame, + *, + session_hour_offset: int | None = None, +) -> pd.DataFrame: + """ + Build (N, 24) feature table, chronological index matching df. + Drops first ~50 rows (warmup). + """ + if session_hour_offset is None: + session_hour_offset = int(os.environ.get("SESSION_HOUR_OFFSET", "0")) + + o = df["open"].to_numpy(dtype=np.float64) + h = df["high"].to_numpy(dtype=np.float64) + l = df["low"].to_numpy(dtype=np.float64) + c = df["close"].astype(float) + vol = df["tick_volume"].to_numpy(dtype=np.float64) + n = len(df) + idx = df.index + + rsi7 = wilder_rsi(c, 7) + rsi14 = wilder_rsi(c, 14) + rsi21 = wilder_rsi(c, 21) + + ema20 = c.ewm(span=20, adjust=False).mean().to_numpy() + ema50 = c.ewm(span=50, adjust=False).mean().to_numpy() + + tr = np.maximum( + h - l, + np.maximum(np.abs(h - np.roll(c.to_numpy(), 1)), np.abs(l - np.roll(c.to_numpy(), 1))), + ) + tr[0] = h[0] - l[0] + atr = pd.Series(tr).rolling(14).mean().to_numpy() + + vol_ma = np.zeros(n) + for j in range(n): + s = 0.0 + cnt = 0 + for k in range(j, min(j + 20, n)): + s += vol[k] + cnt += 1 + vol_ma[j] = s / cnt if cnt else vol[j] + + pc_ea = np.zeros(n) + cvals = c.to_numpy() + for j in range(1, n): + den = cvals[j - 1] + pc_ea[j] = (cvals[j] - den) / den if den else 0.0 + + hours = np.zeros(n, dtype=np.int32) + for j in range(n): + ts = idx[j] + try: + hts = int(ts.hour) + except Exception: + hts = 0 + hours[j] = (hts + session_hour_offset) % 24 + + rows = [] + for j in range(n): + r0 = rsi14[j] + r1 = rsi14[j - 1] if j > 0 else r0 + r2 = rsi14[j - 2] if j > 1 else r1 + + spread = np.clip((r0 - rsi7[j]) / 50.0, -1.0, 1.0) + vel = (r0 - r1) / 25.0 + acc = ((r0 - r1) - (r1 - r2)) / 25.0 + dist_mid = abs(r0 - 50.0) / 50.0 + + cross_ob = 1.0 if (r1 < RSI_OVERBOUGHT and r0 >= RSI_OVERBOUGHT) else 0.0 + cross_os = 1.0 if (r1 > RSI_OVERSOLD and r0 <= RSI_OVERSOLD) else 0.0 + cross_50_up = 1.0 if (r1 < 50.0 and r0 >= 50.0) else 0.0 + cross_50_dn = 1.0 if (r1 > 50.0 and r0 <= 50.0) else 0.0 + asian = 1.0 if (0 <= hours[j] < 8) else 0.0 + + rows.append( + [ + float(o[j]), + float(h[j]), + float(l[j]), + float(cvals[j]), + float(vol[j] / 1_000_000.0), + float(rsi14[j] / 100.0), + float((ema20[j] - cvals[j]) / cvals[j]) if cvals[j] else 0.0, + float((ema50[j] - cvals[j]) / cvals[j]) if cvals[j] else 0.0, + float(atr[j] / cvals[j]) if cvals[j] else 0.0, + float(pc_ea[j]), + float(h[j] / l[j]) if l[j] else 1.0, + float(vol_ma[j] / 1_000_000.0), + float(vol[j] / vol_ma[j]) if vol_ma[j] > 0 else 1.0, + float(rsi7[j] / 100.0), + float(rsi21[j] / 100.0), + float(spread), + float(vel), + float(acc), + float(dist_mid), + float(cross_ob), + float(cross_os), + float(cross_50_up), + float(cross_50_dn), + float(asian), + ] + ) + + cols = [ + "open", + "high", + "low", + "close", + "tick_volume", + "rsi", + "ema20_n", + "ema50_n", + "atr_n", + "price_change", + "high_low_ratio", + "volume_ma", + "volume_ratio", + "rsi7_n", + "rsi21_n", + "rsi_fast_slow_spread", + "rsi_velocity", + "rsi_accel", + "rsi_dist_mid_50", + "rsi_cross_overbought", + "rsi_cross_oversold", + "rsi_cross_50_up", + "rsi_cross_50_down", + "session_asian_utc", + ] + + out = pd.DataFrame(rows, index=idx, columns=cols) + return out.iloc[50:].copy() diff --git a/ai/xauusd_h1/labeling.py b/ai/xauusd_h1/labeling.py new file mode 100644 index 0000000..8bfeba4 --- /dev/null +++ b/ai/xauusd_h1/labeling.py @@ -0,0 +1,128 @@ +""" +Buy-low / sell-high style labels for OHLCV bars (no fixed SL/TP in labels). + +H1 defaults scale M15 bar counts to ~similar wall-clock horizons: + M15 horizon=32 -> 8h -> H1 horizon=8 + M15 local=24 -> 6h -> H1 local=6 + M15 pullback=20 -> 5h -> H1 pullback=5 + +Classes (integer, matches EA): + 0 HOLD + 1 BUY — forward upside vs ATR + local swing low + 2 SELL_SHORT — forward downside vs ATR + local swing high + 3 CLOSE_LONG — past-only: pullback from recent range high + 4 CLOSE_SHORT — past-only: bounce from recent range low +""" + +from __future__ import annotations + +import numpy as np +import pandas as pd + + +def atr_series(df: pd.DataFrame, period: int = 14) -> pd.Series: + high, low, close = df["high"], df["low"], df["close"] + tr = pd.concat( + [ + high - low, + (high - close.shift()).abs(), + (low - close.shift()).abs(), + ], + axis=1, + ).max(axis=1) + return tr.rolling(period).mean() + + +def compute_action_labels( + df: pd.DataFrame, + *, + horizon: int = 8, + local_window: int = 6, + pullback_window: int = 5, + k_forward_atr: float = 0.75, + local_pct: float = 0.28, + pullback_mult: float = 0.55, + trend_mult: float = 1.05, +) -> pd.Series: + """ + Return a Series of int labels 0..4 aligned to df index. + Last `horizon` rows → HOLD (no forward path for buy/sell scoring). + """ + close = df["close"].values + high = df["high"].values + low = df["low"].values + n = len(df) + atr = atr_series(df, 14).values + labels = np.zeros(n, dtype=np.int64) + + lw = local_window + pw = pullback_window + need = max(lw, pw) + 2 + + for t in range(n): + if t < need or t >= n - horizon: + labels[t] = 0 + continue + + a = atr[t] + if not np.isfinite(a) or a <= 0: + a = close[t] * 1e-4 + + sl = low[t + 1 : t + horizon + 1] + sh = high[t + 1 : t + horizon + 1] + fwd_max = float(np.max(sh)) + fwd_min = float(np.min(sl)) + up_move = (fwd_max - close[t]) / a + down_move = (close[t] - fwd_min) / a + + loc_low = float(np.min(low[t - lw : t + 1])) + loc_high = float(np.max(high[t - lw : t + 1])) + rng = max(loc_high - loc_low, a * 0.15) + near_low = (close[t] - loc_low) / rng <= local_pct + near_high = (loc_high - close[t]) / rng <= local_pct + + buy_sig = near_low and (up_move >= k_forward_atr) and (up_move >= down_move * 0.85) + sell_sig = near_high and (down_move >= k_forward_atr) and (down_move > up_move * 1.05) + + seg_h = high[t - pw : t + 1] + seg_l = low[t - pw : t + 1] + rh = float(np.max(seg_h)) + rl = float(np.min(seg_l)) + range_atr = (rh - rl) / a + pull_from_high = (rh - close[t]) / a + bounce_from_low = (close[t] - rl) / a + + exit_long = ( + range_atr >= trend_mult + and pull_from_high >= pullback_mult + and close[t] < close[t - 1] + ) + exit_short = ( + range_atr >= trend_mult + and bounce_from_low >= pullback_mult + and close[t] > close[t - 1] + ) + + if exit_long and not buy_sig: + labels[t] = 3 + elif exit_short and not sell_sig: + labels[t] = 4 + elif buy_sig and not sell_sig: + labels[t] = 1 + elif sell_sig and not buy_sig: + labels[t] = 2 + elif buy_sig and sell_sig: + labels[t] = 1 if up_move >= down_move else 2 + else: + labels[t] = 0 + + return pd.Series(labels, index=df.index, name="action_label") + + +def class_weights(y: np.ndarray, n_classes: int = 5) -> dict[int, float]: + from sklearn.utils.class_weight import compute_class_weight + + y_int = y.astype(int) + classes = np.arange(n_classes) + cw = compute_class_weight("balanced", classes=classes, y=y_int) + return {i: float(cw[i]) for i in range(n_classes)} diff --git a/ai/xauusd_h1/main.py b/ai/xauusd_h1/main.py new file mode 100644 index 0000000..b531629 --- /dev/null +++ b/ai/xauusd_h1/main.py @@ -0,0 +1,209 @@ +""" +XAUUSD H1 — ONNX action model (buy / sell short / close long / close short / hold). + +Same 24 features as M15 stack; labels use H1-scaled horizons (~wall-clock parity with M15). +Row order matches XAUUSD_H1_ActionEA.mq5 (row 0 = newest bar). +Data: MT5, 2008–2026 (limited by downloaded history). +""" + +from __future__ import annotations + +import json +import os +import pickle +import sys +from datetime import datetime, timedelta + +import MetaTrader5 as mt5 +import numpy as np +import pandas as pd +import tensorflow as tf +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import MinMaxScaler +from tensorflow import keras +from tensorflow.keras import layers +from tqdm import tqdm +import tf2onnx +import onnx + +from labeling import class_weights, compute_action_labels +from features import NUM_FEATURES, prepare_features_full + +NUM_CLASSES = 5 +CLASS_NAMES = ["HOLD", "BUY", "SELL_SHORT", "CLOSE_LONG", "CLOSE_SHORT"] + + +def fetch_mt5_range( + symbol: str, + timeframe: int, + start_date: datetime, + end_date: datetime, +) -> pd.DataFrame: + if not mt5.initialize(): + raise RuntimeError(f"MT5 init failed: {mt5.last_error()}") + + info = mt5.symbol_info(symbol) + if info is None: + mt5.shutdown() + raise ValueError(f"Symbol {symbol} not found") + if not info.visible and not mt5.symbol_select(symbol, True): + mt5.shutdown() + raise ValueError(f"Cannot select {symbol}") + + all_rows: list[dict] = [] + chunk_days = 120 + cur = start_date + while cur < end_date: + chunk_end = min(cur + timedelta(days=chunk_days), end_date) + rates = mt5.copy_rates_range(symbol, timeframe, cur, chunk_end) + if rates is not None and len(rates) > 1: + for row in rates: + all_rows.append({n: row[n] for n in rates.dtype.names}) + cur = chunk_end + + if not all_rows: + mt5.shutdown() + raise ValueError("No rates returned — download XAUUSD H1 in MT5 History Center") + + df = pd.DataFrame(all_rows) + df["time"] = pd.to_datetime(df["time"], unit="s") + df = df.set_index("time").sort_index() + df = df[~df.index.duplicated(keep="first")] + return df + + +def create_sequences( + X: np.ndarray, y: np.ndarray, lookback: int +) -> tuple[np.ndarray, np.ndarray]: + xs, ys = [], [] + for i in tqdm(range(lookback - 1, len(X)), desc="sequences"): + window = X[i - lookback + 1 : i + 1].copy() + window = window[::-1] + xs.append(window) + ys.append(y[i]) + return np.asarray(xs, dtype=np.float32), np.asarray(ys, dtype=np.int64) + + +def build_model(lookback: int, n_feat: int) -> keras.Model: + inp = layers.Input(shape=(lookback, n_feat)) + x = layers.LSTM(96, return_sequences=True)(inp) + x = layers.Dropout(0.25)(x) + x = layers.LSTM(48)(x) + x = layers.Dropout(0.25)(x) + x = layers.Dense(32, activation="relu")(x) + out = layers.Dense(NUM_CLASSES, activation="softmax", name="action_probs")(x) + model = keras.Model(inp, out) + model.compile( + optimizer=keras.optimizers.Adam(1e-3), + loss="sparse_categorical_crossentropy", + metrics=["accuracy"], + ) + return model + + +def main() -> int: + symbol = os.environ.get("XAU_SYMBOL", "XAUUSD") + lookback = int(os.environ.get("XAU_H1_LOOKBACK", os.environ.get("XAU_LOOKBACK", "48"))) + epochs = int(os.environ.get("XAU_EPOCHS", "40")) + batch_size = int(os.environ.get("XAU_BATCH", "64")) + + start_date = datetime(2008, 1, 1) + end_date = datetime(2026, 12, 31) + + out_dir = os.path.join(os.path.dirname(__file__), "models") + os.makedirs(out_dir, exist_ok=True) + onnx_path = os.path.join(out_dir, f"{symbol}_H1_action.onnx") + meta_path = os.path.join(out_dir, f"{symbol}_H1_action_meta.json") + + print("Fetching MT5 H1 data …") + try: + raw = fetch_mt5_range(symbol, mt5.TIMEFRAME_H1, start_date, end_date) + finally: + mt5.shutdown() + print(f"Bars: {len(raw)} range: {raw.index[0]} → {raw.index[-1]}") + + feat = prepare_features_full(raw) + labels_full = compute_action_labels(raw) + labels = labels_full.loc[feat.index] + + y = labels.loc[feat.index].values.astype(np.int64) + X_raw = feat.values.astype(np.float32) + + valid = np.isfinite(X_raw).all(axis=1) & (y >= 0) & (y < NUM_CLASSES) + X_raw = X_raw[valid] + y = y[valid] + + print("Label counts:", {CLASS_NAMES[i]: int((y == i).sum()) for i in range(NUM_CLASSES)}) + + scaler = MinMaxScaler() + Xn = scaler.fit_transform(X_raw).astype(np.float32) + + X_seq, y_seq = create_sequences(Xn, y, lookback) + if len(X_seq) < 500: + print("ERROR: Too few sequences — need more H1 history in MT5.") + return 1 + + X_train, X_val, y_train, y_val = train_test_split( + X_seq, y_seq, test_size=0.15, shuffle=False + ) + + cw = class_weights(y_train, NUM_CLASSES) + sample_w = np.array([cw[int(c)] for c in y_train], dtype=np.float32) + + model = build_model(lookback, NUM_FEATURES) + model.summary() + + model.fit( + X_train, + y_train, + sample_weight=sample_w, + validation_data=(X_val, y_val), + epochs=epochs, + batch_size=batch_size, + verbose=1, + callbacks=[ + keras.callbacks.EarlyStopping( + monitor="val_loss", patience=8, restore_best_weights=True + ), + keras.callbacks.ReduceLROnPlateau( + monitor="val_loss", factor=0.5, patience=4, min_lr=1e-6 + ), + ], + ) + + spec = (tf.TensorSpec((None, lookback, NUM_FEATURES), tf.float32, name="input"),) + onnx_m, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13) + onnx.save_model(onnx_m, onnx_path) + + with open(onnx_path.replace(".onnx", "_scaler.pkl"), "wb") as f: + pickle.dump(scaler, f) + + meta = { + "symbol": symbol, + "timeframe": "H1", + "lookback": lookback, + "num_features": int(NUM_FEATURES), + "feature_columns": feat.columns.tolist(), + "num_classes": NUM_CLASSES, + "class_names": CLASS_NAMES, + "label_horizon_bars": 8, + "label_note": "H1 labeling defaults: horizon=8, local=6, pullback=5 (~M15 wall-clock parity)", + "scaler_feature_min": scaler.data_min_.tolist(), + "scaler_feature_max": scaler.data_max_.tolist(), + "scaler_scale": scaler.scale_.tolist() if hasattr(scaler, "scale_") else None, + "notes": "MinMax in EA; row0=newest. Match EA InpLookback to lookback here.", + } + with open(meta_path, "w", encoding="utf-8") as f: + json.dump(meta, f, indent=2) + + print(f"Saved: {onnx_path}") + print(f"Meta: {meta_path}") + print("\n--- Paste into EA InpFeatMinStr / InpFeatMaxStr (comma-separated, %d floats each) ---" % NUM_FEATURES) + print(",".join(f"{x:.8g}" for x in scaler.data_min_)) + print(",".join(f"{x:.8g}" for x in scaler.data_max_)) + print(f"\nSet EA InpLookback = {lookback} (must match ONNX input dim).") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ai/xauusd_h1/models/XAUUSD_H1_action.onnx b/ai/xauusd_h1/models/XAUUSD_H1_action.onnx new file mode 100644 index 0000000..a18858f Binary files /dev/null and b/ai/xauusd_h1/models/XAUUSD_H1_action.onnx differ diff --git a/ai/xauusd_h1/models/XAUUSD_H1_action_meta.json b/ai/xauusd_h1/models/XAUUSD_H1_action_meta.json new file mode 100644 index 0000000..0246dbd --- /dev/null +++ b/ai/xauusd_h1/models/XAUUSD_H1_action_meta.json @@ -0,0 +1,121 @@ +{ + "symbol": "XAUUSD", + "timeframe": "H1", + "lookback": 48, + "num_features": 24, + "feature_columns": [ + "open", + "high", + "low", + "close", + "tick_volume", + "rsi", + "ema20_n", + "ema50_n", + "atr_n", + "price_change", + "high_low_ratio", + "volume_ma", + "volume_ratio", + "rsi7_n", + "rsi21_n", + "rsi_fast_slow_spread", + "rsi_velocity", + "rsi_accel", + "rsi_dist_mid_50", + "rsi_cross_overbought", + "rsi_cross_oversold", + "rsi_cross_50_up", + "rsi_cross_50_down", + "session_asian_utc" + ], + "num_classes": 5, + "class_names": [ + "HOLD", + "BUY", + "SELL_SHORT", + "CLOSE_LONG", + "CLOSE_SHORT" + ], + "label_horizon_bars": 8, + "label_note": "H1 labeling defaults: horizon=8, local=6, pullback=5 (~M15 wall-clock parity)", + "scaler_feature_min": [ + 679.5499877929688, + 735.0499877929688, + 679.5499877929688, + 711.2999877929688, + 0.0, + 0.0778568685054779, + -0.08346110582351685, + -0.13061486184597015, + 0.0006287021678872406, + -0.09134025126695633, + 1.0, + 0.0018113000551238656, + 0.0, + 0.020070146769285202, + 0.1127406507730484, + -0.45013511180877686, + -1.6049232482910156, + -2.0694637298583984, + 1.0986201232299209e-05, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "scaler_feature_max": [ + 5562.419921875, + 5598.06005859375, + 5554.68994140625, + 5562.43994140625, + 0.15629400312900543, + 0.9388294816017151, + 0.1516682505607605, + 0.17963257431983948, + 0.07575831562280655, + 0.10734681040048599, + 1.138908863067627, + 0.11270634829998016, + 11.032988548278809, + 0.9843139052391052, + 0.8853746056556702, + 0.44974473118782043, + 1.5471272468566895, + 2.1628992557525635, + 0.8776589632034302, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "scaler_scale": [ + 0.00020479758677538484, + 0.00020563394355122, + 0.00020512231276370585, + 0.00020613710512407124, + 6.398198127746582, + 1.1614770889282227, + 4.2529778480529785, + 3.223233938217163, + 13.310330390930176, + 5.033040523529053, + 7.198964595794678, + 9.017535209655762, + 0.09063727408647537, + 1.0370821952819824, + 1.2942739725112915, + 1.1112594604492188, + 0.31725379824638367, + 0.23627464473247528, + 1.1394089460372925, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "notes": "MinMax in EA; row0=newest. Match EA InpLookback to lookback here." +} \ No newline at end of file diff --git a/ai/xauusd_h1/models/XAUUSD_H1_action_scaler.pkl b/ai/xauusd_h1/models/XAUUSD_H1_action_scaler.pkl new file mode 100644 index 0000000..f93fcd1 Binary files /dev/null and b/ai/xauusd_h1/models/XAUUSD_H1_action_scaler.pkl differ diff --git a/ai/xauusd_h1/requirements.txt b/ai/xauusd_h1/requirements.txt new file mode 100644 index 0000000..5f73675 --- /dev/null +++ b/ai/xauusd_h1/requirements.txt @@ -0,0 +1,8 @@ +numpy>=1.23 +pandas>=2.0 +MetaTrader5>=5.0.45 +tensorflow>=2.14 +tf2onnx>=1.16 +onnx>=1.15 +scikit-learn>=1.3 +tqdm>=4.66 diff --git a/ai/xauusd_h1/risk_controls.py b/ai/xauusd_h1/risk_controls.py new file mode 100644 index 0000000..1a29d77 --- /dev/null +++ b/ai/xauusd_h1/risk_controls.py @@ -0,0 +1,21 @@ +""" +Dynamic adverse risk (conceptual mirror of EA InpMaxAdverseATR). +""" + +from __future__ import annotations + +import numpy as np + + +def adverse_hit_long( + entry: float, + low_path: np.ndarray, + atr_path: np.ndarray, + max_adverse_atr: float, +) -> int | None: + for i in range(len(low_path)): + atr = max(atr_path[i], entry * 1e-6) + adv = (entry - low_path[i]) / atr + if adv >= max_adverse_atr: + return i + return None diff --git a/ai/xauusd_m15/FRONTLINE_RSI_INTEGRATION.md b/ai/xauusd_m15/FRONTLINE_RSI_INTEGRATION.md new file mode 100644 index 0000000..c0ade91 --- /dev/null +++ b/ai/xauusd_m15/FRONTLINE_RSI_INTEGRATION.md @@ -0,0 +1,43 @@ +# Frontline RSI 经验 → `ai/xauusd_m15` 特征映射 + +本文把 `frontline/MQL5/_united/Strategies` 里与 RSI 相关的**可量化**逻辑,映射到训练用的 **24 维特征**(前 13 维与原版 EA 一致,后 11 维为 RSI/时段扩展)。 + +## 策略来源与特征对应 + +| Frontline 模块 | 经验要点 | 模型中的体现 | +|----------------|----------|----------------| +| **RSIReversalAsianStrategy** | 上穿超买 / 下穿超卖的**交叉**;亚洲时段(UTC 0–8)语境 | `rsi_cross_overbought` / `rsi_cross_oversold`(默认 70/30);`session_asian_utc` | +| **RSICrossOverReversalStrategy** | 超买/超卖区附近的**反转入场**、RSI 退出位 | 交叉特征 + `rsi_velocity` / `rsi_accel` 描述短期摆动 | +| **RSIScalpingStrategy** | 极值区外的**回升/回落**(多根 RSI 结构) | `rsi_velocity`、`rsi_accel`(3 根 RSI14 近似) | +| **RSIMidPointHijackStrategy** | 相对 **50** 中轴、快慢 RSI 状态 | `rsi_dist_mid_50`;`rsi_fast_slow_spread`(RSI14 vs RSI7) | +| **多品种 RSI Scalping** | 更短周期敏感 | `rsi7_n`(快周期)、`rsi21_n`(慢周期) | + +## 特征索引(与 Python / EA 顺序一致) + +| 索引 | 名称 | 说明 | +|------|------|------| +| 0–4 | OHLC + tick_volume | 与原版一致 | +| 5 | rsi | Wilder RSI(14)/100 | +| 6–12 | EMA/ATR/价量 | 与原版一致 | +| 13 | rsi7_n | RSI(7)/100 | +| 14 | rsi21_n | RSI(21)/100 | +| 15 | rsi_fast_slow_spread | clip((RSI14−RSI7)/50, −1, 1) | +| 16 | rsi_velocity | (RSI14₀−RSI14₁)/25 | +| 17 | rsi_accel | ((RSI14₀−RSI14₁)−(RSI14₁−RSI14₂))/25 | +| 18 | rsi_dist_mid_50 | \|RSI14−50\|/50 | +| 19–22 | cross_* | 0/1,与 frontline 交叉定义一致(上一根→当前根) | +| 23 | session_asian_utc | 小时经偏移后 ∈ [0,8) 则为 1 | + +## 时段偏移 + +MT5 K 线时间多为**服务器时区**。若要与 UTC 亚洲窗对齐,训练时设环境变量 `SESSION_HOUR_OFFSET`,EA 使用 `InpSessionHourOffset`,使 `(hour + offset) % 24` 与你在回测里认定的 UTC 一致。 + +## 未直接编码的规则(可后续扩展) + +- **点差、最大持仓时长、Magic 分策略**:可作为额外标量特征或单独过滤层。 +- **RSIMidPoint 的「先标记超买再下穿退出线」**:可用连续两 bar 的 cross 组合特征或 LSTM 隐式学习;当前用 cross + dist_mid 近似。 +- **Darvas / EMA 等非 RSI 策略**:未并入本 ONNX 特征;可在 `features.py` 中追加列并同步改 `NUM_FEATURES` 与 EA。 + +## 再训练提醒 + +修改 `NUM_FEATURES` 后必须:**重新导出 ONNX**、更新 EA 中 `#resource` 模型、`OnnxSetInputShape` 第三维、**24 个 scaler min/max**。 diff --git a/ai/xauusd_m15/README.md b/ai/xauusd_m15/README.md new file mode 100644 index 0000000..ff8fba9 --- /dev/null +++ b/ai/xauusd_m15/README.md @@ -0,0 +1,49 @@ +# XAUUSD M15 — ONNX action model (buy / sell / close) + +## What it does + +- Pulls **XAUUSD** (**M15**) from **MetaTrader 5** (2008–2026 requested; actual range depends on History Center). +- **24 features**: 13 legacy OHLC/EMA/ATR/volume + **11 RSI / session** features aligned with **frontline** strategies (crosses, velocity, RSI7/21, Asian window). See **`FRONTLINE_RSI_INTEGRATION.md`**. +- Labels: **buy-low / sell-high** (forward window) + **close-long / close-short** (past-only). RSI enters as **inputs**, not as hard-coded label rules. +- Trains **LSTM → softmax(5)**: `HOLD`, `BUY`, `SELL_SHORT`, `CLOSE_LONG`, `CLOSE_SHORT`. +- Exports **`models/XAUUSD_M15_action.onnx`** + scaler + **`XAUUSD_M15_action_meta.json`** (includes `feature_columns`). +- **EA**: **SL=0, TP=0**; **InpMaxAdverseATR**; **InpSessionHourOffset** should match training `SESSION_HOUR_OFFSET` for Asian flag. + +This is research tooling — not investment advice. Past labels do not guarantee live performance. + +## Setup + +1. MT5 installed, logged in, **XAUUSD** visible; download **M15** history (Tools → History Center or chart scroll). +2. Python 3.10+: + +```bash +cd ai/xauusd_m15 +pip install -r requirements.txt +python main.py +``` + +Optional env: `XAU_SYMBOL`, `XAU_LOOKBACK` (default 64), `XAU_EPOCHS`, `XAU_BATCH`, `SESSION_HOUR_OFFSET` (Asian session hour alignment vs server time). + +3. Copy `models/XAUUSD_M15_action.onnx` to **`MQL5/Files/`** (same path as `#resource` in the EA). +4. Open `XAUUSD_M15_ActionEA.mq5` in MetaEditor; compile. +5. Paste two lines from training stdout into **InpFeatMinStr** and **InpFeatMaxStr** (comma-separated **24** floats each). + +## ONNX I/O + +- Input: `[1, lookback, 24]` float32, **row 0 = newest bar**. +- Output: `[1, 5]` softmax probabilities. + +## Files + +| File | Role | +|------|------| +| `main.py` | Fetch, features, labels, train, ONNX + meta | +| `features.py` | 24-dim pipeline + Wilder RSI | +| `FRONTLINE_RSI_INTEGRATION.md` | frontline 策略 → 特征对照 | +| `labeling.py` | `compute_action_labels` | +| `risk_controls.py` | Adverse ATR helper for Python backtests | +| `XAUUSD_M15_ActionEA.mq5` | Live inference + trading skeleton | + +## Tuning labels + +Edit parameters in `labeling.compute_action_labels()` (`horizon`, `k_forward_atr`, `pullback_mult`, etc.) and retrain. diff --git a/ai/xauusd_m15/XAUUSD_M15_ActionEA.mq5 b/ai/xauusd_m15/XAUUSD_M15_ActionEA.mq5 new file mode 100644 index 0000000..e752c69 --- /dev/null +++ b/ai/xauusd_m15/XAUUSD_M15_ActionEA.mq5 @@ -0,0 +1,375 @@ +//+------------------------------------------------------------------+ +//| XAUUSD_M15_ActionEA.mq5 | +//| ONNX softmax [5]: HOLD, BUY, SELL_SHORT, CLOSE_LONG, CLOSE_SHORT | +//| 24 features: base 13 + RSI/frontline (see FRONTLINE_RSI_*.md) | +//| Exits: model CLOSE_* + optional InpTakeProfitATR; adverse ATR | +//+------------------------------------------------------------------+ +#property copyright "Profitable EA Project" +#property version "1.03" + +#include + +#resource "XAUUSD_M15_action.onnx" as uchar ExtModel[] + +#define FEAT_COUNT 24 + +input group "Model" +input int InpLookback = 64; +// 0 = legacy: require p(BUY)>=InpProbBuy and p(SELL)>=InpProbSell (use ~0.18 for 5-class softmax) +// 1 = default: open only when directional prob beats HOLD (typical 5-way outputs ~0.15–0.25 each) +input int InpEntryMode = 1; +input double InpProbBuy = 0.18; +input double InpProbSell = 0.18; +input double InpMinBeatHold = 0.0; // mode 1: require max(p1,p2)-p0 >= this (e.g. 0.02) +// Exit: 0 = p3/p4 >= thresholds (use ~0.18 for 5-class); 1 = CLOSE beats HOLD and beats add (p3>p1 / p4>p2) +// 2 = default: CLOSE beats HOLD only (lets winners exit when pullback signal > hold; still weak in trends) +input int InpExitMode = 2; +input double InpProbCloseL = 0.18; +input double InpProbCloseS = 0.18; +input double InpMinCloseBeatHold = 0.0; // exit modes 1–2: require p3/p4 > p0 + this + +input group "Session (match Python SESSION_HOUR_OFFSET)" +input int InpSessionHourOffset = 0; // add to bar hour so Asian 0–8 matches training + +input group "Scaler: paste 24 floats each from python main.py" +input string InpFeatMinStr = ""; +input string InpFeatMaxStr = ""; + +input group "Risk" +input double InpLotSize = 0.01; +input int InpMagic = 902015; +input int InpSlippage = 30; +input double InpMaxAdverseATR = 2.0; +input double InpTakeProfitATR = 0.0; // >0: close in profit when price move >= this * ATR(14) (banks winners) + +double g_feat_min[FEAT_COUNT]; +double g_feat_max[FEAT_COUNT]; + +CTrade trade; +long g_onnx = INVALID_HANDLE; +datetime g_last_bar = 0; + +void InitDefaultScalerBounds() +{ + double def_min[FEAT_COUNT] = { + 0,0,0,0,0,0,-0.05,-0.05,0,-0.02,1.0,0,0.1, + 0,0,-1,-0.2,-0.2,0,0,0,0,0,0 + }; + double def_max[FEAT_COUNT] = { + 5000,5000,5000,5000,1,1,0.05,0.05,0.05,0.02,1.02,1,5.0, + 1,1,1,0.2,0.2,1,1,1,1,1,1 + }; + for(int i = 0; i < FEAT_COUNT; i++) + { + g_feat_min[i] = def_min[i]; + g_feat_max[i] = def_max[i]; + } +} + +bool ParseFeatCsv(const string s, double &arr[]) +{ + if(StringLen(s) < 3) return false; + string parts[]; + int n = StringSplit(s, ',', parts); + if(n != FEAT_COUNT) return false; + for(int i = 0; i < FEAT_COUNT; i++) + arr[i] = StringToDouble(parts[i]); + return true; +} + +int OnInit() +{ + InitDefaultScalerBounds(); + trade.SetExpertMagicNumber(InpMagic); + trade.SetDeviationInPoints(InpSlippage); + trade.SetTypeFilling(ORDER_FILLING_IOC); + + if(StringLen(InpFeatMinStr) > 0 && ParseFeatCsv(InpFeatMinStr, g_feat_min)) + Print("Loaded InpFeatMinStr (24)"); + if(StringLen(InpFeatMaxStr) > 0 && ParseFeatCsv(InpFeatMaxStr, g_feat_max)) + Print("Loaded InpFeatMaxStr (24)"); + + g_onnx = OnnxCreateFromBuffer(ExtModel, ONNX_DEBUG_LOGS); + if(g_onnx == INVALID_HANDLE) + { + Print("OnnxCreateFromBuffer failed ", GetLastError()); + return INIT_FAILED; + } + + const long inShape[] = {1, InpLookback, FEAT_COUNT}; + if(!OnnxSetInputShape(g_onnx, 0, inShape)) + { + Print("OnnxSetInputShape failed ", GetLastError()); + OnnxRelease(g_onnx); + return INIT_FAILED; + } + const long outShape[] = {1, 5}; + if(!OnnxSetOutputShape(g_onnx, 0, outShape)) + { + Print("OnnxSetOutputShape failed ", GetLastError()); + OnnxRelease(g_onnx); + return INIT_FAILED; + } + return INIT_SUCCEEDED; +} + +void OnDeinit(const int r) +{ + if(g_onnx != INVALID_HANDLE) OnnxRelease(g_onnx); +} + +double AtrNow() +{ + double b[]; + ArraySetAsSeries(b, true); + int h = iATR(_Symbol, PERIOD_CURRENT, 14); + if(h == INVALID_HANDLE) return 0; + if(CopyBuffer(h, 0, 0, 2, b) < 1) { IndicatorRelease(h); return 0; } + double v = b[0]; + IndicatorRelease(h); + return v; +} + +bool AdverseExit(const long type, const double open_price) +{ + double atr = AtrNow(); + if(atr <= 0) return false; + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(type == POSITION_TYPE_BUY) + { + double adv = (open_price - bid) / atr; + return adv >= InpMaxAdverseATR; + } + double adv = (ask - open_price) / atr; + return adv >= InpMaxAdverseATR; +} + +bool ProfitExit(const long type, const double open_price) +{ + if(InpTakeProfitATR <= 0.0) return false; + double atr = AtrNow(); + if(atr <= 0.0) return false; + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + if(type == POSITION_TYPE_BUY) + return (bid - open_price) >= InpTakeProfitATR * atr; + return (open_price - ask) >= InpTakeProfitATR * atr; +} + +bool ModelCloseLong(const double p0, const double p1, const double p3) +{ + if(InpExitMode == 0) + return (p3 >= InpProbCloseL); + if(InpExitMode == 1) + return (p3 > p0 + InpMinCloseBeatHold && p3 > p1); + // mode 2: close-long probability beats hold (trends can still keep p1 high; use InpTakeProfitATR then) + return (p3 > p0 + InpMinCloseBeatHold); +} + +bool ModelCloseShort(const double p0, const double p2, const double p4) +{ + if(InpExitMode == 0) + return (p4 >= InpProbCloseS); + if(InpExitMode == 1) + return (p4 > p0 + InpMinCloseBeatHold && p4 > p2); + return (p4 > p0 + InpMinCloseBeatHold); +} + +void ScaleFeatures(const float &raw[], float &out[]) +{ + for(int f = 0; f < FEAT_COUNT; f++) + { + double den = g_feat_max[f] - g_feat_min[f]; + if(den < 1e-12) den = 1e-12; + double x = (double)raw[f] - g_feat_min[f]; + out[f] = (float)MathMax(0.0, MathMin(1.0, x / den)); + } +} + +bool PrepareMatrix(matrixf &M) +{ + int L = InpLookback; + double open[], high[], low[], close[]; + long vol[]; + datetime bt[]; + ArraySetAsSeries(open, true); + ArraySetAsSeries(high, true); + ArraySetAsSeries(low, true); + ArraySetAsSeries(close, true); + ArraySetAsSeries(vol, true); + ArraySetAsSeries(bt, true); + + int need = L + 55; + if(CopyOpen(_Symbol, PERIOD_CURRENT, 0, need, open) < L) return false; + if(CopyHigh(_Symbol, PERIOD_CURRENT, 0, need, high) < L) return false; + if(CopyLow(_Symbol, PERIOD_CURRENT, 0, need, low) < L) return false; + if(CopyClose(_Symbol, PERIOD_CURRENT, 0, need, close) < L) return false; + if(CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, need, vol) < L) return false; + if(CopyTime(_Symbol, PERIOD_CURRENT, 0, need, bt) < L) return false; + + double rsi7[], rsi14[], rsi21[], ema20[], ema50[], atr[]; + ArraySetAsSeries(rsi7, true); + ArraySetAsSeries(rsi14, true); + ArraySetAsSeries(rsi21, true); + ArraySetAsSeries(ema20, true); + ArraySetAsSeries(ema50, true); + ArraySetAsSeries(atr, true); + + int h7 = iRSI(_Symbol, PERIOD_CURRENT, 7, PRICE_CLOSE); + int h14 = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); + int h21 = iRSI(_Symbol, PERIOD_CURRENT, 21, PRICE_CLOSE); + int hE20 = iMA(_Symbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE); + int hE50 = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE); + int hA = iATR(_Symbol, PERIOD_CURRENT, 14); + if(h7 == INVALID_HANDLE || h14 == INVALID_HANDLE || h21 == INVALID_HANDLE || + hE20 == INVALID_HANDLE || hE50 == INVALID_HANDLE || hA == INVALID_HANDLE) + return false; + + if(CopyBuffer(h7, 0, 0, need, rsi7) < L || + CopyBuffer(h14, 0, 0, need, rsi14) < L || + CopyBuffer(h21, 0, 0, need, rsi21) < L || + CopyBuffer(hE20, 0, 0, need, ema20) < L || + CopyBuffer(hE50, 0, 0, need, ema50) < L || + CopyBuffer(hA, 0, 0, need, atr) < L) + { + IndicatorRelease(h7); IndicatorRelease(h14); IndicatorRelease(h21); + IndicatorRelease(hE20); IndicatorRelease(hE50); IndicatorRelease(hA); + return false; + } + IndicatorRelease(h7); IndicatorRelease(h14); IndicatorRelease(h21); + IndicatorRelease(hE20); IndicatorRelease(hE50); IndicatorRelease(hA); + + M.Resize(L, FEAT_COUNT); + const double RSI_OB = 70.0; + const double RSI_OS = 30.0; + + for(int i = 0; i < L; i++) + { + double vma = 0; + int cnt = 0; + for(int k = i; k < i + 20 && k < ArraySize(vol); k++) { vma += (double)vol[k]; cnt++; } + if(cnt < 1) cnt = 1; + vma /= cnt; + + double r0 = rsi14[i]; + double r1 = (i + 1 < ArraySize(rsi14)) ? rsi14[i + 1] : r0; + double r2 = (i + 2 < ArraySize(rsi14)) ? rsi14[i + 2] : r1; + double rv7 = rsi7[i]; + double rv21 = rsi21[i]; + + double spread = (r0 - rv7) / 50.0; + if(spread > 1.0) spread = 1.0; + if(spread < -1.0) spread = -1.0; + double vel = (r0 - r1) / 25.0; + double acc = ((r0 - r1) - (r1 - r2)) / 25.0; + double dist_mid = MathAbs(r0 - 50.0) / 50.0; + double c_ob = (r1 < RSI_OB && r0 >= RSI_OB) ? 1.0 : 0.0; + double c_os = (r1 > RSI_OS && r0 <= RSI_OS) ? 1.0 : 0.0; + double c50u = (r1 < 50.0 && r0 >= 50.0) ? 1.0 : 0.0; + double c50d = (r1 > 50.0 && r0 <= 50.0) ? 1.0 : 0.0; + + MqlDateTime st; + TimeToStruct(bt[i], st); + int hr = (st.hour + InpSessionHourOffset) % 24; + if(hr < 0) hr += 24; + double asian = (hr >= 0 && hr < 8) ? 1.0 : 0.0; + + float raw[FEAT_COUNT]; + raw[0] = (float)open[i]; + raw[1] = (float)high[i]; + raw[2] = (float)low[i]; + raw[3] = (float)close[i]; + raw[4] = (float)((double)vol[i] / 1000000.0); + raw[5] = (float)(r0 / 100.0); + raw[6] = (float)((ema20[i] - close[i]) / close[i]); + raw[7] = (float)((ema50[i] - close[i]) / close[i]); + raw[8] = (float)(atr[i] / close[i]); + double pc = (i < L - 1) ? (close[i] - close[i + 1]) / close[i + 1] : 0.0; + raw[9] = (float)pc; + raw[10] = (float)(high[i] / low[i]); + raw[11] = (float)(vma / 1000000.0); + raw[12] = (float)(vma > 0 ? (double)vol[i] / vma : 1.0); + raw[13] = (float)(rv7 / 100.0); + raw[14] = (float)(rv21 / 100.0); + raw[15] = (float)spread; + raw[16] = (float)vel; + raw[17] = (float)acc; + raw[18] = (float)dist_mid; + raw[19] = (float)c_ob; + raw[20] = (float)c_os; + raw[21] = (float)c50u; + raw[22] = (float)c50d; + raw[23] = (float)asian; + + float sc[FEAT_COUNT]; + ScaleFeatures(raw, sc); + for(int j = 0; j < FEAT_COUNT; j++) + M[i][j] = sc[j]; + } + return true; +} + +void OnTick() +{ + datetime t = iTime(_Symbol, PERIOD_CURRENT, 0); + if(t == g_last_bar) return; + g_last_bar = t; + + matrixf Min; + if(!PrepareMatrix(Min)) + { + Print("PrepareMatrix failed"); + return; + } + + vectorf out; + out.Resize(5); + if(!OnnxRun(g_onnx, ONNX_NO_CONVERSION, Min, out)) + { + Print("OnnxRun failed ", GetLastError()); + return; + } + + double p0 = out[0], p1 = out[1], p2 = out[2], p3 = out[3], p4 = out[4]; + Print("ONNX HOLD=", p0, " BUY=", p1, " SELL=", p2, " CL=", p3, " CS=", p4); + + if(!PositionSelect(_Symbol)) + { + if(InpEntryMode == 1) + { + double dir = MathMax(p1, p2); + if(dir <= p0 + InpMinBeatHold) + return; + if(p1 >= p2 && p1 > p0 + InpMinBeatHold) + trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "AI BUY"); + else if(p2 > p1 && p2 > p0 + InpMinBeatHold) + trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "AI SELL"); + } + else + { + if(p1 >= InpProbBuy && p1 >= p2) + trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "AI BUY"); + else if(p2 >= InpProbSell && p2 > p1) + trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "AI SELL"); + } + return; + } + + long typ = (long)PositionGetInteger(POSITION_TYPE); + double opn = PositionGetDouble(POSITION_PRICE_OPEN); + if(AdverseExit(typ, opn)) + { + trade.PositionClose(_Symbol); + return; + } + if(ProfitExit(typ, opn)) + { + trade.PositionClose(_Symbol); + return; + } + if(typ == POSITION_TYPE_BUY && ModelCloseLong(p0, p1, p3)) + trade.PositionClose(_Symbol); + else if(typ == POSITION_TYPE_SELL && ModelCloseShort(p0, p2, p4)) + trade.PositionClose(_Symbol); +} diff --git a/ai/xauusd_m15/XAUUSD_M15_ActionEA_optimize.set b/ai/xauusd_m15/XAUUSD_M15_ActionEA_optimize.set new file mode 100644 index 0000000..fc2dbbd --- /dev/null +++ b/ai/xauusd_m15/XAUUSD_M15_ActionEA_optimize.set @@ -0,0 +1,27 @@ +; XAUUSD_M15_ActionEA v1.03 — optimization preset +; Copy to: MetaQuotes\Terminal\\MQL5\Profiles\Tester\ +; Strategy Tester → Inputs → context menu → Load +; +; Format: Name=value||optimize_start||step||stop||Y|N (Y = optimize this parameter) +; +; Model +InpLookback=64||48||8||96||Y +InpEntryMode=1||0||1||1||Y +InpProbBuy=0.18||0.14||0.02||0.26||Y +InpProbSell=0.18||0.14||0.02||0.26||Y +InpMinBeatHold=0.0||0.0||0.01||0.05||Y +InpExitMode=2||0||1||2||Y +InpProbCloseL=0.18||0.14||0.02||0.26||Y +InpProbCloseS=0.18||0.14||0.02||0.26||Y +InpMinCloseBeatHold=0.0||0.0||0.01||0.04||Y +; Session (match Python SESSION_HOUR_OFFSET) +InpSessionHourOffset=0||-3||1||3||N +; Scaler: paste 24 floats from python main.py (not optimizable) +InpFeatMinStr= +InpFeatMaxStr= +; Risk +InpLotSize=0.01||0.01||0.001000||0.100000||N +InpMagic=902015||902015||1||9020150||N +InpSlippage=30||30||1||300||N +InpMaxAdverseATR=2.0||1.0||0.25||3.5||Y +InpTakeProfitATR=0.0||0.0||0.25||3.0||Y diff --git a/ai/xauusd_m15/__pycache__/features.cpython-312.pyc b/ai/xauusd_m15/__pycache__/features.cpython-312.pyc new file mode 100644 index 0000000..4744cd3 Binary files /dev/null and b/ai/xauusd_m15/__pycache__/features.cpython-312.pyc differ diff --git a/ai/xauusd_m15/__pycache__/labeling.cpython-312.pyc b/ai/xauusd_m15/__pycache__/labeling.cpython-312.pyc new file mode 100644 index 0000000..5100d93 Binary files /dev/null and b/ai/xauusd_m15/__pycache__/labeling.cpython-312.pyc differ diff --git a/ai/xauusd_m15/features.py b/ai/xauusd_m15/features.py new file mode 100644 index 0000000..16bbc24 --- /dev/null +++ b/ai/xauusd_m15/features.py @@ -0,0 +1,173 @@ +""" +Feature pipeline: base 13 (EA-compatible) + 11 RSI / session features from frontline experience. + +Frontline mapping (see FRONTLINE_RSI_INTEGRATION.md): + - RSIReversalAsianStrategy / RSICrossOverReversal: cross OB/OS, cross 50 + - RSIScalpingStrategy: RSI velocity (bounce from extreme uses 3-bar structure → vel/acc) + - RSIMidPointHijack: distance from 50, RSI(7) vs RSI(14) spread + - Asian session gate → binary feature (hour window; offset for server vs UTC) + +RSI uses Wilder smoothing (ewm alpha=1/period) to align with MT5 iRSI. +""" + +from __future__ import annotations + +import os + +import numpy as np +import pandas as pd + +NUM_BASE_FEATURES = 13 +NUM_RSI_EXTRA = 11 +NUM_FEATURES = NUM_BASE_FEATURES + NUM_RSI_EXTRA # 24 + +# Default thresholds aligned with common frontline inputs (Asian / scalping) +RSI_OVERBOUGHT = 70.0 +RSI_OVERSOLD = 30.0 + + +def wilder_rsi(close: pd.Series, period: int) -> np.ndarray: + """Wilder RSI (matches MetaTrader iRSI closely).""" + delta = close.diff() + gain = delta.clip(lower=0.0) + loss = (-delta).clip(lower=0.0) + avg_g = gain.ewm(alpha=1.0 / period, min_periods=period, adjust=False).mean() + avg_l = loss.ewm(alpha=1.0 / period, min_periods=period, adjust=False).mean() + rs = avg_g / avg_l.replace(0, np.nan) + rsi = 100.0 - (100.0 / (1.0 + rs)) + return rsi.fillna(50.0).to_numpy(dtype=np.float64) + + +def prepare_features_full( + df: pd.DataFrame, + *, + session_hour_offset: int | None = None, +) -> pd.DataFrame: + """ + Build (N, 24) feature table, chronological index matching df. + Drops first ~50 rows (warmup) like the original pipeline. + """ + if session_hour_offset is None: + session_hour_offset = int(os.environ.get("SESSION_HOUR_OFFSET", "0")) + + o = df["open"].to_numpy(dtype=np.float64) + h = df["high"].to_numpy(dtype=np.float64) + l = df["low"].to_numpy(dtype=np.float64) + c = df["close"].astype(float) + vol = df["tick_volume"].to_numpy(dtype=np.float64) + n = len(df) + idx = df.index + + rsi7 = wilder_rsi(c, 7) + rsi14 = wilder_rsi(c, 14) + rsi21 = wilder_rsi(c, 21) + + ema20 = c.ewm(span=20, adjust=False).mean().to_numpy() + ema50 = c.ewm(span=50, adjust=False).mean().to_numpy() + + tr = np.maximum( + h - l, + np.maximum(np.abs(h - np.roll(c.to_numpy(), 1)), np.abs(l - np.roll(c.to_numpy(), 1))), + ) + tr[0] = h[0] - l[0] + atr = pd.Series(tr).rolling(14).mean().to_numpy() + + vol_ma = np.zeros(n) + for j in range(n): + s = 0.0 + cnt = 0 + for k in range(j, min(j + 20, n)): + s += vol[k] + cnt += 1 + vol_ma[j] = s / cnt if cnt else vol[j] + + pc_ea = np.zeros(n) + cvals = c.to_numpy() + for j in range(1, n): + den = cvals[j - 1] + pc_ea[j] = (cvals[j] - den) / den if den else 0.0 + + hours = np.zeros(n, dtype=np.int32) + for j in range(n): + ts = idx[j] + try: + hts = int(ts.hour) + except Exception: + hts = 0 + hours[j] = (hts + session_hour_offset) % 24 + + rows = [] + for j in range(n): + r0 = rsi14[j] + r1 = rsi14[j - 1] if j > 0 else r0 + r2 = rsi14[j - 2] if j > 1 else r1 + + spread = np.clip((r0 - rsi7[j]) / 50.0, -1.0, 1.0) + vel = (r0 - r1) / 25.0 + acc = ((r0 - r1) - (r1 - r2)) / 25.0 + dist_mid = abs(r0 - 50.0) / 50.0 + + cross_ob = 1.0 if (r1 < RSI_OVERBOUGHT and r0 >= RSI_OVERBOUGHT) else 0.0 + cross_os = 1.0 if (r1 > RSI_OVERSOLD and r0 <= RSI_OVERSOLD) else 0.0 + cross_50_up = 1.0 if (r1 < 50.0 and r0 >= 50.0) else 0.0 + cross_50_dn = 1.0 if (r1 > 50.0 and r0 <= 50.0) else 0.0 + asian = 1.0 if (0 <= hours[j] < 8) else 0.0 + + rows.append( + [ + float(o[j]), + float(h[j]), + float(l[j]), + float(cvals[j]), + float(vol[j] / 1_000_000.0), + float(rsi14[j] / 100.0), + float((ema20[j] - cvals[j]) / cvals[j]) if cvals[j] else 0.0, + float((ema50[j] - cvals[j]) / cvals[j]) if cvals[j] else 0.0, + float(atr[j] / cvals[j]) if cvals[j] else 0.0, + float(pc_ea[j]), + float(h[j] / l[j]) if l[j] else 1.0, + float(vol_ma[j] / 1_000_000.0), + float(vol[j] / vol_ma[j]) if vol_ma[j] > 0 else 1.0, + float(rsi7[j] / 100.0), + float(rsi21[j] / 100.0), + float(spread), + float(vel), + float(acc), + float(dist_mid), + float(cross_ob), + float(cross_os), + float(cross_50_up), + float(cross_50_dn), + float(asian), + ] + ) + + cols = [ + "open", + "high", + "low", + "close", + "tick_volume", + "rsi", + "ema20_n", + "ema50_n", + "atr_n", + "price_change", + "high_low_ratio", + "volume_ma", + "volume_ratio", + "rsi7_n", + "rsi21_n", + "rsi_fast_slow_spread", + "rsi_velocity", + "rsi_accel", + "rsi_dist_mid_50", + "rsi_cross_overbought", + "rsi_cross_oversold", + "rsi_cross_50_up", + "rsi_cross_50_down", + "session_asian_utc", + ] + + out = pd.DataFrame(rows, index=idx, columns=cols) + return out.iloc[50:].copy() diff --git a/ai/xauusd_m15/labeling.py b/ai/xauusd_m15/labeling.py new file mode 100644 index 0000000..c9a311e --- /dev/null +++ b/ai/xauusd_m15/labeling.py @@ -0,0 +1,130 @@ +""" +Buy-low / sell-high style labels for OHLCV bars (no fixed SL/TP in labels). + +Optional context: frontline RSI strategies (Asian reversal, scalping, mid-50) +are encoded as *features* in features.py (crosses, velocity, session), not as +hard rules here — the network learns joint patterns with price/volume. + +Classes (integer, matches EA): + 0 HOLD + 1 BUY — forward upside vs ATR + local swing low + 2 SELL_SHORT — forward downside vs ATR + local swing high + 3 CLOSE_LONG — past-only: pullback from recent range high + 4 CLOSE_SHORT — past-only: bounce from recent range low + +CLOSE_* use only bars <= t (no future leak). +BUY/SELL use forward window [t+1, t+horizon] (supervised targets). +""" +from __future__ import annotations + +import numpy as np +import pandas as pd + + +def atr_series(df: pd.DataFrame, period: int = 14) -> pd.Series: + high, low, close = df["high"], df["low"], df["close"] + tr = pd.concat( + [ + high - low, + (high - close.shift()).abs(), + (low - close.shift()).abs(), + ], + axis=1, + ).max(axis=1) + return tr.rolling(period).mean() + + +def compute_action_labels( + df: pd.DataFrame, + *, + horizon: int = 32, + local_window: int = 24, + pullback_window: int = 20, + k_forward_atr: float = 0.75, + local_pct: float = 0.28, + pullback_mult: float = 0.55, + trend_mult: float = 1.05, +) -> pd.Series: + """ + Return a Series of int labels 0..4 aligned to df index. + Last `horizon` rows → HOLD (no forward path for buy/sell scoring). + """ + close = df["close"].values + high = df["high"].values + low = df["low"].values + n = len(df) + atr = atr_series(df, 14).values + labels = np.zeros(n, dtype=np.int64) + + lw = local_window + pw = pullback_window + need = max(lw, pw) + 2 + + for t in range(n): + if t < need or t >= n - horizon: + labels[t] = 0 + continue + + a = atr[t] + if not np.isfinite(a) or a <= 0: + a = close[t] * 1e-4 + + sl = low[t + 1 : t + horizon + 1] + sh = high[t + 1 : t + horizon + 1] + fwd_max = float(np.max(sh)) + fwd_min = float(np.min(sl)) + up_move = (fwd_max - close[t]) / a + down_move = (close[t] - fwd_min) / a + + loc_low = float(np.min(low[t - lw : t + 1])) + loc_high = float(np.max(high[t - lw : t + 1])) + rng = max(loc_high - loc_low, a * 0.15) + near_low = (close[t] - loc_low) / rng <= local_pct + near_high = (loc_high - close[t]) / rng <= local_pct + + buy_sig = near_low and (up_move >= k_forward_atr) and (up_move >= down_move * 0.85) + sell_sig = near_high and (down_move >= k_forward_atr) and (down_move > up_move * 1.05) + + # Past window [t-pw, t] + seg_h = high[t - pw : t + 1] + seg_l = low[t - pw : t + 1] + rh = float(np.max(seg_h)) + rl = float(np.min(seg_l)) + range_atr = (rh - rl) / a + pull_from_high = (rh - close[t]) / a + bounce_from_low = (close[t] - rl) / a + + exit_long = ( + range_atr >= trend_mult + and pull_from_high >= pullback_mult + and close[t] < close[t - 1] + ) + exit_short = ( + range_atr >= trend_mult + and bounce_from_low >= pullback_mult + and close[t] > close[t - 1] + ) + + if exit_long and not buy_sig: + labels[t] = 3 + elif exit_short and not sell_sig: + labels[t] = 4 + elif buy_sig and not sell_sig: + labels[t] = 1 + elif sell_sig and not buy_sig: + labels[t] = 2 + elif buy_sig and sell_sig: + labels[t] = 1 if up_move >= down_move else 2 + else: + labels[t] = 0 + + return pd.Series(labels, index=df.index, name="action_label") + + +def class_weights(y: np.ndarray, n_classes: int = 5) -> dict[int, float]: + from sklearn.utils.class_weight import compute_class_weight + + y_int = y.astype(int) + classes = np.arange(n_classes) + cw = compute_class_weight("balanced", classes=classes, y=y_int) + return {i: float(cw[i]) for i in range(n_classes)} diff --git a/ai/xauusd_m15/main.py b/ai/xauusd_m15/main.py new file mode 100644 index 0000000..9e37504 --- /dev/null +++ b/ai/xauusd_m15/main.py @@ -0,0 +1,210 @@ +""" +XAUUSD M15 — ONNX action model (buy / sell short / close long / close short / hold). + +Features: 24 dims — base 13 + RSI/frontline stack (see features.py, FRONTLINE_RSI_INTEGRATION.md). +Row order matches XAUUSD_M15_ActionEA.mq5 (row 0 = newest bar). +Data: MT5, 2008–2026 (limited by downloaded history). +""" + +from __future__ import annotations + +import json +import os +import pickle +import sys +from datetime import datetime, timedelta + +import MetaTrader5 as mt5 +import numpy as np +import pandas as pd +import tensorflow as tf +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import MinMaxScaler +from tensorflow import keras +from tensorflow.keras import layers +from tqdm import tqdm +import tf2onnx +import onnx + +from labeling import class_weights, compute_action_labels +from features import NUM_FEATURES, prepare_features_full + +NUM_CLASSES = 5 +CLASS_NAMES = ["HOLD", "BUY", "SELL_SHORT", "CLOSE_LONG", "CLOSE_SHORT"] + + +def fetch_mt5_range( + symbol: str, + timeframe: int, + start_date: datetime, + end_date: datetime, +) -> pd.DataFrame: + if not mt5.initialize(): + raise RuntimeError(f"MT5 init failed: {mt5.last_error()}") + + info = mt5.symbol_info(symbol) + if info is None: + mt5.shutdown() + raise ValueError(f"Symbol {symbol} not found") + if not info.visible and not mt5.symbol_select(symbol, True): + mt5.shutdown() + raise ValueError(f"Cannot select {symbol}") + + all_rows: list[dict] = [] + chunk_days = 30 + cur = start_date + while cur < end_date: + chunk_end = min(cur + timedelta(days=chunk_days), end_date) + rates = mt5.copy_rates_range(symbol, timeframe, cur, chunk_end) + if rates is not None and len(rates) > 1: + for row in rates: + all_rows.append({n: row[n] for n in rates.dtype.names}) + cur = chunk_end + + if not all_rows: + mt5.shutdown() + raise ValueError("No rates returned — download XAUUSD M15 in MT5 History Center") + + df = pd.DataFrame(all_rows) + df["time"] = pd.to_datetime(df["time"], unit="s") + df = df.set_index("time").sort_index() + df = df[~df.index.duplicated(keep="first")] + return df + + +def create_sequences( + X: np.ndarray, y: np.ndarray, lookback: int +) -> tuple[np.ndarray, np.ndarray]: + """ + Window ends at bar i (chronological). Rows: newest-first inside each window + (matches MT5 series arrays in EA). + """ + xs, ys = [], [] + for i in tqdm(range(lookback - 1, len(X)), desc="sequences"): + window = X[i - lookback + 1 : i + 1].copy() + window = window[::-1] # newest bar first → same as EA matrix row 0 + xs.append(window) + ys.append(y[i]) + return np.asarray(xs, dtype=np.float32), np.asarray(ys, dtype=np.int64) + + +def build_model(lookback: int, n_feat: int) -> keras.Model: + inp = layers.Input(shape=(lookback, n_feat)) + x = layers.LSTM(96, return_sequences=True)(inp) + x = layers.Dropout(0.25)(x) + x = layers.LSTM(48)(x) + x = layers.Dropout(0.25)(x) + x = layers.Dense(32, activation="relu")(x) + out = layers.Dense(NUM_CLASSES, activation="softmax", name="action_probs")(x) + model = keras.Model(inp, out) + model.compile( + optimizer=keras.optimizers.Adam(1e-3), + loss="sparse_categorical_crossentropy", + metrics=["accuracy"], + ) + return model + + +def main() -> int: + symbol = os.environ.get("XAU_SYMBOL", "XAUUSD") + lookback = int(os.environ.get("XAU_LOOKBACK", "64")) + epochs = int(os.environ.get("XAU_EPOCHS", "40")) + batch_size = int(os.environ.get("XAU_BATCH", "64")) + + start_date = datetime(2008, 1, 1) + end_date = datetime(2026, 12, 31) + + out_dir = os.path.join(os.path.dirname(__file__), "models") + os.makedirs(out_dir, exist_ok=True) + onnx_path = os.path.join(out_dir, f"{symbol}_M15_action.onnx") + meta_path = os.path.join(out_dir, f"{symbol}_M15_action_meta.json") + + print("Fetching MT5 data …") + try: + raw = fetch_mt5_range(symbol, mt5.TIMEFRAME_M15, start_date, end_date) + finally: + mt5.shutdown() + print(f"Bars: {len(raw)} range: {raw.index[0]} → {raw.index[-1]}") + + feat = prepare_features_full(raw) + labels_full = compute_action_labels(raw) + labels = labels_full.loc[feat.index] + + y = labels.loc[feat.index].values.astype(np.int64) + X_raw = feat.values.astype(np.float32) + + valid = np.isfinite(X_raw).all(axis=1) & (y >= 0) & (y < NUM_CLASSES) + X_raw = X_raw[valid] + y = y[valid] + + print("Label counts:", {CLASS_NAMES[i]: int((y == i).sum()) for i in range(NUM_CLASSES)}) + + scaler = MinMaxScaler() + Xn = scaler.fit_transform(X_raw).astype(np.float32) + + X_seq, y_seq = create_sequences(Xn, y, lookback) + if len(X_seq) < 500: + print("ERROR: Too few sequences — need more M15 history in MT5.") + return 1 + + X_train, X_val, y_train, y_val = train_test_split( + X_seq, y_seq, test_size=0.15, shuffle=False + ) + + cw = class_weights(y_train, NUM_CLASSES) + sample_w = np.array([cw[int(c)] for c in y_train], dtype=np.float32) + + model = build_model(lookback, NUM_FEATURES) + model.summary() + + model.fit( + X_train, + y_train, + sample_weight=sample_w, + validation_data=(X_val, y_val), + epochs=epochs, + batch_size=batch_size, + verbose=1, + callbacks=[ + keras.callbacks.EarlyStopping( + monitor="val_loss", patience=8, restore_best_weights=True + ), + keras.callbacks.ReduceLROnPlateau( + monitor="val_loss", factor=0.5, patience=4, min_lr=1e-6 + ), + ], + ) + + spec = (tf.TensorSpec((None, lookback, NUM_FEATURES), tf.float32, name="input"),) + onnx_m, _ = tf2onnx.convert.from_keras(model, input_signature=spec, opset=13) + onnx.save_model(onnx_m, onnx_path) + + with open(onnx_path.replace(".onnx", "_scaler.pkl"), "wb") as f: + pickle.dump(scaler, f) + + meta = { + "symbol": symbol, + "timeframe": "M15", + "lookback": lookback, + "num_features": int(NUM_FEATURES), + "feature_columns": feat.columns.tolist(), + "num_classes": NUM_CLASSES, + "class_names": CLASS_NAMES, + "scaler_feature_min": scaler.data_min_.tolist(), + "scaler_feature_max": scaler.data_max_.tolist(), + "scaler_scale": scaler.scale_.tolist() if hasattr(scaler, "scale_") else None, + "notes": "MinMax in EA; row0=newest. See FRONTLINE_RSI_INTEGRATION.md.", + } + with open(meta_path, "w", encoding="utf-8") as f: + json.dump(meta, f, indent=2) + + print(f"Saved: {onnx_path}") + print(f"Meta: {meta_path}") + print("\n--- Paste into EA InpFeatMinStr / InpFeatMaxStr (comma-separated, %d floats each) ---" % NUM_FEATURES) + print(",".join(f"{x:.8g}" for x in scaler.data_min_)) + print(",".join(f"{x:.8g}" for x in scaler.data_max_)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/ai/xauusd_m15/models/XAUUSD_M15_action.onnx b/ai/xauusd_m15/models/XAUUSD_M15_action.onnx new file mode 100644 index 0000000..288c913 Binary files /dev/null and b/ai/xauusd_m15/models/XAUUSD_M15_action.onnx differ diff --git a/ai/xauusd_m15/models/XAUUSD_M15_action_meta.json b/ai/xauusd_m15/models/XAUUSD_M15_action_meta.json new file mode 100644 index 0000000..0e12b57 --- /dev/null +++ b/ai/xauusd_m15/models/XAUUSD_M15_action_meta.json @@ -0,0 +1,119 @@ +{ + "symbol": "XAUUSD", + "timeframe": "M15", + "lookback": 64, + "num_features": 24, + "feature_columns": [ + "open", + "high", + "low", + "close", + "tick_volume", + "rsi", + "ema20_n", + "ema50_n", + "atr_n", + "price_change", + "high_low_ratio", + "volume_ma", + "volume_ratio", + "rsi7_n", + "rsi21_n", + "rsi_fast_slow_spread", + "rsi_velocity", + "rsi_accel", + "rsi_dist_mid_50", + "rsi_cross_overbought", + "rsi_cross_oversold", + "rsi_cross_50_up", + "rsi_cross_50_down", + "session_asian_utc" + ], + "num_classes": 5, + "class_names": [ + "HOLD", + "BUY", + "SELL_SHORT", + "CLOSE_LONG", + "CLOSE_SHORT" + ], + "scaler_feature_min": [ + 1616.6700439453125, + 1618.8499755859375, + 1614.8199462890625, + 1616.6800537109375, + 0.0, + 0.08494461327791214, + -0.03888450935482979, + -0.0373079888522625, + 0.0001997762155951932, + -0.036351919174194336, + 1.0, + 0.00017494999337941408, + 0.0, + 0.02623281255364418, + 0.12661120295524597, + -0.5349156260490417, + -2.0385215282440186, + -2.397653818130493, + 2.298711478943005e-06, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "scaler_feature_max": [ + 5585.740234375, + 5598.06005859375, + 5577.580078125, + 5585.740234375, + 0.013647999614477158, + 0.9493793845176697, + 0.06255777180194855, + 0.06892234832048416, + 0.01657661236822605, + 0.04392698407173157, + 1.056401252746582, + 0.010342299938201904, + 5.7435832023620605, + 0.9802423715591431, + 0.9216755032539368, + 0.5254908204078674, + 1.544172763824463, + 2.009221076965332, + 0.8987588286399841, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "scaler_scale": [ + 0.00025194816407747567, + 0.0002513061626814306, + 0.00025234936038032174, + 0.00025194883346557617, + 73.27081298828125, + 1.156825304031372, + 9.85782241821289, + 9.413507461547852, + 61.06185531616211, + 12.456572532653809, + 17.7301025390625, + 98.35404205322266, + 0.17410734295845032, + 1.0482075214385986, + 1.2577598094940186, + 0.9430346488952637, + 0.2791195511817932, + 0.22691819071769714, + 1.112648367881775, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0 + ], + "notes": "MinMax in EA; row0=newest. See FRONTLINE_RSI_INTEGRATION.md." +} \ No newline at end of file diff --git a/ai/xauusd_m15/models/XAUUSD_M15_action_scaler.pkl b/ai/xauusd_m15/models/XAUUSD_M15_action_scaler.pkl new file mode 100644 index 0000000..42001e2 Binary files /dev/null and b/ai/xauusd_m15/models/XAUUSD_M15_action_scaler.pkl differ diff --git a/ai/xauusd_m15/requirements.txt b/ai/xauusd_m15/requirements.txt new file mode 100644 index 0000000..5f73675 --- /dev/null +++ b/ai/xauusd_m15/requirements.txt @@ -0,0 +1,8 @@ +numpy>=1.23 +pandas>=2.0 +MetaTrader5>=5.0.45 +tensorflow>=2.14 +tf2onnx>=1.16 +onnx>=1.15 +scikit-learn>=1.3 +tqdm>=4.66 diff --git a/ai/xauusd_m15/risk_controls.py b/ai/xauusd_m15/risk_controls.py new file mode 100644 index 0000000..c2041b7 --- /dev/null +++ b/ai/xauusd_m15/risk_controls.py @@ -0,0 +1,25 @@ +""" +Dynamic adverse risk (conceptual mirror of EA InpMaxAdverseATR). + +For backtests in Python: given entry price, ATR series, and bid/ask path, +exit when (entry - bid)/atr >= max_adv for long. +""" + +from __future__ import annotations + +import numpy as np + + +def adverse_hit_long( + entry: float, + low_path: np.ndarray, + atr_path: np.ndarray, + max_adverse_atr: float, +) -> int | None: + """Return first index where adverse >= threshold, else None.""" + for i in range(len(low_path)): + atr = max(atr_path[i], entry * 1e-6) + adv = (entry - low_path[i]) / atr + if adv >= max_adverse_atr: + return i + return None diff --git a/frontline/MQL5/RSIScalpingMSFT/main.mq5 b/back-pedal/archive/RSIScalpingMSFT/main.mq5 similarity index 100% rename from frontline/MQL5/RSIScalpingMSFT/main.mq5 rename to back-pedal/archive/RSIScalpingMSFT/main.mq5 diff --git a/frontline/MQL5/RSIScalpingMSFT/report.html b/back-pedal/archive/RSIScalpingMSFT/report.html similarity index 100% rename from frontline/MQL5/RSIScalpingMSFT/report.html rename to back-pedal/archive/RSIScalpingMSFT/report.html diff --git a/frontline/MQL5/RSIScalpingMSFT/report.png b/back-pedal/archive/RSIScalpingMSFT/report.png similarity index 100% rename from frontline/MQL5/RSIScalpingMSFT/report.png rename to back-pedal/archive/RSIScalpingMSFT/report.png diff --git a/frontline/MQL5/RSI_secret_sauce_XAUUSD/main.mq5 b/frontline/MQL5/RSI_secret_sauce_XAUUSD/main.mq5 new file mode 100644 index 0000000..1778a0f --- /dev/null +++ b/frontline/MQL5/RSI_secret_sauce_XAUUSD/main.mq5 @@ -0,0 +1,508 @@ +//+------------------------------------------------------------------+ +//| RSI_SecretSauce_XAUUSD.mq5 | +//| Copyright 2025, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2025, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.01" +#property description "RSI Secret Sauce Strategy: Wait for RSI to leave 70/30 zone, then enter when it comes back in" +#property description "Based on momentum flip concept - not traditional overbought/oversold" + +#include +#include + +//--- Input Parameters +input group "=== Trading Settings ===" +input string InpSymbol = "XAUUSD"; // Trading Symbol (set was tuned on BTCUSD) +input double InpLotSize = 0.1; // Lot Size (Profiles/Tester/secret_sauce.set) +input int InpMagicNumber = 789012; // Magic Number +input int InpSlippage = 10; // Slippage in points +input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M30; // Trading Timeframe (set value 30 = M30) + +input group "=== RSI Settings ===" +input int InpRSIPeriod = 16; // RSI Period +input double InpRSIOverbought = 72.5; // RSI Overbought Level +input double InpRSIOversold = 32.5; // RSI Oversold Level +input int InpRSILookback = 60; // RSI Lookback for Peak/Bottom Detection + +input group "=== Entry Logic ===" +input int InpPeakBars = 2; // Bars to confirm peak/bottom +input bool InpRequireDivergence = false; // Require divergence confirmation (optional) + +input group "=== Risk Management ===" +input double InpStopLossATR = 2.75; // Stop Loss (ATR multiples) +input double InpTakeProfitATR = 5.0; // Take Profit (ATR multiples) +input int InpATRPeriod = 14; // ATR Period +input bool InpUseSwingStopLoss = false; // Use previous swing high/low for stop loss +input int InpSwingLookback = 30; // Bars to look back for swing points + +input group "=== Position Management ===" +input int InpMaxPositions = 1; // Max Simultaneous Positions +input int InpMinBarsBetweenTrades = 7; // Min Bars Between Trades + +//--- Global Variables +CTrade trade; +CPositionInfo positionInfo; + +string actualSymbol; +int rsiHandle = INVALID_HANDLE; +int atrHandle = INVALID_HANDLE; + +double rsiBuffer[]; +double atrBuffer[]; +double highBuffer[]; +double lowBuffer[]; + +// RSI state tracking +bool rsiWasOverbought = false; // RSI was above 70 +bool rsiWasOversold = false; // RSI was below 30 +bool rsiBackInRange = false; // RSI came back into range +datetime lastRSIExitTime = 0; // When RSI left the range +datetime lastRSIReentryTime = 0; // When RSI came back in + +// Trade tracking +datetime lastTradeTime = 0; +int barsSinceLastTrade = 0; + +datetime lastBarTime = 0; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + // Determine actual symbol + if(InpSymbol == "" || InpSymbol == NULL) + actualSymbol = _Symbol; + else + actualSymbol = InpSymbol; + + // Check if symbol exists + if(!SymbolInfoInteger(actualSymbol, SYMBOL_SELECT)) + { + Print("Error: Symbol ", actualSymbol, " not found. Using chart symbol."); + actualSymbol = _Symbol; + } + + // Initialize RSI indicator + rsiHandle = iRSI(actualSymbol, InpTimeframe, InpRSIPeriod, PRICE_CLOSE); + if(rsiHandle == INVALID_HANDLE) + { + Print("Error creating RSI indicator"); + return INIT_FAILED; + } + ArraySetAsSeries(rsiBuffer, true); + + // Initialize ATR indicator + atrHandle = iATR(actualSymbol, InpTimeframe, InpATRPeriod); + if(atrHandle == INVALID_HANDLE) + { + Print("Error creating ATR indicator"); + return INIT_FAILED; + } + ArraySetAsSeries(atrBuffer, true); + + // Initialize price buffers + ArraySetAsSeries(highBuffer, true); + ArraySetAsSeries(lowBuffer, true); + + // Set trade parameters + trade.SetExpertMagicNumber(InpMagicNumber); + trade.SetDeviationInPoints(InpSlippage); + trade.SetTypeFilling(ORDER_FILLING_FOK); + + Print("=== RSI Secret Sauce Strategy Initialized ==="); + Print("Symbol: ", actualSymbol); + Print("Timeframe: ", EnumToString(InpTimeframe)); + Print("RSI Period: ", InpRSIPeriod, " | Overbought: ", InpRSIOverbought, " | Oversold: ", InpRSIOversold); + Print("Stop Loss: ", InpStopLossATR, "x ATR | Take Profit: ", InpTakeProfitATR, "x ATR"); + + return INIT_SUCCEEDED; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(rsiHandle != INVALID_HANDLE) + IndicatorRelease(rsiHandle); + if(atrHandle != INVALID_HANDLE) + IndicatorRelease(atrHandle); + + Print("Expert Advisor deinitialized. Reason: ", reason); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Check if we have enough bars + int requiredBars = MathMax(InpRSILookback, InpSwingLookback) + 10; + if(Bars(actualSymbol, InpTimeframe) < requiredBars) + return; + + // Check if this is a new bar (wait for candle close) + datetime currentBarTime = iTime(actualSymbol, InpTimeframe, 0); + if(currentBarTime == lastBarTime) + return; // Still the same bar, don't process + + lastBarTime = currentBarTime; + + // Update indicators + if(!UpdateIndicators()) + return; + + // Update RSI state tracking + UpdateRSIState(); + + // Check existing positions + CheckExistingPositions(); + + // Check for entry signals + if(CanOpenNewPosition()) + { + CheckEntrySignals(); + } +} + +//+------------------------------------------------------------------+ +//| Update indicator values | +//+------------------------------------------------------------------+ +bool UpdateIndicators() +{ + // Update RSI (need enough bars for lookback) + int rsiBarsNeeded = InpRSILookback + 5; + if(CopyBuffer(rsiHandle, 0, 0, rsiBarsNeeded, rsiBuffer) < rsiBarsNeeded) + return false; + + // Update ATR + if(CopyBuffer(atrHandle, 0, 0, 2, atrBuffer) < 2) + return false; + + // Update price buffers for swing detection + if(CopyHigh(actualSymbol, InpTimeframe, 0, InpSwingLookback + 5, highBuffer) < InpSwingLookback + 5) + return false; + if(CopyLow(actualSymbol, InpTimeframe, 0, InpSwingLookback + 5, lowBuffer) < InpSwingLookback + 5) + return false; + + return true; +} + +//+------------------------------------------------------------------+ +//| Update RSI state tracking | +//+------------------------------------------------------------------+ +void UpdateRSIState() +{ + double rsiCurrent = rsiBuffer[0]; + double rsiPrev = rsiBuffer[1]; + + // Check if RSI left overbought zone (was above 70, now below 70) + if(rsiPrev >= InpRSIOverbought && rsiCurrent < InpRSIOverbought) + { + rsiWasOverbought = true; + rsiBackInRange = true; + lastRSIExitTime = TimeCurrent(); + lastRSIReentryTime = TimeCurrent(); + Print(TimeToString(TimeCurrent()), " - RSI left overbought zone (", rsiPrev, " -> ", rsiCurrent, ")"); + } + + // Check if RSI left oversold zone (was below 30, now above 30) + if(rsiPrev <= InpRSIOversold && rsiCurrent > InpRSIOversold) + { + rsiWasOversold = true; + rsiBackInRange = true; + lastRSIExitTime = TimeCurrent(); + lastRSIReentryTime = TimeCurrent(); + Print(TimeToString(TimeCurrent()), " - RSI left oversold zone (", rsiPrev, " -> ", rsiCurrent, ")"); + } + + // Reset flags if RSI goes back to extreme + if(rsiCurrent >= InpRSIOverbought) + { + rsiWasOverbought = false; + rsiBackInRange = false; + } + + if(rsiCurrent <= InpRSIOversold) + { + rsiWasOversold = false; + rsiBackInRange = false; + } +} + +//+------------------------------------------------------------------+ +//| Check if we can open a new position | +//+------------------------------------------------------------------+ +bool CanOpenNewPosition() +{ + // Check max positions + int positionCount = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(positionInfo.SelectByIndex(i)) + { + if(positionInfo.Symbol() == actualSymbol && positionInfo.Magic() == InpMagicNumber) + positionCount++; + } + } + + if(positionCount >= InpMaxPositions) + return false; + + // Check minimum bars between trades + if(lastTradeTime > 0) + { + int barsSince = Bars(actualSymbol, InpTimeframe, lastTradeTime, TimeCurrent()); + if(barsSince < InpMinBarsBetweenTrades) + return false; + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check for entry signals | +//+------------------------------------------------------------------+ +void CheckEntrySignals() +{ + // LONG Entry: RSI was overbought (>70), came back in range, now look for peak + if(rsiWasOverbought && rsiBackInRange) + { + // Check if RSI is back in normal range (below 70) + if(rsiBuffer[0] < InpRSIOverbought) + { + // Look for a peak in RSI after re-entry + if(IsRSIPeak()) + { + Print(TimeToString(TimeCurrent()), " - LONG Signal: RSI peak detected after leaving overbought zone"); + OpenPosition(POSITION_TYPE_BUY); + } + } + } + + // SHORT Entry: RSI was oversold (<30), came back in range, now look for bottom + if(rsiWasOversold && rsiBackInRange) + { + // Check if RSI is back in normal range (above 30) + if(rsiBuffer[0] > InpRSIOversold) + { + // Look for a bottom in RSI after re-entry + if(IsRSIBottom()) + { + Print(TimeToString(TimeCurrent()), " - SHORT Signal: RSI bottom detected after leaving oversold zone"); + OpenPosition(POSITION_TYPE_SELL); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Check if RSI is forming a peak (for LONG entry) | +//+------------------------------------------------------------------+ +bool IsRSIPeak() +{ + // We need at least InpPeakBars + 1 bars to confirm a peak + if(ArraySize(rsiBuffer) < InpPeakBars + 2) + return false; + + // Check if current RSI is higher than previous bars (forming a peak) + double currentRSI = rsiBuffer[0]; + bool isPeak = true; + + // Check if current is higher than the next few bars + for(int i = 1; i <= InpPeakBars; i++) + { + if(rsiBuffer[i] >= currentRSI) + { + isPeak = false; + break; + } + } + + // Also check if previous bar was lower (confirming upward movement before peak) + if(rsiBuffer[1] >= currentRSI) + isPeak = false; + + return isPeak; +} + +//+------------------------------------------------------------------+ +//| Check if RSI is forming a bottom (for SHORT entry) | +//+------------------------------------------------------------------+ +bool IsRSIBottom() +{ + // We need at least InpPeakBars + 1 bars to confirm a bottom + if(ArraySize(rsiBuffer) < InpPeakBars + 2) + return false; + + // Check if current RSI is lower than previous bars (forming a bottom) + double currentRSI = rsiBuffer[0]; + bool isBottom = true; + + // Check if current is lower than the next few bars + for(int i = 1; i <= InpPeakBars; i++) + { + if(rsiBuffer[i] <= currentRSI) + { + isBottom = false; + break; + } + } + + // Also check if previous bar was higher (confirming downward movement before bottom) + if(rsiBuffer[1] <= currentRSI) + isBottom = false; + + return isBottom; +} + +//+------------------------------------------------------------------+ +//| Open position | +//+------------------------------------------------------------------+ +void OpenPosition(ENUM_POSITION_TYPE type) +{ + double price = (type == POSITION_TYPE_BUY) ? + SymbolInfoDouble(actualSymbol, SYMBOL_ASK) : + SymbolInfoDouble(actualSymbol, SYMBOL_BID); + + if(price <= 0) + return; + + // Calculate stop loss and take profit + double sl = 0.0, tp = 0.0; + if(!CalculateStops(price, type, sl, tp)) + { + Print("Error: Failed to calculate stops"); + return; + } + + string comment = "RSI_Secret_" + (type == POSITION_TYPE_BUY ? "LONG" : "SHORT"); + + bool result = false; + if(type == POSITION_TYPE_BUY) + result = trade.Buy(InpLotSize, actualSymbol, 0, sl, tp, comment); + else + result = trade.Sell(InpLotSize, actualSymbol, 0, sl, tp, comment); + + if(result) + { + lastTradeTime = TimeCurrent(); + ulong ticket = trade.ResultOrder(); + Print(TimeToString(TimeCurrent()), " - Position opened: ", comment, " Ticket: ", ticket, + " Price: ", price, " SL: ", sl, " TP: ", tp); + + // Reset RSI state after opening position + if(type == POSITION_TYPE_BUY) + rsiWasOverbought = false; + else + rsiWasOversold = false; + rsiBackInRange = false; + } + else + { + Print("Failed to open position: ", comment, " Error: ", + trade.ResultRetcode(), " - ", trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Calculate stop loss and take profit | +//+------------------------------------------------------------------+ +bool CalculateStops(double price, ENUM_POSITION_TYPE type, double &sl, double &tp) +{ + double atrValue = atrBuffer[0]; + if(atrValue <= 0) + atrValue = price * 0.01; // Fallback: 1% of price + + double slDistance = atrValue * InpStopLossATR; + double tpDistance = atrValue * InpTakeProfitATR; + + int digits = (int)SymbolInfoInteger(actualSymbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(actualSymbol, SYMBOL_POINT); + int stopsLevel = (int)SymbolInfoInteger(actualSymbol, SYMBOL_TRADE_STOPS_LEVEL); + double minStopDistance = MathMax(stopsLevel * point, point * 10); + + // Use swing-based stop loss if enabled + if(InpUseSwingStopLoss) + { + double swingStop = GetSwingStopLoss(price, type); + if(swingStop > 0) + { + if(type == POSITION_TYPE_BUY) + { + if(swingStop < price && (price - swingStop) > minStopDistance) + slDistance = price - swingStop; + } + else + { + if(swingStop > price && (swingStop - price) > minStopDistance) + slDistance = swingStop - price; + } + } + } + + // Ensure minimum distance + if(slDistance < minStopDistance) + slDistance = minStopDistance; + if(tpDistance < minStopDistance) + tpDistance = minStopDistance; + + if(type == POSITION_TYPE_BUY) + { + sl = NormalizeDouble(price - slDistance, digits); + tp = NormalizeDouble(price + tpDistance, digits); + } + else + { + sl = NormalizeDouble(price + slDistance, digits); + tp = NormalizeDouble(price - tpDistance, digits); + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Get swing-based stop loss (previous swing high/low) | +//+------------------------------------------------------------------+ +double GetSwingStopLoss(double currentPrice, ENUM_POSITION_TYPE type) +{ + // For LONG: find previous swing low + // For SHORT: find previous swing high + + if(type == POSITION_TYPE_BUY) + { + // Find the lowest low in the lookback period + double lowestLow = lowBuffer[0]; + for(int i = 1; i < InpSwingLookback && i < ArraySize(lowBuffer); i++) + { + if(lowBuffer[i] < lowestLow) + lowestLow = lowBuffer[i]; + } + return lowestLow; + } + else + { + // Find the highest high in the lookback period + double highestHigh = highBuffer[0]; + for(int i = 1; i < InpSwingLookback && i < ArraySize(highBuffer); i++) + { + if(highBuffer[i] > highestHigh) + highestHigh = highBuffer[i]; + } + return highestHigh; + } +} + +//+------------------------------------------------------------------+ +//| Check existing positions | +//+------------------------------------------------------------------+ +void CheckExistingPositions() +{ + // Position management can be added here if needed + // For now, positions are managed by TP/SL +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united/Strategies/RSIReversalAsianStrategy.mqh b/frontline/MQL5/_united/Strategies/RSIReversalAsianStrategy.mqh index 46dc9d3..6762248 100644 --- a/frontline/MQL5/_united/Strategies/RSIReversalAsianStrategy.mqh +++ b/frontline/MQL5/_united/Strategies/RSIReversalAsianStrategy.mqh @@ -259,7 +259,7 @@ bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol, double rsi[]; ArraySetAsSeries(rsi, true); - int retryCount = 0; + retryCount = 0; bool rsiInitialized = false; while(retryCount < 10 && !rsiInitialized) diff --git a/frontline/MQL5/_united_dynamic/MagicNumberHelpers.mqh b/frontline/MQL5/_united_dynamic/MagicNumberHelpers.mqh new file mode 100644 index 0000000..dc1fa31 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/MagicNumberHelpers.mqh @@ -0,0 +1,159 @@ +//+------------------------------------------------------------------+ +//| 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" + +//+------------------------------------------------------------------+ +//| Select position by symbol and magic number | +//+------------------------------------------------------------------+ +bool PositionSelectByMagic(string symbol, ulong magic_number) +{ + // First try to find position by symbol + if(!PositionSelect(symbol)) + return false; + + // Check if the selected position has the correct magic number + if(PositionGetInteger(POSITION_MAGIC) != magic_number) + { + // Position exists but wrong magic number, search all positions + 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; +} + +//+------------------------------------------------------------------+ +//| Select position by ticket and verify magic number and symbol | +//+------------------------------------------------------------------+ +bool PositionSelectByTicketAndMagic(ulong ticket, ulong magic_number) +{ + if(!PositionSelectByTicket(ticket)) + return false; + + return (PositionGetInteger(POSITION_MAGIC) == magic_number); +} + +//+------------------------------------------------------------------+ +//| Select position by ticket and verify symbol, magic number | +//+------------------------------------------------------------------+ +bool PositionSelectByTicketSymbolAndMagic(ulong ticket, string symbol, ulong magic_number) +{ + if(!PositionSelectByTicket(ticket)) + return false; + + return (PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == magic_number); +} + +//+------------------------------------------------------------------+ +//| Check if position exists with correct magic number | +//+------------------------------------------------------------------+ +bool PositionExistsByMagic(string symbol, ulong magic_number) +{ + return PositionSelectByMagic(symbol, magic_number); +} + +//+------------------------------------------------------------------+ +//| Get position ticket by symbol and magic number | +//+------------------------------------------------------------------+ +ulong GetPositionTicketByMagic(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 ticket; + } + } + } + return 0; +} + +//+------------------------------------------------------------------+ +//| Close position by symbol and magic number | +//+------------------------------------------------------------------+ +bool ClosePositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number) +{ + ulong ticket = GetPositionTicketByMagic(symbol, magic_number); + if(ticket == 0) + return false; + + return trade_obj.PositionClose(ticket); +} + +//+------------------------------------------------------------------+ +//| Modify position by symbol and magic number | +//+------------------------------------------------------------------+ +bool ModifyPositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number, + double sl, double tp) +{ + ulong ticket = GetPositionTicketByMagic(symbol, magic_number); + if(ticket == 0) + return false; + + return trade_obj.PositionModify(ticket, sl, tp); +} + +//+------------------------------------------------------------------+ +//| Get position profit by symbol and magic number | +//+------------------------------------------------------------------+ +double GetPositionProfitByMagic(string symbol, ulong magic_number) +{ + if(!PositionSelectByMagic(symbol, magic_number)) + return 0.0; + + return PositionGetDouble(POSITION_PROFIT); +} + +//+------------------------------------------------------------------+ +//| Get position type by symbol and magic number | +//+------------------------------------------------------------------+ +ENUM_POSITION_TYPE GetPositionTypeByMagic(string symbol, ulong magic_number) +{ + if(!PositionSelectByMagic(symbol, magic_number)) + return WRONG_VALUE; + + return (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); +} + +//+------------------------------------------------------------------+ +//| Count positions by symbol and magic number | +//+------------------------------------------------------------------+ +int CountPositionsByMagic(string symbol, ulong magic_number) +{ + int count = 0; + 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) + { + count++; + } + } + } + return count; +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/PEPPERSTONE_US_SETUP.md b/frontline/MQL5/_united_dynamic/PEPPERSTONE_US_SETUP.md new file mode 100644 index 0000000..82dc835 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/PEPPERSTONE_US_SETUP.md @@ -0,0 +1,75 @@ +# Pepperstone US - Symbol Setup Guide + +## Finding Correct Symbol Names in MetaTrader 5 + +### Step-by-Step Instructions: + +1. **Open Market Watch Window** + - Press `Ctrl+M` or go to `View > Market Watch` + +2. **Show All Symbols** + - Right-click in the Market Watch window + - Select `Show All` or `Symbols` + - This shows all available symbols from your broker + +3. **Search for Your Symbols** + - Use the search box in the Market Watch window + - Search for: "AAPL", "MSFT", "NVDA", "TSLA", "BTCUSD", "XAUUSD" + +4. **Note the Exact Symbol Name** + - The symbol name shown in Market Watch is what you need to use + - Common formats for Pepperstone US: + - Stocks: `AAPL.US`, `MSFT.US`, `NVDA.US`, `TSLA.US` + - Or: `NASDAQ:AAPL`, `NASDAQ:MSFT`, etc. + - Or: Just `AAPL`, `MSFT`, etc. (if available) + +5. **Add to Market Watch** + - Double-click the symbol to add it to your Market Watch + - Or right-click and select `Show` + +6. **Update EA Inputs** + - Open the EA inputs in MetaTrader 5 + - Update each symbol parameter with the exact name from Market Watch + +## Common Pepperstone US Symbol Formats + +### US Stocks: +- **Apple**: `AAPL.US` or `NASDAQ:AAPL` or `AAPL` +- **Microsoft**: `MSFT.US` or `NASDAQ:MSFT` or `MSFT` +- **NVIDIA**: `NVDA.US` or `NASDAQ:NVDA` or `NVDA` +- **Tesla**: `TSLA.US` or `NASDAQ:TSLA` or `TSLA` + +### Cryptocurrencies: +- **Bitcoin**: `BTCUSD` or `BTC/USD` or `BTCUSD.c` + +### Precious Metals: +- **Gold**: `XAUUSD` or `GOLD` or `XAU/USD` + +## Important Notes: + +1. **Symbol Names are Case-Sensitive**: Use exact capitalization +2. **Add Symbols to Market Watch**: Symbols must be in Market Watch for the EA to access them +3. **Check Trading Hours**: US stocks trade during US market hours (9:30 AM - 4:00 PM ET) +4. **CFD vs Stock**: Pepperstone offers CFDs on stocks, not actual stocks +5. **Spread**: Check the spread for each symbol - some may have wider spreads + +## Troubleshooting: + +### If Symbol Not Found: +1. Check if you're connected to Pepperstone US server +2. Verify your account type supports the symbol +3. Contact Pepperstone support for symbol availability +4. Check if symbol requires special account permissions + +### If EA Shows "Symbol Not Available": +1. Make sure symbol is added to Market Watch +2. Verify symbol name matches exactly (including dots, colons, etc.) +3. Check broker connection status +4. Try different symbol format variations + +## Testing Symbols: + +You can test if a symbol works by: +1. Opening a chart with that symbol +2. If chart opens successfully, the symbol name is correct +3. Use that exact symbol name in the EA inputs diff --git a/frontline/MQL5/_united_dynamic/PerformanceEvaluator.mqh b/frontline/MQL5/_united_dynamic/PerformanceEvaluator.mqh new file mode 100644 index 0000000..1389540 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/PerformanceEvaluator.mqh @@ -0,0 +1,607 @@ +//+------------------------------------------------------------------+ +//| PerformanceEvaluator.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" + +//+------------------------------------------------------------------+ +//| Performance Metrics Structure | +//+------------------------------------------------------------------+ +struct StrategyPerformance { + string strategyName; + string symbol; // Store symbol to determine if it's a stock + int magicNumber; + double initialLotSize; + double currentLotSize; + double quarterProfit; + double quarterTrades; + double quarterWins; + double quarterLosses; + double maxDrawdown; + double winRate; + datetime quarterStart; + datetime quarterEnd; + bool isActive; + bool inPenaltyMode; // True if strategy is in penalty (worst performer) + double lotSizeBeforePenalty; // Store lot size before penalty + datetime penaltyStartTime; // When penalty started +}; + +//+------------------------------------------------------------------+ +//| Global Performance Tracking | +//+------------------------------------------------------------------+ +StrategyPerformance strategyPerformances[]; +int totalStrategies = 0; +datetime lastMonthCheck = 0; +datetime currentMonthStart = 0; +datetime currentMonthEnd = 0; + +//+------------------------------------------------------------------+ +//| Performance Adjustment Parameters | +//+------------------------------------------------------------------+ +input group "=== Performance Evaluation Settings ===" +input bool PE_EnableAutoAdjustment = true; // Enable automatic lot size adjustment +input double PE_LotSizeIncreasePercent = 10.0; // % increase for top-ranked strategies +input double PE_LotSizeDecreasePercent = 10.0; // % decrease for bottom-ranked strategies +input double PE_MinLotSize = 0.01; // Minimum lot size for forex/crypto +input double PE_MinLotSizeStocks = 5.0; // Minimum lot size for stocks (5-10 range) +input double PE_MaxLotSize = 100.0; // Maximum lot size after adjustment +input int PE_TopPerformersCount = 3; // Number of top strategies to increase lot size +input int PE_BottomPerformersCount = 3; // Number of bottom strategies to decrease lot size +input bool PE_UseWinRateWeight = true; // Consider win rate in ranking (50% profit, 50% win rate) +input bool PE_EnableBlitzPlay = true; // Enable blitz play: worst performer gets minimum lot size penalty +input bool PE_EnableLogging = true; // Enable performance logging + +//+------------------------------------------------------------------+ +//| Initialize Performance Tracking | +//+------------------------------------------------------------------+ +void InitPerformanceTracking() +{ + // Calculate current month dates + MqlDateTime dt; + TimeToStruct(TimeCurrent(), dt); + + // Determine month start (first day of current month) + dt.day = 1; + dt.hour = 0; + dt.min = 0; + dt.sec = 0; + currentMonthStart = StructToTime(dt); + + // Calculate month end (first day of next month - 1 second) + dt.mon += 1; + if(dt.mon > 12) + { + dt.mon = 1; + dt.year++; + } + currentMonthEnd = StructToTime(dt) - 1; // End of last day of month + + lastMonthCheck = TimeCurrent(); + + if(PE_EnableLogging) + { + Print("Performance Evaluator: Initialized"); + Print("Current Month Start: ", TimeToString(currentMonthStart)); + Print("Current Month End: ", TimeToString(currentMonthEnd)); + } +} + +//+------------------------------------------------------------------+ +//| Check if Symbol is a Stock | +//+------------------------------------------------------------------+ +bool IsStockSymbol(string symbol) +{ + // Check if symbol contains common stock indicators + if(StringFind(symbol, ".US") >= 0) return true; + if(StringFind(symbol, "NASDAQ:") >= 0) return true; + if(StringFind(symbol, "NYSE:") >= 0) return true; + + // Note: Symbol category check removed to avoid enum conversion issues + // String-based checks (.US, NASDAQ:, NYSE:, common tickers) are sufficient + + // Common stock tickers (without .US suffix) + string commonStocks[] = {"AAPL", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "AMD", "NFLX"}; + for(int i = 0; i < ArraySize(commonStocks); i++) + { + if(StringFind(symbol, commonStocks[i]) == 0) return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Get Minimum Lot Size for Symbol | +//+------------------------------------------------------------------+ +double GetMinLotSizeForSymbol(string symbol) +{ + if(IsStockSymbol(symbol)) + return PE_MinLotSizeStocks; + else + return PE_MinLotSize; +} + +//+------------------------------------------------------------------+ +//| Register Strategy for Performance Tracking | +//+------------------------------------------------------------------+ +void RegisterStrategy(string strategyName, int magicNumber, double initialLotSize, string symbol = "") +{ + // Check if strategy already registered + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].strategyName == strategyName && + strategyPerformances[i].magicNumber == magicNumber) + { + if(PE_EnableLogging) + Print("Performance Evaluator: Strategy '", strategyName, "' already registered"); + return; + } + } + + // Add new strategy + int newSize = ArraySize(strategyPerformances) + 1; + ArrayResize(strategyPerformances, newSize); + + strategyPerformances[newSize - 1].strategyName = strategyName; + strategyPerformances[newSize - 1].symbol = symbol; + strategyPerformances[newSize - 1].magicNumber = magicNumber; + strategyPerformances[newSize - 1].initialLotSize = initialLotSize; + // Start with minimum lot size for safety (symbol-specific minimum) + double minLot = GetMinLotSizeForSymbol(symbol); + strategyPerformances[newSize - 1].currentLotSize = minLot; + strategyPerformances[newSize - 1].quarterProfit = 0.0; + strategyPerformances[newSize - 1].quarterTrades = 0; + strategyPerformances[newSize - 1].quarterWins = 0; + strategyPerformances[newSize - 1].quarterLosses = 0; + strategyPerformances[newSize - 1].maxDrawdown = 0.0; + strategyPerformances[newSize - 1].winRate = 0.0; + strategyPerformances[newSize - 1].quarterStart = currentMonthStart; + strategyPerformances[newSize - 1].quarterEnd = currentMonthEnd; + strategyPerformances[newSize - 1].isActive = true; + strategyPerformances[newSize - 1].inPenaltyMode = false; + strategyPerformances[newSize - 1].lotSizeBeforePenalty = initialLotSize; + strategyPerformances[newSize - 1].penaltyStartTime = 0; + + totalStrategies = newSize; + + if(PE_EnableLogging) + Print("Performance Evaluator: Registered strategy '", strategyName, + "' (Magic: ", magicNumber, ", Initial Lot: ", initialLotSize, ")"); +} + +//+------------------------------------------------------------------+ +//| Update Strategy Performance Metrics | +//+------------------------------------------------------------------+ +void UpdateStrategyPerformance(string strategyName, int magicNumber) +{ + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].strategyName == strategyName && + strategyPerformances[i].magicNumber == magicNumber && + strategyPerformances[i].isActive) + { + // Calculate performance for current quarter + double totalProfit = 0.0; + int totalTrades = 0; + int wins = 0; + int losses = 0; + double maxDD = 0.0; + double peakBalance = 0.0; + + // Scan all closed deals in current quarter + datetime quarterStart = strategyPerformances[i].quarterStart; + datetime quarterEnd = strategyPerformances[i].quarterEnd; + + // Select history for the quarter + if(HistorySelect(quarterStart, quarterEnd)) + { + int totalDeals = HistoryDealsTotal(); + for(int j = 0; j < totalDeals; j++) + { + ulong ticket = HistoryDealGetTicket(j); + if(ticket > 0) + { + long dealMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC); + if(dealMagic == magicNumber) + { + double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); + double swap = HistoryDealGetDouble(ticket, DEAL_SWAP); + double commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION); + double totalDealProfit = profit + swap + commission; + + totalProfit += totalDealProfit; + totalTrades++; + + if(totalDealProfit > 0) + wins++; + else if(totalDealProfit < 0) + losses++; + } + } + } + } + + // Calculate win rate + double winRate = 0.0; + if(totalTrades > 0) + winRate = (double)wins / (double)totalTrades * 100.0; + + // Update metrics + strategyPerformances[i].quarterProfit = totalProfit; + strategyPerformances[i].quarterTrades = totalTrades; + strategyPerformances[i].quarterWins = wins; + strategyPerformances[i].quarterLosses = losses; + strategyPerformances[i].winRate = winRate; + + break; + } + } +} + +//+------------------------------------------------------------------+ +//| Strategy Ranking Structure | +//+------------------------------------------------------------------+ +struct StrategyRank { + int index; + double score; +}; + +//+------------------------------------------------------------------+ +//| Calculate Strategy Score for Ranking | +//+------------------------------------------------------------------+ +double CalculateStrategyScore(int strategyIndex) +{ + double profit = strategyPerformances[strategyIndex].quarterProfit; + double winRate = strategyPerformances[strategyIndex].winRate; + double trades = strategyPerformances[strategyIndex].quarterTrades; + + // Normalize profit (scale to 0-100 range, assuming max profit of $1000) + double normalizedProfit = MathMin(profit / 10.0, 100.0); + if(profit < 0) normalizedProfit = profit / 5.0; // Penalize losses more + + // Calculate score + double score = 0.0; + if(PE_UseWinRateWeight) + { + // 50% profit, 50% win rate (if enough trades) + if(trades >= 5) + score = (normalizedProfit * 0.5) + (winRate * 0.5); + else + score = normalizedProfit; // Not enough trades, use profit only + } + else + { + // Profit only + score = normalizedProfit; + } + + return score; +} + +//+------------------------------------------------------------------+ +//| Check if Month Ended and Evaluate Performance | +//+------------------------------------------------------------------+ +void CheckMonthEnd() +{ + datetime now = TimeCurrent(); + + // Check if we've entered a new month + if(now >= currentMonthEnd) + { + if(PE_EnableLogging) + Print("Performance Evaluator: Month ended. Evaluating and ranking strategies..."); + + // Update performance metrics for all strategies + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + UpdateStrategyPerformance(strategyPerformances[i].strategyName, + strategyPerformances[i].magicNumber); + } + } + + // Rank strategies + int activeCount = 0; + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + activeCount++; + } + + if(activeCount > 0) + { + // Create ranking array + StrategyRank ranks[]; + ArrayResize(ranks, activeCount); + int rankIndex = 0; + + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + ranks[rankIndex].index = i; + ranks[rankIndex].score = CalculateStrategyScore(i); + rankIndex++; + } + } + + // Sort by score (descending - highest score first) + for(int i = 0; i < activeCount - 1; i++) + { + for(int j = i + 1; j < activeCount; j++) + { + if(ranks[j].score > ranks[i].score) + { + StrategyRank temp = ranks[i]; + ranks[i] = ranks[j]; + ranks[j] = temp; + } + } + } + + // Adjust lot sizes based on ranking + if(PE_EnableAutoAdjustment) + { + // Increase top performers (skip if in penalty mode) + int topCount = MathMin(PE_TopPerformersCount, activeCount); + for(int i = 0; i < topCount; i++) + { + int strategyIdx = ranks[i].index; + + // Skip if strategy is in penalty mode + if(strategyPerformances[strategyIdx].inPenaltyMode) + continue; + + double oldLotSize = strategyPerformances[strategyIdx].currentLotSize; + double newLotSize = oldLotSize * (1.0 + PE_LotSizeIncreasePercent / 100.0); + + if(newLotSize > PE_MaxLotSize) + newLotSize = PE_MaxLotSize; + + strategyPerformances[strategyIdx].currentLotSize = newLotSize; + + if(PE_EnableLogging) + Print("Performance Evaluator: Rank #", (i+1), " - Increasing '", + strategyPerformances[strategyIdx].strategyName, + "' lot size from ", oldLotSize, " to ", newLotSize, + " (Score: ", DoubleToString(ranks[i].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2), + ", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%)"); + } + + // Decrease bottom performers (skip worst one if blitz play is enabled) + int bottomCount = MathMin(PE_BottomPerformersCount, activeCount); + int startIdx = activeCount - bottomCount; + + // If blitz play is enabled, skip the worst performer (it will get minimum penalty) + if(PE_EnableBlitzPlay && activeCount > 0) + startIdx = activeCount - bottomCount + 1; + + for(int i = startIdx; i < activeCount; i++) + { + int strategyIdx = ranks[i].index; + + // Skip if strategy is in penalty mode + if(strategyPerformances[strategyIdx].inPenaltyMode) + continue; + + double oldLotSize = strategyPerformances[strategyIdx].currentLotSize; + double newLotSize = oldLotSize * (1.0 - PE_LotSizeDecreasePercent / 100.0); + + // Use symbol-specific minimum lot size + double minLot = GetMinLotSizeForSymbol(strategyPerformances[strategyIdx].symbol); + if(newLotSize < minLot) + newLotSize = minLot; + + strategyPerformances[strategyIdx].currentLotSize = newLotSize; + + if(PE_EnableLogging) + Print("Performance Evaluator: Rank #", (i+1), " - Decreasing '", + strategyPerformances[strategyIdx].strategyName, + "' lot size from ", oldLotSize, " to ", newLotSize, + " (Score: ", DoubleToString(ranks[i].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2), + ", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%)"); + } + } + + // Blitz Play: Apply penalty to worst performer + if(PE_EnableBlitzPlay && activeCount > 0) + { + // Find worst performer (last in ranking) + int worstIdx = ranks[activeCount - 1].index; + + // Remove penalty from previous worst performer (if any) + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive && strategyPerformances[i].inPenaltyMode) + { + // Check if penalty period has passed (one month) + if(now - strategyPerformances[i].penaltyStartTime >= 2592000) // ~30 days + { + // Restore lot size to before penalty + strategyPerformances[i].currentLotSize = strategyPerformances[i].lotSizeBeforePenalty; + strategyPerformances[i].inPenaltyMode = false; + strategyPerformances[i].penaltyStartTime = 0; + + if(PE_EnableLogging) + Print("Blitz Play: Penalty removed from '", strategyPerformances[i].strategyName, + "'. Lot size restored to ", strategyPerformances[i].currentLotSize); + } + } + } + + // Apply penalty to new worst performer + if(!strategyPerformances[worstIdx].inPenaltyMode) + { + strategyPerformances[worstIdx].lotSizeBeforePenalty = strategyPerformances[worstIdx].currentLotSize; + // Use symbol-specific minimum lot size + double minLot = GetMinLotSizeForSymbol(strategyPerformances[worstIdx].symbol); + strategyPerformances[worstIdx].currentLotSize = minLot; + strategyPerformances[worstIdx].inPenaltyMode = true; + strategyPerformances[worstIdx].penaltyStartTime = now; + + if(PE_EnableLogging) + Print("Blitz Play: WORST PERFORMER - '", strategyPerformances[worstIdx].strategyName, + "' penalized! Lot size reduced from ", strategyPerformances[worstIdx].lotSizeBeforePenalty, + " to minimum ", minLot, " (Score: ", DoubleToString(ranks[activeCount - 1].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[worstIdx].quarterProfit, 2), ")"); + } + } + + // Log performance report + if(PE_EnableLogging) + { + Print("=== Monthly Performance Ranking ==="); + for(int i = 0; i < activeCount; i++) + { + int strategyIdx = ranks[i].index; + Print("Rank #", (i+1), ": ", strategyPerformances[strategyIdx].strategyName, + " - Score: ", DoubleToString(ranks[i].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2), + ", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%", + ", Trades: ", (int)strategyPerformances[strategyIdx].quarterTrades, + ", Lot Size: ", DoubleToString(strategyPerformances[strategyIdx].currentLotSize, 2)); + } + Print("==================================="); + } + } + + // Reset month metrics for all strategies + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + strategyPerformances[i].quarterProfit = 0.0; + strategyPerformances[i].quarterTrades = 0; + strategyPerformances[i].quarterWins = 0; + strategyPerformances[i].quarterLosses = 0; + strategyPerformances[i].maxDrawdown = 0.0; + strategyPerformances[i].winRate = 0.0; + } + } + + // Update month dates + MqlDateTime dt; + TimeToStruct(now, dt); + + // First day of current month + dt.day = 1; + dt.hour = 0; + dt.min = 0; + dt.sec = 0; + currentMonthStart = StructToTime(dt); + + // First day of next month - 1 second + dt.mon += 1; + if(dt.mon > 12) + { + dt.mon = 1; + dt.year++; + } + currentMonthEnd = StructToTime(dt) - 1; + + // Update month dates for all strategies + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + strategyPerformances[i].quarterStart = currentMonthStart; + strategyPerformances[i].quarterEnd = currentMonthEnd; + } + + lastMonthCheck = now; + } +} + +//+------------------------------------------------------------------+ +//| Get Current Lot Size for Strategy | +//+------------------------------------------------------------------+ +double GetStrategyLotSize(string strategyName, int magicNumber) +{ + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].strategyName == strategyName && + strategyPerformances[i].magicNumber == magicNumber && + strategyPerformances[i].isActive) + { + return strategyPerformances[i].currentLotSize; + } + } + return 0.0; +} + +//+------------------------------------------------------------------+ +//| Process Performance Evaluation (call from OnTick) | +//+------------------------------------------------------------------+ +void ProcessPerformanceEvaluation() +{ + // Check if month ended + CheckMonthEnd(); + + // Check for penalty expiration (blitz play) + if(PE_EnableBlitzPlay) + { + datetime now = TimeCurrent(); + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive && strategyPerformances[i].inPenaltyMode) + { + // Check if penalty period has passed (one month = ~30 days) + if(now - strategyPerformances[i].penaltyStartTime >= 2592000) + { + // Restore lot size to before penalty + strategyPerformances[i].currentLotSize = strategyPerformances[i].lotSizeBeforePenalty; + strategyPerformances[i].inPenaltyMode = false; + strategyPerformances[i].penaltyStartTime = 0; + + if(PE_EnableLogging) + Print("Blitz Play: Penalty expired for '", strategyPerformances[i].strategyName, + "'. Lot size restored to ", strategyPerformances[i].currentLotSize); + } + } + } + } + + // Update performance metrics periodically (every hour) + static datetime lastUpdate = 0; + if(TimeCurrent() - lastUpdate >= 3600) + { + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + UpdateStrategyPerformance(strategyPerformances[i].strategyName, + strategyPerformances[i].magicNumber); + } + } + lastUpdate = TimeCurrent(); + } +} + +//+------------------------------------------------------------------+ +//| Get Performance Summary | +//+------------------------------------------------------------------+ +string GetPerformanceSummary() +{ + string summary = "\n=== Performance Summary ===\n"; + summary += "Current Month: " + TimeToString(currentMonthStart) + " to " + TimeToString(currentMonthEnd) + "\n\n"; + + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + summary += strategyPerformances[i].strategyName + ":\n"; + summary += " Profit: $" + DoubleToString(strategyPerformances[i].quarterProfit, 2) + "\n"; + summary += " Trades: " + IntegerToString((int)strategyPerformances[i].quarterTrades) + "\n"; + summary += " Win Rate: " + DoubleToString(strategyPerformances[i].winRate, 2) + "%\n"; + summary += " Lot Size: " + DoubleToString(strategyPerformances[i].currentLotSize, 2) + "\n\n"; + } + } + + return summary; +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/STRATEGY_CONFIGURATION.md b/frontline/MQL5/_united_dynamic/STRATEGY_CONFIGURATION.md new file mode 100644 index 0000000..799c760 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/STRATEGY_CONFIGURATION.md @@ -0,0 +1,76 @@ +# United EA Strategy Configuration Summary + +## Strategy Symbols and Magic Numbers + +### Strategy 1: DarvasBox +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 135790 + +### Strategy 2: EMASlopeDistance +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 12350 + +### Strategy 3: RSICrossOverReversal +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 7 + +### Strategy 4: RSIMidPointHijack +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Numbers**: + - RSIFollow: 1001 + - RSIReverse: 1002 + - EMACross: 1003 + +### Strategy 5: RSI Scalping APPL (Apple) +- **Symbol**: AAPL (Apple stock) +- **Magic Number**: 20001 +- **Note**: Changed from "APPL" to "AAPL" (correct ticker symbol) + +### Strategy 6: RSI Scalping BTCUSD +- **Symbol**: BTCUSD (Bitcoin/USD) +- **Magic Number**: 123459123 + +### Strategy 7: RSI Scalping MSFT +- **Symbol**: MSFT (Microsoft stock) +- **Magic Number**: 20002 + +### Strategy 8: RSI Scalping NVDA +- **Symbol**: NVDA (NVIDIA stock) +- **Magic Number**: 20003 + +### Strategy 9: RSI Scalping TSLA +- **Symbol**: TSLA (Tesla stock) +- **Magic Number**: 125421321 + +### Strategy 10: RSI Scalping XAUUSD +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 129102315 + +## Important Notes + +1. **Stock Symbols**: Stock symbols (AAPL, MSFT, NVDA, TSLA) must be: + - Added to Market Watch in MetaTrader 5 + - Available from your broker + - Use the correct ticker symbol (e.g., "AAPL" not "APPL") + +2. **Magic Numbers**: All strategies have unique magic numbers to prevent interference: + - Each strategy can be identified by its magic number + - RSIMidPointHijack uses 3 magic numbers (one for each sub-strategy) + +3. **Symbol Configuration**: Each strategy trades on its own symbol: + - You can change symbols in the input parameters + - The EA will log warnings if a symbol is not available + - Strategies with unavailable symbols will be skipped (EA continues running) + +4. **RSI Scalping Strategies**: + - Each RSI Scalping variant trades on a different symbol + - They all use the same strategy logic but with different parameters + - Buy and sell signals are generated based on RSI levels for each symbol + +## Troubleshooting + +If stock symbols are not working: +1. Check if the symbol exists in your broker's symbol list +2. Add the symbol to Market Watch in MetaTrader 5 +3. Verify the symbol name matches your broker's naming convention +4. Some brokers use prefixes/suffixes (e.g., "NASDAQ:AAPL" or "AAPL.US") diff --git a/frontline/MQL5/_united_dynamic/Strategies/DarvasBoxStrategy.mqh b/frontline/MQL5/_united_dynamic/Strategies/DarvasBoxStrategy.mqh new file mode 100644 index 0000000..ff1d13b --- /dev/null +++ b/frontline/MQL5/_united_dynamic/Strategies/DarvasBoxStrategy.mqh @@ -0,0 +1,300 @@ +//+------------------------------------------------------------------+ +//| DarvasBoxStrategy.mqh | +//+------------------------------------------------------------------+ + +bool InitDarvasBox(string symbol) +{ + dbData.symbol = symbol; + dbData.boxHigh = 0; + dbData.boxLow = 0; + dbData.boxFormed = false; + dbData.lastBoxTime = 0; + dbData.boxName = "DarvasBox_" + IntegerToString(DB_MagicNumber) + "_"; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("DarvasBox: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + dbData.point = SymbolInfoDouble(symbol, SYMBOL_POINT); + dbData.minStopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * dbData.point; + + dbData.maHandle = iMA(symbol, DB_TrendTimeframe, DB_MA_Period, 0, DB_MA_Method, DB_MA_Price); + dbData.volumeHandle = iVolumes(symbol, PERIOD_CURRENT, VOLUME_TICK); + + if(dbData.maHandle == INVALID_HANDLE || dbData.volumeHandle == INVALID_HANDLE) + { + Print("DarvasBox: Error creating indicators for '", symbol, "'"); + return false; + } + + dbData.trade.SetDeviationInPoints(10); + dbData.trade.SetTypeFilling(ORDER_FILLING_IOC); + dbData.trade.SetAsyncMode(false); + dbData.trade.SetExpertMagicNumber(DB_MagicNumber); + + ObjectsDeleteAll(0, dbData.boxName); + dbData.isInitialized = true; + Print("DarvasBox: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitDarvasBox() +{ + if(dbData.maHandle != INVALID_HANDLE) IndicatorRelease(dbData.maHandle); + if(dbData.volumeHandle != INVALID_HANDLE) IndicatorRelease(dbData.volumeHandle); + ObjectsDeleteAll(0, dbData.boxName); +} + +void DrawDarvasBox() +{ + if(!dbData.boxFormed) return; + + datetime time1 = iTime(dbData.symbol, PERIOD_H1, DB_BoxPeriod); + datetime time2 = iTime(dbData.symbol, PERIOD_H1, 0); + + ObjectsDeleteAll(0, dbData.boxName); + + ObjectCreate(0, dbData.boxName + "Top", OBJ_TREND, 0, time1, dbData.boxHigh, time2, dbData.boxHigh); + ObjectCreate(0, dbData.boxName + "Bottom", OBJ_TREND, 0, time1, dbData.boxLow, time2, dbData.boxLow); + + ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_COLOR, DB_BoxColor); + ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_COLOR, DB_BoxColor); + ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_WIDTH, DB_BoxWidth); + ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_WIDTH, DB_BoxWidth); + ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_RAY_RIGHT, true); + ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_RAY_RIGHT, true); +} + +void CalculateDarvasBox() +{ + double high = 0; + double low = DBL_MAX; + + // Find highest high and lowest low in the period - EXACTLY like original + for(int i = 0; i < DB_BoxPeriod; i++) + { + high = MathMax(high, iHigh(dbData.symbol, PERIOD_H1, i)); + low = MathMin(low, iLow(dbData.symbol, PERIOD_H1, i)); + } + + double range = high - low; + double allowedRange = DB_BoxDeviation * dbData.point; // Use dbData.point instead of _Point + + if(DB_EnableLogging) + { + Print("DarvasBox: Box Calculation - High: ", high, " Low: ", low, " Range: ", range, " Allowed Range: ", allowedRange); + } + + // Check if box is formed - EXACTLY like original + if(range <= allowedRange) + { + dbData.boxHigh = high; + dbData.boxLow = low; + dbData.boxFormed = true; + dbData.lastBoxTime = iTime(dbData.symbol, PERIOD_CURRENT, 0); + + // Draw the box + DrawDarvasBox(); + + if(DB_EnableLogging) + Print("DarvasBox: Box Formed - High: ", dbData.boxHigh, " Low: ", dbData.boxLow, " Time: ", dbData.lastBoxTime); + } + else + { + dbData.boxFormed = false; + // Delete box if it exists + ObjectsDeleteAll(0, dbData.boxName); + } +} + +bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE orderType) +{ + double minSlDistance = MathMax(dbData.minStopLevel, DB_StopLoss * dbData.point); + double minTpDistance = MathMax(dbData.minStopLevel, DB_TakeProfit * dbData.point); + + if(orderType == ORDER_TYPE_BUY) + { + sl = price - minSlDistance; + tp = price + minTpDistance; + } + else + { + sl = price + minSlDistance; + tp = price - minTpDistance; + } + + return true; +} + +bool IsTrendFavorable(ENUM_ORDER_TYPE orderType) +{ + double ma[]; + ArraySetAsSeries(ma, true); + + if(CopyBuffer(dbData.maHandle, 0, 0, 2, ma) <= 0) + return false; + + double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK); + double trendStrength = MathAbs(currentPrice - ma[0]) / dbData.point; + + if(orderType == ORDER_TYPE_BUY) + return (currentPrice > ma[0] && trendStrength > DB_TrendThreshold); + else + return (currentPrice < ma[0] && trendStrength > DB_TrendThreshold); +} + +bool CheckVolumeConditions() +{ + double volumes[]; + ArraySetAsSeries(volumes, true); + + if(CopyBuffer(dbData.volumeHandle, 0, 0, DB_VolumeMA_Period + 1, volumes) <= 0) + return false; + + double volumeMA = 0; + for(int i = 1; i <= DB_VolumeMA_Period; i++) + volumeMA += volumes[i]; + volumeMA /= DB_VolumeMA_Period; + + double currentVolume = volumes[0]; + double volumeRatio = currentVolume / volumeMA; + + return (volumeRatio > DB_VolumeThresholdMultiplier); +} + +bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp) +{ + if(!ValidateStopLevels(price, sl, tp, orderType)) + { + if(DB_EnableLogging) + Print("DarvasBox: Order rejected - Stop levels validation failed"); + return false; + } + + if(!IsTrendFavorable(orderType)) + { + if(DB_EnableLogging) + Print("DarvasBox: Order rejected - Trend not favorable for ", EnumToString(orderType)); + return false; + } + + if(!CheckVolumeConditions()) + { + if(DB_EnableLogging) + Print("DarvasBox: Order rejected - Volume conditions not met"); + return false; + } + + bool result = false; + + // Use market price (0) instead of explicit price - this ensures market order execution + // In backtesting, explicit price might fail if price has moved + if(orderType == ORDER_TYPE_BUY) + result = dbData.trade.Buy(g_DB_LotSize, dbData.symbol, 0, sl, tp, "Darvas Box Breakout"); + else + result = dbData.trade.Sell(g_DB_LotSize, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown"); + + // Always log errors, success only if logging enabled + if(result) + { + if(DB_EnableLogging) + Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), " Order Placed Successfully"); + } + else + { + // Always log failures with detailed info + uint retcode_uint = dbData.trade.ResultRetcode(); + int retcode = (int)retcode_uint; + string desc = dbData.trade.ResultRetcodeDescription(); + ulong deal = dbData.trade.ResultDeal(); + ulong order = dbData.trade.ResultOrder(); + Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), + " Order Failed - Retcode: ", retcode, + ", Description: ", desc, + ", Deal: ", deal, + ", Order: ", order, + ", Symbol: ", dbData.symbol, + ", Requested Price: ", price, + ", SL: ", sl, + ", TP: ", tp); + } + + return result; +} + +void ProcessDarvasBox(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!dbData.isInitialized) + return; + + dbData.symbol = symbol; // Update symbol in case it changed + + // Calculate new box levels - EXACTLY like original (called every tick) + CalculateDarvasBox(); + + // Check for trading signals - EXACTLY like original (checked every tick) + if(dbData.boxFormed) + { + double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK); + long currentVolume_long = iVolume(dbData.symbol, PERIOD_CURRENT, 0); + double currentVolume = (double)currentVolume_long; + + if(DB_EnableLogging) + { + Print("DarvasBox: Current Price: ", currentPrice, " Box High: ", dbData.boxHigh, " Box Low: ", dbData.boxLow); + Print("DarvasBox: Current Volume: ", currentVolume, " Volume Threshold: ", DB_VolumeThreshold); + } + + // Check for breakout above box - EXACTLY like original + if(currentPrice > dbData.boxHigh && currentVolume > DB_VolumeThreshold) + { + if(DB_EnableLogging) + Print("DarvasBox: Breakout Signal Detected - Price above box high"); + + // Buy signal + if(!PositionExistsByMagic(dbData.symbol, (ulong)DB_MagicNumber)) // No existing positions with our magic number + { + double sl = currentPrice - DB_StopLoss * dbData.point; + double tp = currentPrice + DB_TakeProfit * dbData.point; + + if(DB_EnableLogging) + Print("DarvasBox: Preparing Buy Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp); + + PlaceOrder(ORDER_TYPE_BUY, currentPrice, sl, tp); + } + else if(DB_EnableLogging) + Print("DarvasBox: Skipping Buy Signal - Position already exists"); + } + + // Check for breakdown below box - EXACTLY like original + if(currentPrice < dbData.boxLow && currentVolume > DB_VolumeThreshold) + { + if(DB_EnableLogging) + Print("DarvasBox: Breakdown Signal Detected - Price below box low"); + + // Sell signal + if(!PositionExistsByMagic(dbData.symbol, (ulong)DB_MagicNumber)) // No existing positions with our magic number + { + double sl = currentPrice + DB_StopLoss * dbData.point; + double tp = currentPrice - DB_TakeProfit * dbData.point; + + if(DB_EnableLogging) + Print("DarvasBox: Preparing Sell Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp); + + PlaceOrder(ORDER_TYPE_SELL, currentPrice, sl, tp); + } + else if(DB_EnableLogging) + Print("DarvasBox: Skipping Sell Signal - Position already exists"); + } + } + else if(DB_EnableLogging) + Print("DarvasBox: No Box Formed - Waiting for consolidation"); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/Strategies/EMASlopeDistanceStrategy.mqh b/frontline/MQL5/_united_dynamic/Strategies/EMASlopeDistanceStrategy.mqh new file mode 100644 index 0000000..70e4502 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/Strategies/EMASlopeDistanceStrategy.mqh @@ -0,0 +1,496 @@ +//+------------------------------------------------------------------+ +//| EMASlopeDistanceStrategy.mqh | +//+------------------------------------------------------------------+ + +bool InitEMASlopeDistance(string symbol) +{ + esData.symbol = symbol; + esData.letzte_überwachung_zeit = 0; + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.steigung_trigger_aktiv = false; + esData.ticket = 0; + esData.trades_in_current_crossover = 0; + esData.crossover_detected = false; + esData.trade_open_time = 0; + esData.last_bar_time = 0; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("EMASlopeDistance: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + esData.trade.SetExpertMagicNumber(ES_MagicNumber); + esData.trade.SetDeviationInPoints(10); + esData.trade.SetTypeFilling(ORDER_FILLING_IOC); + + esData.ema_handle = iMA(symbol, ES_Timeframe, ES_EMA_Periode, 0, MODE_EMA, PRICE_CLOSE); + + if(esData.ema_handle == INVALID_HANDLE) + { + Print("EMASlopeDistance: Error creating EMA indicator for '", symbol, "'"); + return false; + } + + ArraySetAsSeries(esData.ema_array, true); + esData.isInitialized = true; + Print("EMASlopeDistance: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitEMASlopeDistance() +{ + if(esData.ema_handle != INVALID_HANDLE) + IndicatorRelease(esData.ema_handle); +} + +//+------------------------------------------------------------------+ +//| EMA Berechnung (EMA Calculation) | +//+------------------------------------------------------------------+ +void BerechneEMA() +{ + //--- EMA Werte vom Indicator kopieren (Copy EMA values from indicator) + int copied = CopyBuffer(esData.ema_handle, 0, 0, 3, esData.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]: ", esData.ema_array[0], " [1]: ", esData.ema_array[1], " [2]: ", esData.ema_array[2]); +} + +//+------------------------------------------------------------------+ +//| Trigger-Bedingungen prüfen (Check trigger conditions) | +//+------------------------------------------------------------------+ +void PrüfeTrigger() +{ + if(ArraySize(esData.ema_array) < 2) + { + Print("TRACE: Array zu klein - Größe: ", ArraySize(esData.ema_array)); + return; + } + + //--- Aktuelle Werte (Current values) + double aktueller_preis = SymbolInfoDouble(esData.symbol, SYMBOL_BID); + double aktueller_ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK); + double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0); + int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT); + double pips_multiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0; + + //--- EMA Werte in Variablen (EMA values in variables) + double ema_aktuell = esData.ema_array[0]; + double ema_vorher = esData.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) + { + esData.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: ", ES_PreisSchwelle, ")"); + Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell); + Print("TRACE: Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover); + + if(preis_abstand > ES_PreisSchwelle && !esData.preis_trigger_aktiv) + { + esData.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: ", ES_SteigungSchwelle, ")"); + + if(MathAbs(steigung) > ES_SteigungSchwelle && !esData.steigung_trigger_aktiv) + { + esData.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(esData.preis_trigger_aktiv && esData.steigung_trigger_aktiv && !esData.überwachung_aktiv) + { + esData.überwachung_aktiv = true; + + if(ES_UseBarData) + { + esData.letzte_überwachung_zeit = iTime(esData.symbol, ES_Timeframe, 0); // Aktuelle Bar-Zeit + Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Bar: ", TimeToString(esData.letzte_überwachung_zeit), ")"); + } + else + { + esData.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(esData.ü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(esData.trades_in_current_crossover >= ES_MaxTradesPerCrossover) + { + Print("TRACE: Trade-Limit erreicht (", ES_MaxTradesPerCrossover, ") - Kein neuer Trade"); + return; + } + + if(bullish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")"); + if(PlatziereTrade(ORDER_TYPE_BUY)) + { + esData.trades_in_current_crossover++; + } + } + else if(bearish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")"); + if(PlatziereTrade(ORDER_TYPE_SELL)) + { + esData.trades_in_current_crossover++; + } + } + else if(PositionExistsByMagic(esData.symbol, (ulong)ES_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: ", g_ES_LotSize); + + bool success = false; + + if(order_type == ORDER_TYPE_BUY) + { + success = esData.trade.Buy(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade"); + } + else + { + success = esData.trade.Sell(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade"); + } + + if(success) + { + esData.ticket = (int)esData.trade.ResultOrder(); + Print("TRACE: Trade erfolgreich platziert: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF", " Ticket: ", esData.ticket); + + //--- Trade-Öffnungszeit speichern (Save trade opening time) + esData.trade_open_time = iTime(esData.symbol, ES_Timeframe, 0); + Print("TRACE: Trade-Öffnungszeit: ", TimeToString(esData.trade_open_time)); + + //--- Überwachung zurücksetzen (Reset monitoring) + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.steigung_trigger_aktiv = false; + + return true; + } + else + { + Print("TRACE: Fehler beim Platzieren des Trades - Retcode: ", esData.trade.ResultRetcode()); + Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription()); + + return false; + } +} + +//+------------------------------------------------------------------+ +//| Trades verwalten (Manage trades) | +//+------------------------------------------------------------------+ +void VerwalteTrades() +{ + if(!PositionSelectByMagic(esData.symbol, (ulong)ES_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); + + int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT); + double pips_multiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0; + double trailing_stop_pips = ES_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(esData.ema_array) >= 1) + { + double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0); + double ema_aktuell = esData.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 ", esData.trades_in_current_crossover); + } + } + + //--- Profit-Prüfung nach X Bars (Profit check after X bars) + if(ES_CloseUnprofitableTrades && esData.trade_open_time != 0 && PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + Print("TRACE: Profit-Prüfung aktiviert - CloseUnprofitableTrades: ", ES_CloseUnprofitableTrades); + PrüfeProfitNachBars(); + } + else if(!ES_CloseUnprofitableTrades) + { + Print("TRACE: Profit-Prüfung deaktiviert - CloseUnprofitableTrades: ", ES_CloseUnprofitableTrades); + } +} + +//+------------------------------------------------------------------+ +//| Profit-Prüfung nach X Bars (Profit check after X bars) | +//+------------------------------------------------------------------+ +void PrüfeProfitNachBars() +{ + if(!PositionSelectByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + return; // Keine Position offen + } + + datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0); + int bars_since_trade_open = iBarShift(esData.symbol, ES_Timeframe, esData.trade_open_time); + + Print("TRACE: Bars seit Trade-Öffnung: ", bars_since_trade_open, "/", ES_ProfitCheckBars); + + //--- Prüfe ob genügend Bars vergangen sind (Check if enough bars have passed) + if(bars_since_trade_open >= ES_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 ", ES_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) + esData.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) + esData.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(esData.trade, esData.symbol, (ulong)ES_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: ", esData.trade.ResultRetcode()); + Print("TRACE: Fehlerbeschreibung: ", esData.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(esData.trade, esData.symbol, (ulong)ES_MagicNumber); + + if(success) + { + Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason); + } + else + { + Print("TRACE: Fehler beim Schließen der Position - Retcode: ", esData.trade.ResultRetcode()); + Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void ProcessEMASlopeDistance(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!esData.isInitialized) + return; + + esData.symbol = symbol; // Update symbol in case it changed + + //--- Bar-Daten oder Tick-Daten verwenden (Use bar data or tick data) + if(ES_UseBarData) + { + //--- Nur bei neuen Bars ausführen (Only execute on new bars) + datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0); + + if(current_bar_time == esData.last_bar_time) + { + return; // Kein neuer Bar, nichts tun + } + + esData.last_bar_time = current_bar_time; + } + + //--- EMA Werte berechnen (Calculate EMA values) + BerechneEMA(); + + //--- Debug: Aktuelle Werte ausgeben (Debug: Output current values) + if(ArraySize(esData.ema_array) > 0) + { + double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0); + double ema_aktuell = esData.ema_array[0]; + double ema_vorher = esData.ema_array[1]; + int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT); + double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / point; + double steigung = (ema_aktuell - ema_vorher) / point; + + if(ES_UseBarData) + { + Print("=== DEBUG INFO (Neuer Bar) ==="); + Print("Bar Zeit: ", TimeToString(iTime(esData.symbol, ES_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: ", esData.preis_trigger_aktiv, " Steigungs-Trigger: ", esData.steigung_trigger_aktiv); + Print("Überwachung aktiv: ", esData.überwachung_aktiv); + Print("Position offen: ", PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)); + Print("Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover); + Print("=================="); + } + + //--- Überwachung prüfen (Check monitoring) + if(esData.überwachung_aktiv) + { + if(ES_UseBarData) + { + // Bar-basierte Überwachungszeit + int bars_since_monitoring = iBarShift(esData.symbol, ES_Timeframe, esData.letzte_überwachung_zeit); + int timeout_bars = (int)(ES_ÜberwachungTimeout / PeriodSeconds(ES_Timeframe)); + + if(bars_since_monitoring > timeout_bars) + { + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.steigung_trigger_aktiv = false; + Print("Überwachung beendet - Bar-basierte Zeitüberschreitung (", bars_since_monitoring, " Bars)"); + } + } + else + { + // Tick-basierte Überwachungszeit + if(TimeCurrent() - esData.letzte_überwachung_zeit > ES_ÜberwachungTimeout) + { + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.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(); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/Strategies/RSICrossOverReversalStrategy.mqh b/frontline/MQL5/_united_dynamic/Strategies/RSICrossOverReversalStrategy.mqh new file mode 100644 index 0000000..f182476 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/Strategies/RSICrossOverReversalStrategy.mqh @@ -0,0 +1,240 @@ +//+------------------------------------------------------------------+ +//| RSICrossOverReversalStrategy.mqh | +//+------------------------------------------------------------------+ + +void WeekDays_Init() +{ + rcData.WeekDays[0] = RC_Sunday; + rcData.WeekDays[1] = RC_Monday; + rcData.WeekDays[2] = RC_Tuesday; + rcData.WeekDays[3] = RC_Wednesday; + rcData.WeekDays[4] = RC_Thursday; + rcData.WeekDays[5] = RC_Friday; + rcData.WeekDays[6] = RC_Saturday; +} + +bool WeekDays_Check(datetime aTime) +{ + MqlDateTime stm; + TimeToStruct(aTime, stm); + return(rcData.WeekDays[stm.day_of_week]); +} + +int TimeHour(datetime when = 0) +{ + if(when == 0) when = TimeCurrent(); + MqlDateTime dt; + TimeToStruct(when, dt); + return dt.hour; +} + +bool InitRSICrossOverReversal(string symbol) +{ + WeekDays_Init(); + + rcData.symbol = symbol; + rcData.previousRSIDef = 0; + rcData.lastTradeTime = 0; + rcData.bartime = 0; + rcData.lastBarTime = 0; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSICrossOverReversal: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + rcData.rsiHandle = iRSI(symbol, RC_TimeFrame1, RC_rsiPeriod, PRICE_CLOSE); + if(rcData.rsiHandle == INVALID_HANDLE) + { + Print("RSICrossOverReversal: Error creating RSI handle for '", symbol, "'"); + return false; + } + + rcData.emaHandle = iMA(symbol, RC_TimeFrame2, RC_emaPeriod, 0, MODE_EMA, PRICE_CLOSE); + if(rcData.emaHandle == INVALID_HANDLE) + { + Print("RSICrossOverReversal: Error creating EMA handle for '", symbol, "'"); + return false; + } + + rcData.trade.SetExpertMagicNumber(RC_MagicNumber); + rcData.isInitialized = true; + Print("RSICrossOverReversal: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitRSICrossOverReversal() +{ + if(rcData.rsiHandle != INVALID_HANDLE) + IndicatorRelease(rcData.rsiHandle); + if(rcData.emaHandle != INVALID_HANDLE) + IndicatorRelease(rcData.emaHandle); +} + +void Close_Position_MN(ulong magicNumber) +{ + ClosePositionByMagic(rcData.trade, rcData.symbol, (int)magicNumber); +} + +void ApplyTrailingStop() +{ + if(!PositionSelectByMagic(rcData.symbol, RC_MagicNumber)) + return; + + ulong PositionTicket = PositionGetInteger(POSITION_TICKET); + ENUM_POSITION_TYPE trade_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + string symbol = rcData.symbol; + + double POINT = SymbolInfoDouble(symbol, SYMBOL_POINT); + int DIGIT = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + + if(trade_type == POSITION_TYPE_BUY) + { + double Bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), DIGIT); + + if(Bid - PositionGetDouble(POSITION_PRICE_OPEN) > NormalizeDouble(POINT * RC_TrailingStop, DIGIT)) + { + if(PositionGetDouble(POSITION_SL) < NormalizeDouble(Bid - POINT * RC_TrailingStop, DIGIT)) + { + ModifyPositionByMagic(rcData.trade, symbol, RC_MagicNumber, + NormalizeDouble(Bid - POINT * RC_TrailingStop, DIGIT), + PositionGetDouble(POSITION_TP)); + } + } + } + else if(trade_type == POSITION_TYPE_SELL) + { + double Ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), DIGIT); + + if((PositionGetDouble(POSITION_PRICE_OPEN) - Ask) > NormalizeDouble(POINT * RC_TrailingStop, DIGIT)) + { + if((PositionGetDouble(POSITION_SL) > NormalizeDouble(Ask + POINT * RC_TrailingStop, DIGIT)) || + (PositionGetDouble(POSITION_SL) == 0)) + { + ModifyPositionByMagic(rcData.trade, symbol, RC_MagicNumber, + NormalizeDouble(Ask + POINT * RC_TrailingStop, DIGIT), + PositionGetDouble(POSITION_TP)); + } + } + } +} + +void ProcessRSICrossOverReversal(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!rcData.isInitialized) + return; + + rcData.symbol = symbol; // Update symbol in case it changed + if(rcData.bartime == iTime(rcData.symbol, RC_BarTimeFrame, 0)) + return; + rcData.bartime = iTime(rcData.symbol, RC_BarTimeFrame, 0); + + double rsi[]; + if(CopyBuffer(rcData.rsiHandle, 0, 0, 2, rsi) <= 0) + return; + + double ema[]; + if(CopyBuffer(rcData.emaHandle, 0, 0, 2, ema) <= 0) + return; + + datetime currentTime = TimeCurrent(); + int currentHour = TimeHour(TimeCurrent()); + + if(!WeekDays_Check(TimeTradeServer())) + { + Close_Position_MN(RC_MagicNumber); + return; + } + + if(!((currentHour < RC_tradingHourOneEnd && currentHour > RC_tradingHourOneBegin) || + (currentHour < RC_tradingHourTwoEnd && currentHour > RC_tradingHourTwoBegin))) + { + Close_Position_MN(RC_MagicNumber); + return; + } + + bool hasPosition = PositionExistsByMagic(rcData.symbol, RC_MagicNumber); + + double currentRSI = rsi[0]; + double previousRSI = rsi[1]; + + if(rcData.previousRSIDef == 0) + { + rcData.previousRSIDef = currentRSI; + return; + } + + double currentEMA = ema[0]; + double previousEMA = ema[1]; + + double emaSlope = (currentEMA - previousEMA) * 100; + double closeCurr = iClose(Symbol(), Period(), 0); + double priceToEmaDistance = (closeCurr - currentEMA) * 10; + + bool isBuyPosition = false; + bool isSellPosition = false; + if(hasPosition) + { + if(PositionSelectByMagic(rcData.symbol, RC_MagicNumber)) + { + ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if(positionType == POSITION_TYPE_BUY) + isBuyPosition = true; + else if(positionType == POSITION_TYPE_SELL) + isSellPosition = true; + } + } + + ApplyTrailingStop(); + + bool cooldownPassed = (currentTime - rcData.lastTradeTime) >= RC_cooldownSeconds; + bool isTrendStrong = MathAbs(emaSlope) > RC_emaSlopeThreshold || MathAbs(priceToEmaDistance) > RC_emaDistanceThreshold; + + if(isBuyPosition && currentRSI > RC_exitBuyRSI) + { + Close_Position_MN(RC_MagicNumber); + rcData.lastTradeTime = currentTime; + } + + if(isSellPosition && currentRSI < RC_exitSellRSI) + { + Close_Position_MN(RC_MagicNumber); + rcData.lastTradeTime = currentTime; + } + + if(isTrendStrong) + { + Close_Position_MN(RC_MagicNumber); + rcData.lastTradeTime = currentTime; + return; + } + + if(currentRSI < RC_overboughtLevel - RC_entryRSISellSpread && rcData.previousRSIDef >= RC_overboughtLevel && + !isSellPosition && !hasPosition && cooldownPassed) + { + rcData.trade.SetExpertMagicNumber(RC_MagicNumber); + if(rcData.trade.Sell(g_RC_LotSize, rcData.symbol, 0.0, 0.0, 0.0, "Sell Order")) + { + rcData.lastTradeTime = currentTime; + } + } + + if(currentRSI > RC_oversoldLevel + RC_entryRSIBuySpread && rcData.previousRSIDef <= RC_oversoldLevel && + !isBuyPosition && !hasPosition && cooldownPassed) + { + rcData.trade.SetExpertMagicNumber(RC_MagicNumber); + if(rcData.trade.Buy(g_RC_LotSize, rcData.symbol, 0.0, 0.0, 0.0, "Buy Order")) + { + rcData.lastTradeTime = currentTime; + } + } + + rcData.previousRSIDef = currentRSI; +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/Strategies/RSIMidPointHijackStrategy.mqh b/frontline/MQL5/_united_dynamic/Strategies/RSIMidPointHijackStrategy.mqh new file mode 100644 index 0000000..3db8db5 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/Strategies/RSIMidPointHijackStrategy.mqh @@ -0,0 +1,471 @@ +//+------------------------------------------------------------------+ +//| RSIMidPointHijackStrategy.mqh | +//+------------------------------------------------------------------+ + +bool IsNewBar(string symbol) +{ + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + { + if(time[0] != rmData.lastBarTime) + { + rmData.lastBarTime = time[0]; + return true; + } + } + return false; +} + +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); +} + +bool HasPosition(string symbol, int magic) +{ + return PositionExistsByMagic(symbol, magic); +} + +bool HasProfitablePosition(int excludeMagic) +{ + bool hasProfitable = false; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(rmData.positionInfo.SelectByIndex(i)) + { + if(rmData.positionInfo.Magic() != excludeMagic) + { + double profit = rmData.positionInfo.Profit(); + if(profit > RM_InpLockProfitThreshold * _Point) + { + hasProfitable = true; + if(RM_InpCloseOppositeTrades) + { + if((excludeMagic == RM_InpMagicNumberRSIFollow && rmData.positionInfo.Magic() == RM_InpMagicNumberRSIReverse) || + (excludeMagic == RM_InpMagicNumberRSIReverse && rmData.positionInfo.Magic() == RM_InpMagicNumberRSIFollow) || + (excludeMagic == RM_InpMagicNumberEMACross && (rmData.positionInfo.Magic() == RM_InpMagicNumberRSIReverse || rmData.positionInfo.Magic() == RM_InpMagicNumberRSIFollow)) || + ((excludeMagic == RM_InpMagicNumberRSIFollow || excludeMagic == RM_InpMagicNumberRSIReverse) && rmData.positionInfo.Magic() == RM_InpMagicNumberEMACross)) + { + ClosePosition(rmData.symbol, (int)rmData.positionInfo.Magic()); + } + } + } + } + } + } + return hasProfitable; +} + +bool IsRSIReverseInCooldown(string symbol) +{ + if(RM_InpRSIReverseCooldownBars <= 0) + return false; + + if(!rmData.rsiReverseInCooldown) + return false; + + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + { + datetime currentBarTime = time[0]; + datetime cooldownEndTime = rmData.rsiReverseLastCloseTime + RM_InpRSIReverseCooldownBars * PeriodSeconds(RM_InpTimeframe); + + if(currentBarTime >= cooldownEndTime) + { + rmData.rsiReverseInCooldown = false; + return false; + } + } + + return true; +} + +void CheckRSIFollowStrategy(string symbol) +{ + if(!IsWithinTradingHours(RM_InpRSIFollowStartHour, RM_InpRSIFollowEndHour)) + { + if(RM_InpRSIFollowCloseOutsideHours) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + ClosePosition(symbol, RM_InpMagicNumberRSIFollow); + } + return; + } + + if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberRSIFollow)) + return; + + if(rmData.lastBarRSI > RM_InpRSIOverbought) + rmData.rsiOverbought = true; + else if(rmData.lastBarRSI < RM_InpRSIOversold) + rmData.rsiOversold = true; + + if(rmData.rsiOverbought && rmData.lastBarRSI < RM_InpRSIExitLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow"); + } + rmData.rsiOverbought = false; + } + else if(rmData.rsiOversold && rmData.lastBarRSI > RM_InpRSIExitLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow"); + } + rmData.rsiOversold = false; + } +} + +void CheckRSIReverseStrategy(string symbol) +{ + if(!IsWithinTradingHours(RM_InpRSIReverseStartHour, RM_InpRSIReverseEndHour)) + { + if(RM_InpRSIReverseCloseOutsideHours) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + ClosePosition(symbol, RM_InpMagicNumberRSIReverse); + } + return; + } + + if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberRSIReverse)) + return; + + if(IsRSIReverseInCooldown(symbol)) + return; + + if(rmData.lastBarRSIReverse > RM_InpRSIReverseOverbought) + rmData.rsiReverseOverbought = true; + else if(rmData.lastBarRSIReverse < RM_InpRSIReverseOversold) + rmData.rsiReverseOversold = true; + + if(rmData.rsiReverseOverbought && rmData.lastBarRSIReverse < RM_InpRSIReverseCrossLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse"); + } + rmData.rsiReverseOverbought = false; + } + else if(rmData.rsiReverseOversold && rmData.lastBarRSIReverse > RM_InpRSIReverseCrossLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse"); + } + rmData.rsiReverseOversold = false; + } +} + +void CheckEMACrossStrategy(string symbol) +{ + if(!IsWithinTradingHours(RM_InpEMACrossStartHour, RM_InpEMACrossEndHour)) + { + if(RM_InpEMACrossCloseOutsideHours) + { + if(HasPosition(symbol, RM_InpMagicNumberEMACross)) + ClosePosition(symbol, RM_InpMagicNumberEMACross); + } + return; + } + + if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberEMACross)) + return; + + if(rmData.lastBarEMAPrev < rmData.lastBarClosePrev && rmData.lastBarEMA > rmData.lastBarClose) + { + rmData.emaCrossBuySignal = true; + rmData.emaCrossSellSignal = false; + rmData.emaCrossSignalBar = 0; + } + else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose) + { + rmData.emaCrossSellSignal = true; + rmData.emaCrossBuySignal = false; + rmData.emaCrossSignalBar = 0; + } + + if(RM_InpUseEMADistanceEntry) + { + if(rmData.emaCrossBuySignal) + { + bool distanceConditionMet = true; + double emaHistory[], closeHistory[]; + ArraySetAsSeries(emaHistory, true); + ArraySetAsSeries(closeHistory, true); + + if(CopyBuffer(rmData.emaHandle, 0, 0, RM_InpEMADistancePeriod, emaHistory) > 0 && + CopyClose(symbol, RM_InpTimeframe, 0, RM_InpEMADistancePeriod, closeHistory) > 0) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + for(int i = 0; i < RM_InpEMADistancePeriod; i++) + { + double distance = (closeHistory[i] - emaHistory[i]) / point; + if(distance < RM_InpEMADistancePips) + { + distanceConditionMet = false; + break; + } + } + + if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance"); + rmData.emaCrossBuySignal = false; + } + } + } + else if(rmData.emaCrossSellSignal) + { + bool distanceConditionMet = true; + double emaHistory[], closeHistory[]; + ArraySetAsSeries(emaHistory, true); + ArraySetAsSeries(closeHistory, true); + + if(CopyBuffer(rmData.emaHandle, 0, 0, RM_InpEMADistancePeriod, emaHistory) > 0 && + CopyClose(symbol, RM_InpTimeframe, 0, RM_InpEMADistancePeriod, closeHistory) > 0) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + for(int i = 0; i < RM_InpEMADistancePeriod; i++) + { + double distance = (emaHistory[i] - closeHistory[i]) / point; + if(distance < RM_InpEMADistancePips) + { + distanceConditionMet = false; + break; + } + } + + if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance"); + rmData.emaCrossSellSignal = false; + } + } + } + } + else + { + if(rmData.lastBarEMAPrev < rmData.lastBarClosePrev && rmData.lastBarEMA > rmData.lastBarClose) + { + if(!HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross"); + } + } + else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose) + { + if(!HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross"); + } + } + } + + if(rmData.emaCrossBuySignal || rmData.emaCrossSellSignal) + { + rmData.emaCrossSignalBar++; + if(rmData.emaCrossSignalBar > RM_InpEMADistancePeriod * 2) + { + rmData.emaCrossBuySignal = false; + rmData.emaCrossSellSignal = false; + } + } +} + +void CheckExitConditions(string symbol) +{ + if(RM_InpEnableRSIFollow) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + { + if(PositionSelectByMagic(symbol, RM_InpMagicNumberRSIFollow)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if((posType == POSITION_TYPE_BUY && rmData.lastBarRSI < RM_InpRSIExitLevel) || + (posType == POSITION_TYPE_SELL && rmData.lastBarRSI > RM_InpRSIExitLevel)) + { + ClosePosition(symbol, RM_InpMagicNumberRSIFollow); + } + } + } + } + + if(RM_InpEnableRSIReverse) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + { + if(PositionSelectByMagic(symbol, RM_InpMagicNumberRSIReverse)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if((posType == POSITION_TYPE_BUY && rmData.lastBarRSIReverse < RM_InpRSIReverseExitLevel) || + (posType == POSITION_TYPE_SELL && rmData.lastBarRSIReverse > RM_InpRSIReverseExitLevel)) + { + ClosePosition(symbol, RM_InpMagicNumberRSIReverse); + } + } + } + } + + if(RM_InpEnableEMACross) + { + if(HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + if(PositionSelectByMagic(symbol, RM_InpMagicNumberEMACross)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if((posType == POSITION_TYPE_BUY && rmData.lastBarEMA > rmData.lastBarClose) || + (posType == POSITION_TYPE_SELL && rmData.lastBarEMA < rmData.lastBarClose)) + { + ClosePosition(symbol, RM_InpMagicNumberEMACross); + } + } + } + } +} + +void ClosePosition(string symbol, int magic) +{ + if(!PositionExistsByMagic(symbol, magic)) + return; + + ulong ticket = GetPositionTicketByMagic(symbol, magic); + if(ticket == 0) + return; + + if(magic == RM_InpMagicNumberRSIReverse) + { + if(PositionSelectByTicketSymbolAndMagic(ticket, symbol, magic)) + { + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + { + rmData.rsiReverseLastCloseTime = time[0]; + double profit = PositionGetDouble(POSITION_PROFIT); + if(!RM_InpRSIReverseCooldownOnLoss || profit < 0) + { + rmData.rsiReverseInCooldown = true; + } + } + } + } + + ClosePositionByMagic(rmData.trade, symbol, magic); +} + +bool InitRSIMidPointHijack(string symbol) +{ + rmData.symbol = symbol; + rmData.rsiOverbought = false; + rmData.rsiOversold = false; + rmData.rsiReverseOverbought = false; + rmData.rsiReverseOversold = false; + rmData.emaCrossBuySignal = false; + rmData.emaCrossSellSignal = false; + rmData.emaCrossSignalBar = 0; + rmData.rsiReverseInCooldown = false; + rmData.lastBarRSI = 0; + rmData.lastBarRSIReverse = 0; + rmData.lastBarEMA = 0; + rmData.lastBarClose = 0; + rmData.lastBarEMAPrev = 0; + rmData.lastBarClosePrev = 0; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSIMidPointHijack: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + rmData.rsiHandle = iRSI(symbol, RM_InpTimeframe, RM_InpRSIPeriod, PRICE_CLOSE); + rmData.rsiReverseHandle = iRSI(symbol, RM_InpTimeframe, RM_InpRSIReversePeriod, PRICE_CLOSE); + rmData.emaHandle = iMA(symbol, RM_InpTimeframe, RM_InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE); + + if(rmData.rsiHandle == INVALID_HANDLE || rmData.rsiReverseHandle == INVALID_HANDLE || rmData.emaHandle == INVALID_HANDLE) + { + Print("RSIMidPointHijack: Error creating indicators for '", symbol, "'"); + return false; + } + + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow); + rmData.trade.SetMarginMode(); + rmData.trade.SetTypeFillingBySymbol(symbol); + rmData.trade.SetDeviationInPoints(10); + + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + rmData.lastBarTime = time[0]; + + rmData.isInitialized = true; + Print("RSIMidPointHijack: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitRSIMidPointHijack() +{ + if(rmData.rsiHandle != INVALID_HANDLE) IndicatorRelease(rmData.rsiHandle); + if(rmData.rsiReverseHandle != INVALID_HANDLE) IndicatorRelease(rmData.rsiReverseHandle); + if(rmData.emaHandle != INVALID_HANDLE) IndicatorRelease(rmData.emaHandle); +} + +void ProcessRSIMidPointHijack(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!rmData.isInitialized) + return; + + rmData.symbol = symbol; // Update symbol in case it changed + if(!IsNewBar(rmData.symbol)) + return; + + double rsi[], rsiReverse[], ema[], close[]; + ArraySetAsSeries(rsi, true); + ArraySetAsSeries(rsiReverse, true); + ArraySetAsSeries(ema, true); + ArraySetAsSeries(close, true); + + rmData.lastBarEMAPrev = rmData.lastBarEMA; + rmData.lastBarClosePrev = rmData.lastBarClose; + + if(CopyBuffer(rmData.rsiHandle, 0, 0, 1, rsi) > 0) + rmData.lastBarRSI = rsi[0]; + + if(CopyBuffer(rmData.rsiReverseHandle, 0, 0, 1, rsiReverse) > 0) + rmData.lastBarRSIReverse = rsiReverse[0]; + + if(CopyBuffer(rmData.emaHandle, 0, 0, 1, ema) > 0) + rmData.lastBarEMA = ema[0]; + + if(CopyClose(rmData.symbol, RM_InpTimeframe, 0, 1, close) > 0) + rmData.lastBarClose = close[0]; + + if(RM_InpEnableRSIFollow) + CheckRSIFollowStrategy(rmData.symbol); + if(RM_InpEnableRSIReverse) + CheckRSIReverseStrategy(rmData.symbol); + if(RM_InpEnableEMACross) + CheckEMACrossStrategy(rmData.symbol); + + CheckExitConditions(rmData.symbol); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/Strategies/RSIReversalAsianStrategy.mqh b/frontline/MQL5/_united_dynamic/Strategies/RSIReversalAsianStrategy.mqh new file mode 100644 index 0000000..6762248 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/Strategies/RSIReversalAsianStrategy.mqh @@ -0,0 +1,493 @@ +//+------------------------------------------------------------------+ +//| RSIReversalAsianStrategy.mqh | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| RSI Reversal Asian Strategy Data Structure | +//+------------------------------------------------------------------+ +struct RSIReversalAsianData { + string symbol; + bool isInitialized; + int rsiHandle; + CTrade trade; + bool isPositionOpen; + double positionOpenPrice; + datetime positionOpenTime; + ENUM_POSITION_TYPE lastPositionType; + bool sessionCloseAttempted; + + // RSI crossover variables + double rsiCurrent; + double rsiPrevious; + double rsiPrevious2; + bool rsiCrossedOverbought; + bool rsiCrossedOversold; + bool rsiCrossedExitLevel; + + // Strategy parameters + int RSIPeriod; + double OverboughtLevel; + double OversoldLevel; + int TakeProfitPips; + int StopLossPips; + double MaxLotSize; + int MaxSpread; + int MaxDuration; + bool UseStopLoss; + bool UseTakeProfit; + bool UseRSIExit; + double RSIExitLevel; + bool CloseOutsideSession; + ENUM_TIMEFRAMES TimeFrame; + int MagicNumber; + int Slippage; + double point; +}; + +// Session times (UTC) +const int AsianSessionStart = 0; // 00:00 UTC +const int AsianSessionEnd = 8; // 08:00 UTC + +//+------------------------------------------------------------------+ +//| Check if current time is in Asian session | +//+------------------------------------------------------------------+ +bool IsAsianSession() +{ + datetime currentTime = TimeCurrent(); + MqlDateTime timeStruct; + TimeToStruct(currentTime, timeStruct); + + return (timeStruct.hour >= AsianSessionStart && timeStruct.hour < AsianSessionEnd); +} + +//+------------------------------------------------------------------+ +//| Check if trading is allowed for symbol | +//+------------------------------------------------------------------+ +bool IsTradingAllowed(RSIReversalAsianData& data) +{ + // Check if market is open + long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE); + if(tradeMode != SYMBOL_TRADE_MODE_FULL) + { + return false; + } + + // Check if we have enough money + if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0) + { + return false; + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check RSI crossover conditions | +//+------------------------------------------------------------------+ +void CheckRSICrossover(RSIReversalAsianData& data) +{ + // Reset crossover flags + data.rsiCrossedOverbought = false; + data.rsiCrossedOversold = false; + data.rsiCrossedExitLevel = false; + + // Check for overbought crossover (RSI crosses above overbought level) + if(data.rsiPrevious < data.OverboughtLevel && data.rsiCurrent >= data.OverboughtLevel) + { + data.rsiCrossedOverbought = true; + } + + // Check for oversold crossover (RSI crosses below oversold level) + if(data.rsiPrevious > data.OversoldLevel && data.rsiCurrent <= data.OversoldLevel) + { + data.rsiCrossedOversold = true; + } + + // Check for exit level crossover + if(data.rsiPrevious < data.RSIExitLevel && data.rsiCurrent >= data.RSIExitLevel) + { + data.rsiCrossedExitLevel = true; + } + else if(data.rsiPrevious > data.RSIExitLevel && data.rsiCurrent <= data.RSIExitLevel) + { + data.rsiCrossedExitLevel = true; + } +} + +//+------------------------------------------------------------------+ +//| Close all trades for the symbol | +//+------------------------------------------------------------------+ +bool CloseAllTrades(RSIReversalAsianData& data, string reason = "") +{ + bool allClosed = true; + int totalPositions = PositionsTotal(); + + if(totalPositions == 0) + return true; + + for(int i = totalPositions - 1; i >= 0; i--) + { + if(PositionGetSymbol(i) == data.symbol) + { + ulong ticket = PositionGetTicket(i); + if(ticket > 0 && PositionSelectByTicket(ticket)) + { + if(PositionGetInteger(POSITION_MAGIC) == (ulong)data.MagicNumber) + { + // Try to close position with retry logic + int retryCount = 0; + bool positionClosed = false; + + while(retryCount < 3 && !positionClosed) + { + if(data.trade.PositionClose(ticket)) + { + data.isPositionOpen = false; + positionClosed = true; + } + else + { + int error = GetLastError(); + + // If error is 4756 (Trade disabled), wait longer before retry + if(error == 4756) + { + Sleep(5000); // Wait 5 seconds before retry + retryCount++; + } + else + { + // For other errors, break the loop + break; + } + } + } + + if(!positionClosed) + { + allClosed = false; + } + } + } + } + } + + return allClosed; +} + +//+------------------------------------------------------------------+ +//| Initialize RSI Reversal Asian Strategy | +//+------------------------------------------------------------------+ +bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol, + int RSIPeriod, double OverboughtLevel, double OversoldLevel, + int TakeProfitPips, int StopLossPips, double MaxLotSize, + int MaxSpread, int MaxDuration, bool UseStopLoss, + bool UseTakeProfit, bool UseRSIExit, double RSIExitLevel, + bool CloseOutsideSession, ENUM_TIMEFRAMES TimeFrame, + int MagicNumber, int Slippage) +{ + data.symbol = symbol; + data.isInitialized = false; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSIReversalAsian: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + // Wait a bit for symbol to be ready + Sleep(100); + + // Get symbol point + data.point = SymbolInfoDouble(symbol, SYMBOL_POINT); + + // Store parameters + data.RSIPeriod = RSIPeriod; + data.OverboughtLevel = OverboughtLevel; + data.OversoldLevel = OversoldLevel; + data.TakeProfitPips = TakeProfitPips; + data.StopLossPips = StopLossPips; + data.MaxLotSize = MaxLotSize; + data.MaxSpread = MaxSpread; + data.MaxDuration = MaxDuration; + data.UseStopLoss = UseStopLoss; + data.UseTakeProfit = UseTakeProfit; + data.UseRSIExit = UseRSIExit; + data.RSIExitLevel = RSIExitLevel; + data.CloseOutsideSession = CloseOutsideSession; + data.TimeFrame = TimeFrame; + data.MagicNumber = MagicNumber; + data.Slippage = Slippage; + + // Initialize RSI indicator with retry logic (for insufficient history in backtesting) + data.rsiHandle = INVALID_HANDLE; + int retryCount = 0; + int maxRetries = 5; + + while(retryCount < maxRetries && data.rsiHandle == INVALID_HANDLE) + { + data.rsiHandle = iRSI(symbol, TimeFrame, RSIPeriod, PRICE_CLOSE); + + if(data.rsiHandle == INVALID_HANDLE) + { + int error = GetLastError(); + + // Error 4805 = insufficient history - wait longer and retry + if(error == 4805 && retryCount < maxRetries - 1) + { + Sleep(1000); // Wait 1 second for history to load + retryCount++; + continue; + } + + Print("RSIReversalAsian: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", error == 4805 ? "Insufficient history data" : "Unknown", ")"); + return false; + } + } + + if(data.rsiHandle == INVALID_HANDLE) + { + Print("RSIReversalAsian: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries"); + return false; + } + + // Wait a bit for the indicator to be ready + Sleep(100); + + // Initialize RSI values with retry logic + double rsi[]; + ArraySetAsSeries(rsi, true); + + retryCount = 0; + bool rsiInitialized = false; + + while(retryCount < 10 && !rsiInitialized) + { + int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi); + if(copied >= 3) + { + data.rsiCurrent = rsi[0]; + data.rsiPrevious = rsi[1]; + data.rsiPrevious2 = rsi[2]; + rsiInitialized = true; + } + else + { + retryCount++; + Sleep(100); + } + } + + if(!rsiInitialized) + { + // Don't fail initialization, just set default values + data.rsiCurrent = 50.0; + data.rsiPrevious = 50.0; + data.rsiPrevious2 = 50.0; + } + + // Set trade parameters + data.trade.SetExpertMagicNumber(MagicNumber); + data.trade.SetDeviationInPoints(Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_IOC); + + // Initialize state + data.isPositionOpen = false; + data.positionOpenPrice = 0; + data.positionOpenTime = 0; + data.lastPositionType = POSITION_TYPE_BUY; + data.sessionCloseAttempted = false; + data.rsiCrossedOverbought = false; + data.rsiCrossedOversold = false; + data.rsiCrossedExitLevel = false; + + data.isInitialized = true; + + Print("RSIReversalAsian: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +//+------------------------------------------------------------------+ +//| Deinitialize RSI Reversal Asian Strategy | +//+------------------------------------------------------------------+ +void DeinitRSIReversalAsian(RSIReversalAsianData& data) +{ + if(data.rsiHandle != INVALID_HANDLE) + IndicatorRelease(data.rsiHandle); +} + +//+------------------------------------------------------------------+ +//| Process RSI Reversal Asian Strategy | +//+------------------------------------------------------------------+ +void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize) +{ + if(!data.isInitialized) + return; + + // Check if trading is allowed + if(!IsTradingAllowed(data)) + { + return; + } + + // Check if we're in Asian session + if(!IsAsianSession()) + { + // Close all positions if outside Asian session and CloseOutsideSession is true + if(data.CloseOutsideSession && !data.sessionCloseAttempted) + { + CloseAllTrades(data, "Outside Asian session"); + data.sessionCloseAttempted = true; + } + return; + } + else + { + // Reset the session close attempt flag when we enter Asian session + data.sessionCloseAttempted = false; + } + + // Get current spread + double spread = SymbolInfoDouble(data.symbol, SYMBOL_ASK) - SymbolInfoDouble(data.symbol, SYMBOL_BID); + int spreadInPips = (int)(spread / data.point); + + // Check if spread is too high + if(spreadInPips > data.MaxSpread) + { + return; + } + + // Get RSI values from bar data + double rsi[]; + ArraySetAsSeries(rsi, true); + + int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi); + if(copied < 3) + { + return; + } + + // Update RSI values + data.rsiPrevious2 = data.rsiPrevious; + data.rsiPrevious = data.rsiCurrent; + data.rsiCurrent = rsi[0]; + + // Validate RSI values + if(data.rsiCurrent == 0 || data.rsiPrevious == 0) + { + return; + } + + // Check for RSI crossovers + CheckRSICrossover(data); + + // Get current prices + double currentBid = SymbolInfoDouble(data.symbol, SYMBOL_BID); + double currentAsk = SymbolInfoDouble(data.symbol, SYMBOL_ASK); + + // Check for open position + bool hasOpenPosition = PositionExistsByMagic(data.symbol, (ulong)data.MagicNumber); + + if(hasOpenPosition) + { + // Get position details + ulong ticket = GetPositionTicketByMagic(data.symbol, (ulong)data.MagicNumber); + if(ticket > 0 && PositionSelectByTicket(ticket)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + datetime openTime = (datetime)PositionGetInteger(POSITION_TIME); + + // Check for RSI exit if enabled + if(data.UseRSIExit && data.rsiCrossedExitLevel) + { + bool shouldExit = false; + + // For long positions, exit when RSI crosses above exit level + if(posType == POSITION_TYPE_BUY && data.rsiCurrent >= data.RSIExitLevel && data.rsiPrevious < data.RSIExitLevel) + { + shouldExit = true; + } + // For short positions, exit when RSI crosses below exit level + else if(posType == POSITION_TYPE_SELL && data.rsiCurrent <= data.RSIExitLevel && data.rsiPrevious > data.RSIExitLevel) + { + shouldExit = true; + } + + if(shouldExit) + { + CloseAllTrades(data, "RSI Exit Crossover"); + return; + } + } + + // Check for timeout + if(TimeCurrent() - openTime > data.MaxDuration * 3600) + { + CloseAllTrades(data, "Timeout"); + return; + } + } + } + + // If no position is open, look for entry signals based on RSI crossover + if(!hasOpenPosition) + { + // Place buy order if RSI crosses below oversold level (oversold crossover) + if(data.rsiCrossedOversold) + { + double sl = data.UseStopLoss ? currentBid - data.StopLossPips * data.point : 0; + double tp = data.UseTakeProfit ? currentBid + data.TakeProfitPips * data.point : 0; + + if(data.UseStopLoss && sl >= currentBid) + return; + if(data.UseTakeProfit && tp <= currentBid) + return; + + // Set trade parameters + data.trade.SetDeviationInPoints(data.Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_IOC); + data.trade.SetExpertMagicNumber(data.MagicNumber); + + // Use dynamic lot size + double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize; + + // Place buy order using CTrade + if(data.trade.Buy(tradeLotSize, data.symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy")) + { + data.isPositionOpen = true; + data.positionOpenPrice = currentAsk; + data.positionOpenTime = TimeCurrent(); + data.lastPositionType = POSITION_TYPE_BUY; + } + } + // Place sell order if RSI crosses above overbought level (overbought crossover) + else if(data.rsiCrossedOverbought) + { + double sl = data.UseStopLoss ? currentAsk + data.StopLossPips * data.point : 0; + double tp = data.UseTakeProfit ? currentAsk - data.TakeProfitPips * data.point : 0; + + if(data.UseStopLoss && sl <= currentAsk) + return; + if(data.UseTakeProfit && tp >= currentAsk) + return; + + // Set trade parameters + data.trade.SetDeviationInPoints(data.Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_IOC); + data.trade.SetExpertMagicNumber(data.MagicNumber); + + // Use dynamic lot size + double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize; + + // Place sell order using CTrade + if(data.trade.Sell(tradeLotSize, data.symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell")) + { + data.isPositionOpen = true; + data.positionOpenPrice = currentBid; + data.positionOpenTime = TimeCurrent(); + data.lastPositionType = POSITION_TYPE_SELL; + } + } + } +} diff --git a/frontline/MQL5/_united_dynamic/Strategies/RSIScalpingStrategy.mqh b/frontline/MQL5/_united_dynamic/Strategies/RSIScalpingStrategy.mqh new file mode 100644 index 0000000..666dc1d --- /dev/null +++ b/frontline/MQL5/_united_dynamic/Strategies/RSIScalpingStrategy.mqh @@ -0,0 +1,451 @@ +//+------------------------------------------------------------------+ +//| RSIScalpingStrategy.mqh | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| RSI Scalping Strategy Data Structure | +//+------------------------------------------------------------------+ +struct RSIScalpingData { + string symbol; + bool isInitialized; + CTrade trade; + int rsi_handle; + double rsi_buffer[]; + double rsi_prev; + double rsi_current; + double rsi_two_bars_ago; + bool position_open; + ulong position_ticket; + ENUM_POSITION_TYPE current_position_type; + datetime last_bar_time; + bool rsi_against_position; + int bars_against_count; +}; + +string ErrorDescription(int errorCode) +{ + switch(errorCode) + { + case 4801: return "Symbol not found"; + case 4802: return "Symbol not selected"; + case 4803: return "Symbol not visible"; + case 4804: return "Symbol not available"; + case 4805: return "Cannot load indicator - insufficient history data"; + default: return "Unknown error " + IntegerToString(errorCode); + } +} + +bool InitRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period, + ENUM_APPLIED_PRICE RSI_Applied_Price, int MagicNumber, int Slippage) +{ + data.symbol = symbol; + data.isInitialized = false; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSIScalping: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; // Return false but don't fail entire EA + } + + // Wait a bit for symbol to be ready + Sleep(100); + + // Try to create RSI indicator with retry logic (for insufficient history in backtesting) + data.rsi_handle = INVALID_HANDLE; + int retryCount = 0; + int maxRetries = 5; + + while(retryCount < maxRetries && data.rsi_handle == INVALID_HANDLE) + { + data.rsi_handle = iRSI(symbol, TimeFrame, RSI_Period, RSI_Applied_Price); + + if(data.rsi_handle == INVALID_HANDLE) + { + int error = GetLastError(); + + // Error 4805 = insufficient history - wait longer and retry + if(error == 4805 && retryCount < maxRetries - 1) + { + Sleep(1000); // Wait 1 second for history to load + retryCount++; + continue; + } + + Print("RSIScalping: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", ErrorDescription(error), ")"); + return false; // Return false but don't fail entire EA + } + } + + if(data.rsi_handle == INVALID_HANDLE) + { + Print("RSIScalping: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries"); + return false; + } + + data.trade.SetExpertMagicNumber(MagicNumber); + data.trade.SetDeviationInPoints(Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_FOK); + + ArraySetAsSeries(data.rsi_buffer, true); + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + data.isInitialized = true; + + Print("RSIScalping: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitRSIScalping(RSIScalpingData& data) +{ + if(data.rsi_handle != INVALID_HANDLE) + IndicatorRelease(data.rsi_handle); +} + +bool UpdateRSI(RSIScalpingData& data) +{ + if(CopyBuffer(data.rsi_handle, 0, 0, 3, data.rsi_buffer) < 3) + return false; + + data.rsi_current = data.rsi_buffer[0]; + data.rsi_prev = data.rsi_buffer[1]; + data.rsi_two_bars_ago = data.rsi_buffer[2]; + + return true; +} + +void CheckExistingPosition(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber, + double RSI_Oversold, double RSI_Overbought, double RSI_Target_Buy, + double RSI_Target_Sell, int BarsToWait) +{ + // Always check if position exists, even if tracking says it doesn't + bool positionExists = PositionExistsByMagic(data.symbol, MagicNumber); + + if(!positionExists && data.position_open) + { + // Position was closed externally, reset tracking + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + return; + } + + if(!positionExists) + return; + + // Update tracking if we have a position but tracking was lost + if(!data.position_open && positionExists) + { + ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber); + if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber)) + { + data.position_ticket = ticket; + data.position_open = true; + data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + } + } + + // Verify our tracked position still exists + if(data.position_open && data.position_ticket > 0) + { + if(!PositionSelectByTicketSymbolAndMagic(data.position_ticket, data.symbol, MagicNumber)) + { + // Try to find the position again + ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber); + if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber)) + { + data.position_ticket = ticket; + data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + } + else + { + // Position doesn't exist, reset tracking + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + return; + } + } + else + { + // Update position type in case it changed (shouldn't happen, but be safe) + data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + } + } + + if(data.current_position_type == POSITION_TYPE_BUY) + { + if(data.rsi_current < RSI_Oversold) + { + if(!data.rsi_against_position) + { + data.rsi_against_position = true; + data.bars_against_count = 1; + } + else + { + data.bars_against_count++; + } + + if(data.bars_against_count >= BarsToWait) + { + ClosePosition(data, MagicNumber); + return; + } + } + else + { + if(data.rsi_against_position) + { + data.rsi_against_position = false; + data.bars_against_count = 0; + } + + if(data.rsi_current >= RSI_Target_Buy) + { + ClosePosition(data, MagicNumber); + } + } + } + else if(data.current_position_type == POSITION_TYPE_SELL) + { + if(data.rsi_current > RSI_Overbought) + { + if(!data.rsi_against_position) + { + data.rsi_against_position = true; + data.bars_against_count = 1; + } + else + { + data.bars_against_count++; + } + + if(data.bars_against_count >= BarsToWait) + { + ClosePosition(data, MagicNumber); + return; + } + } + else + { + if(data.rsi_against_position) + { + data.rsi_against_position = false; + data.bars_against_count = 0; + } + + if(data.rsi_current <= RSI_Target_Sell) + { + ClosePosition(data, MagicNumber); + } + } + } +} + +void CheckEntrySignals(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber, + double RSI_Oversold, double RSI_Overbought, double LotSize) +{ + if(data.rsi_two_bars_ago <= RSI_Oversold && data.rsi_prev > RSI_Oversold) + { + OpenBuyPosition(data, MagicNumber, LotSize); + } + + if(data.rsi_two_bars_ago >= RSI_Overbought && data.rsi_prev < RSI_Overbought) + { + OpenSellPosition(data, MagicNumber, LotSize); + } +} + +//+------------------------------------------------------------------+ +//| Normalize Lot Size According to Symbol Properties | +//+------------------------------------------------------------------+ +double NormalizeLotSize(string symbol, double lotSize) +{ + double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); + double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); + double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); + + // Round to lot step + if(lotStep > 0) + lotSize = MathFloor(lotSize / lotStep) * lotStep; + + // Apply min/max constraints + if(lotSize < minLot) + lotSize = minLot; + if(lotSize > maxLot) + lotSize = maxLot; + + return lotSize; +} + +void OpenBuyPosition(RSIScalpingData& data, int MagicNumber, double LotSize) +{ + if(PositionExistsByMagic(data.symbol, MagicNumber)) + return; + + // Normalize lot size according to symbol properties + double normalizedLot = NormalizeLotSize(data.symbol, LotSize); + + double ask = SymbolInfoDouble(data.symbol, SYMBOL_ASK); + + if(data.trade.Buy(normalizedLot, data.symbol, ask, 0, 0, "RSI Scalping Buy")) + { + ulong new_ticket = data.trade.ResultOrder(); + if(new_ticket > 0) + { + if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber)) + { + data.position_ticket = new_ticket; + data.position_open = true; + data.current_position_type = POSITION_TYPE_BUY; + } + } + } +} + +void OpenSellPosition(RSIScalpingData& data, int MagicNumber, double LotSize) +{ + if(PositionExistsByMagic(data.symbol, MagicNumber)) + return; + + // Normalize lot size according to symbol properties + double normalizedLot = NormalizeLotSize(data.symbol, LotSize); + + double bid = SymbolInfoDouble(data.symbol, SYMBOL_BID); + + if(data.trade.Sell(normalizedLot, data.symbol, bid, 0, 0, "RSI Scalping Sell")) + { + ulong new_ticket = data.trade.ResultOrder(); + if(new_ticket > 0) + { + if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber)) + { + data.position_ticket = new_ticket; + data.position_open = true; + data.current_position_type = POSITION_TYPE_SELL; + } + } + } +} + +void ClosePosition(RSIScalpingData& data, int MagicNumber) +{ + // First verify position still exists + if(!PositionExistsByMagic(data.symbol, MagicNumber)) + { + // Position doesn't exist, reset tracking + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + return; + } + + // Try to close by ticket first (more reliable) + bool closed = false; + if(data.position_ticket > 0) + { + if(PositionSelectByTicket(data.position_ticket)) + { + // Verify it's our position + if(PositionGetString(POSITION_SYMBOL) == data.symbol && + PositionGetInteger(POSITION_MAGIC) == MagicNumber) + { + closed = data.trade.PositionClose(data.position_ticket); + if(!closed) + { + Print("RSIScalping: Failed to close position by ticket ", data.position_ticket, + " - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")"); + } + } + } + } + + // If ticket method failed, try magic number method + if(!closed) + { + closed = ClosePositionByMagic(data.trade, data.symbol, MagicNumber); + if(!closed) + { + Print("RSIScalping: Failed to close position by magic number for '", data.symbol, + "' - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")"); + } + } + + // Verify position is actually closed + if(closed) + { + // Wait a moment and verify + Sleep(50); + if(!PositionExistsByMagic(data.symbol, MagicNumber)) + { + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + Print("RSIScalping: Position successfully closed for '", data.symbol, "'"); + } + else + { + Print("RSIScalping: Warning - Close returned success but position still exists for '", data.symbol, "'"); + // Try one more time + Sleep(100); + if(PositionExistsByMagic(data.symbol, MagicNumber)) + { + ClosePositionByMagic(data.trade, data.symbol, MagicNumber); + } + // Reset tracking anyway to prevent getting stuck + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + } + } + else + { + // Close failed, but reset tracking to prevent getting stuck + // The position might have been closed externally + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + } +} + +void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period, + ENUM_APPLIED_PRICE RSI_Applied_Price, double RSI_Overbought, + double RSI_Oversold, double RSI_Target_Buy, double RSI_Target_Sell, + int BarsToWait, double LotSize, int MagicNumber) +{ + // Skip if not initialized (symbol not available) + if(!data.isInitialized) + return; + + data.symbol = symbol; // Update symbol in case it changed + if(Bars(data.symbol, TimeFrame) < RSI_Period + 2) + return; + + datetime current_bar_time = iTime(data.symbol, TimeFrame, 0); + if(current_bar_time == data.last_bar_time) + return; + + data.last_bar_time = current_bar_time; + + if(!UpdateRSI(data)) + return; + + CheckExistingPosition(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought, + RSI_Target_Buy, RSI_Target_Sell, BarsToWait); + + if(!data.position_open && !PositionExistsByMagic(data.symbol, MagicNumber)) + { + CheckEntrySignals(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought, LotSize); + } +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/main.mq5 b/frontline/MQL5/_united_dynamic/main.mq5 new file mode 100644 index 0000000..90ad2e8 --- /dev/null +++ b/frontline/MQL5/_united_dynamic/main.mq5 @@ -0,0 +1,675 @@ +//+------------------------------------------------------------------+ +//| UnitedEA.mq5 | +//| Copyright 2025, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2025, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.07" +#property strict + +#include +#include +#include +#include +#include "MagicNumberHelpers.mqh" + +// Lot globals must exist before strategy .mqh (Darvas uses g_DB_LotSize; EMA/RC/RM use g_ES/g_RC/g_RM) +double g_ES_LotSize; +double g_RC_LotSize; +double g_RM_LotSize; +double g_DB_LotSize; +double g_DynMultLast = 1.0; + +// Include strategy implementations early so structs are available +#include "Strategies/DarvasBoxStrategy.mqh" +#include "Strategies/EMASlopeDistanceStrategy.mqh" +#include "Strategies/RSICrossOverReversalStrategy.mqh" +#include "Strategies/RSIMidPointHijackStrategy.mqh" +#include "Strategies/RSIScalpingStrategy.mqh" +#include "Strategies/RSIReversalAsianStrategy.mqh" + +//+------------------------------------------------------------------+ +//| Strategy Enable/Disable Switches | +//+------------------------------------------------------------------+ +input group "=== Strategy Enable/Disable ===" +input bool EnableDarvasBox = true; +input bool EnableEMASlopeDistance = true; +input bool EnableRSICrossOverReversal = true; +input bool EnableRSIMidPointHijack = true; +input bool EnableRSIScalpingAPPL = true; +input bool EnableRSIScalpingBTCUSD = true; +input bool EnableRSIScalpingNVDA = true; +input bool EnableRSIScalpingTSLA = true; +input bool EnableRSIScalpingXAUUSD = true; +input bool EnableRSIReversalAsianEURUSD = true; +input bool EnableRSIReversalAsianAUDUSD = true; + +//+------------------------------------------------------------------+ +//| Dynamic lot sizing — scale base lots vs reference deposit | +//| mult=(equity/ref)^exp; maxMult<=0 上不封顶; minMult<=0 不锁下限 | +//+------------------------------------------------------------------+ +input group "=== Dynamic lot sizing (动态手数) ===" +input bool InpDynamicLotEnable = true; // Enable balance/equity-based scaling +input double InpDynamicRefDeposit = 3000.0; // Reference balance (match Tester initial deposit) +input double InpDynamicExponent = 1.15; // 1.0=linear; >1 faster growth; <1 conservative +input double InpDynamicMinMult = 0.0; // <=0 不锁下限; >0 例如0.25 为最低倍数 +input double InpDynamicMaxMult = 0.0; // <=0 动态倍数不封顶; >0 上限封顶 +input bool InpDynamicUseEquity = true; // true=ACCOUNT_EQUITY, false=ACCOUNT_BALANCE +input double InpDynamicStockLotCap = 0.0; // Extra cap for stock CFDs (0 = none) + +//+------------------------------------------------------------------+ +//| Strategy 1: DarvasBoxXAUUSD | +//+------------------------------------------------------------------+ +input group "=== DarvasBox Strategy ===" +input string DB_Symbol = "XAUUSD"; +input int DB_BoxPeriod = 165; +input double DB_BoxDeviation = 30000; // Increased to allow larger ranges (was 25140) +input int DB_VolumeThreshold = 0; // Set to 0 to disable volume threshold check. Volume data from indicator used instead. +input double DB_StopLoss = 1665; +input double DB_TakeProfit = 3685; +input bool DB_EnableLogging = false; +input color DB_BoxColor = clrBlue; +input int DB_BoxWidth = 1; +input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2; +input int DB_MA_Period = 125; +input ENUM_MA_METHOD DB_MA_Method = MODE_EMA; +input ENUM_APPLIED_PRICE DB_MA_Price = PRICE_WEIGHTED; +input double DB_TrendThreshold = 4.94; +input int DB_VolumeMA_Period = 110; +input double DB_VolumeThresholdMultiplier = 1.5; +input int DB_MagicNumber = 135790; +input double DB_BaseLotSize = 0.01; // Base lot at InpDynamicRefDeposit (Darvas) + +//+------------------------------------------------------------------+ +//| Strategy 2: EMASlopeDistanceCocktailXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== EMA Slope Distance Strategy ===" +input string ES_Symbol = "XAUUSD"; +input int ES_EMA_Periode = 46; +input double ES_PreisSchwelle = 600.0; +input double ES_SteigungSchwelle = 80.0; +input int ES_ÜberwachungTimeout = 800; +input double ES_TrailingStop = 250.0; +input double ES_LotGröße = 0.03; +input int ES_MagicNumber = 12350; +input bool ES_UseSpreadAdjustment = true; +input ENUM_TIMEFRAMES ES_Timeframe = PERIOD_H1; +input bool ES_UseBarData = true; +input int ES_MaxTradesPerCrossover = 9; +input int ES_ProfitCheckBars = 18; +input bool ES_CloseUnprofitableTrades = true; + +//+------------------------------------------------------------------+ +//| Strategy 3: RSICrossOverReversalXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== RSI CrossOver Reversal Strategy ===" +input string RC_Symbol = "XAUUSD"; +input int RC_MagicNumber = 7; +input int RC_rsiPeriod = 19; +input int RC_overboughtLevel = 93; +input int RC_oversoldLevel = 22; +input double RC_entryRSIBuySpread = 0; +input double RC_entryRSISellSpread = 0; +input double RC_lotSize = 0.01; +input int RC_slippage = 3; +input int RC_cooldownSeconds = 209; +input ENUM_TIMEFRAMES RC_TimeFrame1 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_TimeFrame2 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_BarTimeFrame = PERIOD_M12; +input int RC_emaPeriod = 140; +input double RC_emaSlopeThreshold = 105; +input double RC_exitBuyRSI = 86; +input double RC_exitSellRSI = 10; +input double RC_TrailingStop = 295; +input double RC_emaDistanceThreshold = 165; +input int RC_tradingHourOneBegin = 24; +input int RC_tradingHourOneEnd = 22; +input int RC_tradingHourTwoBegin = 6; +input int RC_tradingHourTwoEnd = 19; +input bool RC_Sunday = false; +input bool RC_Monday = false; +input bool RC_Tuesday = true; +input bool RC_Wednesday = true; +input bool RC_Thursday = true; +input bool RC_Friday = false; +input bool RC_Saturday = false; + +//+------------------------------------------------------------------+ +//| Strategy 4: RSIMidPointHijackXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== RSI MidPoint Hijack Strategy ===" +input string RM_Symbol = "XAUUSD"; +input ENUM_TIMEFRAMES RM_InpTimeframe = PERIOD_H1; +input double RM_InpLotSize = 0.02; +input int RM_InpMagicNumberRSIFollow = 1001; +input int RM_InpMagicNumberRSIReverse = 1002; +input int RM_InpMagicNumberEMACross = 1003; +input bool RM_InpEnableRSIFollow = true; +input bool RM_InpEnableRSIReverse = true; +input bool RM_InpEnableEMACross = true; +input bool RM_InpEnableStrategyLock = false; +input double RM_InpLockProfitThreshold = 0.0; +input bool RM_InpCloseOppositeTrades = false; +input int RM_InpRSIPeriod = 32; +input int RM_InpRSIOverbought = 78; +input int RM_InpRSIOversold = 46; +input int RM_InpRSIExitLevel = 44; +input int RM_InpRSIFollowStartHour = 23; +input int RM_InpRSIFollowEndHour = 8; +input bool RM_InpRSIFollowCloseOutsideHours = false; +input int RM_InpRSIReversePeriod = 59; +input int RM_InpRSIReverseOverbought = 51; +input int RM_InpRSIReverseOversold = 49; +input int RM_InpRSIReverseCrossLevel = 53; +input int RM_InpRSIReverseExitLevel = 48; +input int RM_InpRSIReverseStartHour = 7; +input int RM_InpRSIReverseEndHour = 13; +input bool RM_InpRSIReverseCloseOutsideHours = false; +input int RM_InpRSIReverseCooldownBars = 15; +input bool RM_InpRSIReverseCooldownOnLoss = true; +input int RM_InpEMAPeriod = 120; +input int RM_InpEMACrossStartHour = 8; +input int RM_InpEMACrossEndHour = 14; +input bool RM_InpEMACrossCloseOutsideHours = true; +input bool RM_InpUseEMADistanceEntry = true; +input double RM_InpEMADistancePips = 160.0; +input int RM_InpEMADistancePeriod = 26; + +//+------------------------------------------------------------------+ +//| Strategy 5-10: RSI Scalping Strategies | +//| Each RSI Scalping strategy trades on its own symbol: | +//| - APPL: Apple stock (AAPL) | +//| - BTCUSD: Bitcoin/USD | +//| - NVDA: NVIDIA stock | +//| - TSLA: Tesla stock | +//| - XAUUSD: Gold/USD | +//| | +//| PEPPERSTONE US SYMBOL FORMATS: | +//| - Stocks may use: "AAPL.US", "NASDAQ:AAPL", or just "AAPL" | +//| - To find correct symbols: | +//| 1. Open Market Watch (Ctrl+M) | +//| 2. Right-click > Show All | +//| 3. Search for the stock name | +//| 4. Use the exact symbol name shown | +//+------------------------------------------------------------------+ +input group "=== RSI Scalping APPL (AAPL) - Pepperstone US ===" +input string RS_APPL_Symbol = "AAPL.US"; // Try: "AAPL.US", "NASDAQ:AAPL", or "AAPL" +input ENUM_TIMEFRAMES RS_APPL_TimeFrame = PERIOD_M10; +input int RS_APPL_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_APPL_RSI_Applied_Price = PRICE_CLOSE; +input double RS_APPL_RSI_Overbought = 80; +input double RS_APPL_RSI_Oversold = 78; +input double RS_APPL_RSI_Target_Buy = 94; +input double RS_APPL_RSI_Target_Sell = 44; +input int RS_APPL_BarsToWait = 7; +input double RS_APPL_LotSize = 25; +input int RS_APPL_MagicNumber = 20001; +input int RS_APPL_Slippage = 3; + +input group "=== RSI Scalping BTCUSD ===" +input string RS_BTCUSD_Symbol = "BTCUSD"; // Pepperstone may use: "BTCUSD", "BTC/USD", or "BTCUSD.c" +input ENUM_TIMEFRAMES RS_BTCUSD_TimeFrame = PERIOD_H1; +input int RS_BTCUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_BTCUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_BTCUSD_RSI_Overbought = 90; +input double RS_BTCUSD_RSI_Oversold = 73; +input double RS_BTCUSD_RSI_Target_Buy = 88; +input double RS_BTCUSD_RSI_Target_Sell = 48; +input int RS_BTCUSD_BarsToWait = 6; +input double RS_BTCUSD_LotSize = 0.1; +input int RS_BTCUSD_MagicNumber = 123459123; +input int RS_BTCUSD_Slippage = 3; + +input group "=== RSI Scalping NVDA - Pepperstone US ===" +input string RS_NVDA_Symbol = "NVDA.US"; // Try: "NVDA.US", "NASDAQ:NVDA", or "NVDA" +input ENUM_TIMEFRAMES RS_NVDA_TimeFrame = PERIOD_M15; +input int RS_NVDA_RSI_Period = 8; +input ENUM_APPLIED_PRICE RS_NVDA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_NVDA_RSI_Overbought = 36; +input double RS_NVDA_RSI_Oversold = 38; +input double RS_NVDA_RSI_Target_Buy = 90; +input double RS_NVDA_RSI_Target_Sell = 70; +input int RS_NVDA_BarsToWait = 5; +input double RS_NVDA_LotSize = 50; +input int RS_NVDA_MagicNumber = 20003; +input int RS_NVDA_Slippage = 3; + +input group "=== RSI Scalping TSLA - Pepperstone US ===" +input string RS_TSLA_Symbol = "TSLA.US"; // Try: "TSLA.US", "NASDAQ:TSLA", or "TSLA" +input ENUM_TIMEFRAMES RS_TSLA_TimeFrame = PERIOD_H1; +input int RS_TSLA_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_TSLA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_TSLA_RSI_Overbought = 54; +input double RS_TSLA_RSI_Oversold = 73; +input double RS_TSLA_RSI_Target_Buy = 87; +input double RS_TSLA_RSI_Target_Sell = 33; +input int RS_TSLA_BarsToWait = 1; +input double RS_TSLA_LotSize = 50; +input int RS_TSLA_MagicNumber = 125421321; +input int RS_TSLA_Slippage = 3; + +input group "=== RSI Scalping XAUUSD ===" +input string RS_XAUUSD_Symbol = "XAUUSD"; +input ENUM_TIMEFRAMES RS_XAUUSD_TimeFrame = PERIOD_H1; +input int RS_XAUUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_XAUUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_XAUUSD_RSI_Overbought = 71; +input double RS_XAUUSD_RSI_Oversold = 57; +input double RS_XAUUSD_RSI_Target_Buy = 80; +input double RS_XAUUSD_RSI_Target_Sell = 57; +input int RS_XAUUSD_BarsToWait = 4; +input double RS_XAUUSD_LotSize = 0.1; +input int RS_XAUUSD_MagicNumber = 129102315; +input int RS_XAUUSD_Slippage = 3; + +//+------------------------------------------------------------------+ +//| Strategy 11-12: RSI Reversal Asian Strategies | +//| Each RSI Reversal Asian strategy trades on its own symbol: | +//| - EURUSD: Euro/USD | +//| - AUDUSD: Australian Dollar/USD | +//+------------------------------------------------------------------+ +input group "=== RSI Reversal Asian EURUSD ===" +input string RRA_EURUSD_Symbol = "EURUSD"; +input int RRA_EURUSD_RSIPeriod = 28; +input double RRA_EURUSD_OverboughtLevel = 60; +input double RRA_EURUSD_OversoldLevel = 8; +input int RRA_EURUSD_TakeProfitPips = 175; +input int RRA_EURUSD_StopLossPips = 5; +input double RRA_EURUSD_MaxLotSize = 0.1; +input int RRA_EURUSD_MaxSpread = 1000; +input int RRA_EURUSD_MaxDuration = 270; +input bool RRA_EURUSD_UseStopLoss = false; +input bool RRA_EURUSD_UseTakeProfit = false; +input bool RRA_EURUSD_UseRSIExit = true; +input double RRA_EURUSD_RSIExitLevel = 55; +input bool RRA_EURUSD_CloseOutsideSession = false; +input ENUM_TIMEFRAMES RRA_EURUSD_TimeFrame = PERIOD_M15; +input int RRA_EURUSD_MagicNumber = 30001; +input int RRA_EURUSD_Slippage = 3; + +input group "=== RSI Reversal Asian AUDUSD ===" +input string RRA_AUDUSD_Symbol = "AUDUSD"; +input int RRA_AUDUSD_RSIPeriod = 28; +input double RRA_AUDUSD_OverboughtLevel = 68; +input double RRA_AUDUSD_OversoldLevel = 30; +input int RRA_AUDUSD_TakeProfitPips = 175; +input int RRA_AUDUSD_StopLossPips = 5; +input double RRA_AUDUSD_MaxLotSize = 0.2; +input int RRA_AUDUSD_MaxSpread = 1000; +input int RRA_AUDUSD_MaxDuration = 340; +input bool RRA_AUDUSD_UseStopLoss = false; +input bool RRA_AUDUSD_UseTakeProfit = false; +input bool RRA_AUDUSD_UseRSIExit = true; +input double RRA_AUDUSD_RSIExitLevel = 48; +input bool RRA_AUDUSD_CloseOutsideSession = true; +input ENUM_TIMEFRAMES RRA_AUDUSD_TimeFrame = PERIOD_M15; +input int RRA_AUDUSD_MagicNumber = 30002; +input int RRA_AUDUSD_Slippage = 3; + +//+------------------------------------------------------------------+ +//| Global Variables - DarvasBox | +//+------------------------------------------------------------------+ +struct DarvasBoxData { + string symbol; + bool isInitialized; + double boxHigh; + double boxLow; + bool boxFormed; + datetime lastBoxTime; + string boxName; + double minStopLevel; + double point; + CTrade trade; + int maHandle; + int volumeHandle; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - EMA Slope Distance | +//+------------------------------------------------------------------+ +struct EMASlopeData { + string symbol; + bool isInitialized; + int ema_handle; + double ema_array[]; + datetime letzte_überwachung_zeit; + bool überwachung_aktiv; + bool preis_trigger_aktiv; + bool steigung_trigger_aktiv; + int ticket; + CTrade trade; + int trades_in_current_crossover; + bool crossover_detected; + datetime trade_open_time; + datetime last_bar_time; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI CrossOver Reversal | +//+------------------------------------------------------------------+ +struct RSICrossOverData { + string symbol; + bool isInitialized; + int rsiHandle; + int emaHandle; + double previousRSIDef; + CTrade trade; + datetime lastTradeTime; + datetime bartime; + bool WeekDays[7]; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI MidPoint Hijack | +//+------------------------------------------------------------------+ +struct RSIMidPointData { + string symbol; + bool isInitialized; + int rsiHandle; + int rsiReverseHandle; + int emaHandle; + bool rsiOverbought; + bool rsiOversold; + bool rsiReverseOverbought; + bool rsiReverseOversold; + CTrade trade; + CPositionInfo positionInfo; + bool emaCrossBuySignal; + bool emaCrossSellSignal; + int emaCrossSignalBar; + datetime lastBarTime; + datetime rsiReverseLastCloseTime; + bool rsiReverseInCooldown; + double lastBarRSI; + double lastBarRSIReverse; + double lastBarEMA; + double lastBarClose; + double lastBarEMAPrev; + double lastBarClosePrev; +}; + +//+------------------------------------------------------------------+ +//| Global Strategy Instances | +//+------------------------------------------------------------------+ +DarvasBoxData dbData; +EMASlopeData esData; +RSICrossOverData rcData; +RSIMidPointData rmData; +RSIScalpingData rsAPPLData; +RSIScalpingData rsBTCUSDData; +RSIScalpingData rsNVDAData; +RSIScalpingData rsTSLAData; +RSIScalpingData rsXAUUSDData; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI Reversal Asian | +//+------------------------------------------------------------------+ +RSIReversalAsianData rraEURUSDData; +RSIReversalAsianData rraAUDUSDData; + +//+------------------------------------------------------------------+ +//| Dynamic lot helpers | +//+------------------------------------------------------------------+ +double DynClamp(const double v, const double lo, const double hi) +{ + return MathMax(lo, MathMin(hi, v)); +} + +// maxMult<=0: no ceiling. minMult<=0: no floor on raw (equity/ref)^exp. +double ApplyDynamicMultClamp(const double mult) +{ + double m = mult; + if(InpDynamicMinMult > 0.0) + m = MathMax(m, InpDynamicMinMult); + if(InpDynamicMaxMult > 0.0) + m = MathMin(m, InpDynamicMaxMult); + return m; +} + +double NormalizeVolumeForSymbol(const string symbol, double lots) +{ + double minL = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); + double maxL = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); + double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); + if(step > 0.0) + lots = MathFloor(lots / step + 1e-12) * step; + if(lots < minL) lots = minL; + if(lots > maxL) lots = maxL; + return lots; +} + +double GetDynamicMultiplier() +{ + if(!InpDynamicLotEnable) + return 1.0; + double cap = InpDynamicUseEquity ? AccountInfoDouble(ACCOUNT_EQUITY) : AccountInfoDouble(ACCOUNT_BALANCE); + if(cap <= 0.0) + cap = InpDynamicRefDeposit; + double refv = MathMax(InpDynamicRefDeposit, 1.0); + double ratio = cap / refv; + if(ratio <= 0.0) + ratio = 1.0; + double mult = MathPow(ratio, InpDynamicExponent); + return ApplyDynamicMultClamp(mult); +} + +// baseLot = size at reference deposit; optionalCap 0 = no extra ceiling (broker min/max still apply) +double DynamicLotForSymbol(const string symbol, const double baseLot, const double optionalCap = 0.0) +{ + double mult = GetDynamicMultiplier(); + g_DynMultLast = mult; + double v = baseLot * mult; + if(optionalCap > 0.0 && v > optionalCap) + v = optionalCap; + return NormalizeVolumeForSymbol(symbol, v); +} + +void RefreshDynamicStrategyLots() +{ + if(!InpDynamicLotEnable) + { + g_ES_LotSize = NormalizeVolumeForSymbol(ES_Symbol, ES_LotGröße); + g_RC_LotSize = NormalizeVolumeForSymbol(RC_Symbol, RC_lotSize); + g_RM_LotSize = NormalizeVolumeForSymbol(RM_Symbol, RM_InpLotSize); + g_DB_LotSize = NormalizeVolumeForSymbol(DB_Symbol, DB_BaseLotSize); + g_DynMultLast = 1.0; + return; + } + g_ES_LotSize = DynamicLotForSymbol(ES_Symbol, ES_LotGröße); + g_RC_LotSize = DynamicLotForSymbol(RC_Symbol, RC_lotSize); + g_RM_LotSize = DynamicLotForSymbol(RM_Symbol, RM_InpLotSize); + g_DB_LotSize = DynamicLotForSymbol(DB_Symbol, DB_BaseLotSize); +} + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + int initResult = INIT_SUCCEEDED; + + RefreshDynamicStrategyLots(); + + // Initialize strategies - log warnings but don't fail entire EA if symbol unavailable + if(EnableDarvasBox) + if(!InitDarvasBox(DB_Symbol)) + Print("Warning: DarvasBox strategy failed to initialize for symbol '", DB_Symbol, "'"); + + if(EnableEMASlopeDistance) + if(!InitEMASlopeDistance(ES_Symbol)) + Print("Warning: EMASlopeDistance strategy failed to initialize for symbol '", ES_Symbol, "'"); + + if(EnableRSICrossOverReversal) + if(!InitRSICrossOverReversal(RC_Symbol)) + Print("Warning: RSICrossOverReversal strategy failed to initialize for symbol '", RC_Symbol, "'"); + + if(EnableRSIMidPointHijack) + if(!InitRSIMidPointHijack(RM_Symbol)) + Print("Warning: RSIMidPointHijack strategy failed to initialize for symbol '", RM_Symbol, "'"); + + // Initialize RSI Scalping strategies - don't fail entire EA if symbol unavailable + if(EnableRSIScalpingAPPL) + InitRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, RS_APPL_MagicNumber, RS_APPL_Slippage); + + if(EnableRSIScalpingBTCUSD) + InitRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, RS_BTCUSD_MagicNumber, RS_BTCUSD_Slippage); + + if(EnableRSIScalpingNVDA) + InitRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, RS_NVDA_MagicNumber, RS_NVDA_Slippage); + + if(EnableRSIScalpingTSLA) + InitRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, RS_TSLA_MagicNumber, RS_TSLA_Slippage); + + if(EnableRSIScalpingXAUUSD) + InitRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, RS_XAUUSD_MagicNumber, RS_XAUUSD_Slippage); + + // Initialize RSI Reversal Asian strategies + if(EnableRSIReversalAsianEURUSD) + if(!InitRSIReversalAsian(rraEURUSDData, RRA_EURUSD_Symbol, RRA_EURUSD_RSIPeriod, RRA_EURUSD_OverboughtLevel, RRA_EURUSD_OversoldLevel, + RRA_EURUSD_TakeProfitPips, RRA_EURUSD_StopLossPips, RRA_EURUSD_MaxLotSize, + RRA_EURUSD_MaxSpread, RRA_EURUSD_MaxDuration, RRA_EURUSD_UseStopLoss, + RRA_EURUSD_UseTakeProfit, RRA_EURUSD_UseRSIExit, RRA_EURUSD_RSIExitLevel, + RRA_EURUSD_CloseOutsideSession, RRA_EURUSD_TimeFrame, RRA_EURUSD_MagicNumber, RRA_EURUSD_Slippage)) + Print("Warning: RSIReversalAsianEURUSD strategy failed to initialize for symbol '", RRA_EURUSD_Symbol, "'"); + + if(EnableRSIReversalAsianAUDUSD) + if(!InitRSIReversalAsian(rraAUDUSDData, RRA_AUDUSD_Symbol, RRA_AUDUSD_RSIPeriod, RRA_AUDUSD_OverboughtLevel, RRA_AUDUSD_OversoldLevel, + RRA_AUDUSD_TakeProfitPips, RRA_AUDUSD_StopLossPips, RRA_AUDUSD_MaxLotSize, + RRA_AUDUSD_MaxSpread, RRA_AUDUSD_MaxDuration, RRA_AUDUSD_UseStopLoss, + RRA_AUDUSD_UseTakeProfit, RRA_AUDUSD_UseRSIExit, RRA_AUDUSD_RSIExitLevel, + RRA_AUDUSD_CloseOutsideSession, RRA_AUDUSD_TimeFrame, RRA_AUDUSD_MagicNumber, RRA_AUDUSD_Slippage)) + Print("Warning: RSIReversalAsianAUDUSD strategy failed to initialize for symbol '", RRA_AUDUSD_Symbol, "'"); + + string acctCur = AccountInfoString(ACCOUNT_CURRENCY); + double eq0 = AccountInfoDouble(ACCOUNT_EQUITY); + double refvInit = MathMax(InpDynamicRefDeposit, 1.0); + double capInit = InpDynamicUseEquity ? eq0 : AccountInfoDouble(ACCOUNT_BALANCE); + if(capInit <= 0.0) + capInit = refvInit; + double ratioInit = capInit / refvInit; + double rawPowInit = MathPow(ratioInit, InpDynamicExponent); + Print("United EA v1.07 ", acctCur, " equity=", DoubleToString(eq0, 2), " equity/ref=", DoubleToString(ratioInit, 6), + " raw^exp=", DoubleToString(rawPowInit, 6), " multOut=", DoubleToString(g_DynMultLast, 6), + " minM=", InpDynamicMinMult, " maxM=", InpDynamicMaxMult, " ref=", InpDynamicRefDeposit, " exp=", InpDynamicExponent, + " lots ES=", g_ES_LotSize, " RC=", g_RC_LotSize, " RM=", g_RM_LotSize, " DB=", g_DB_LotSize); + Print("United EA initialized. Active strategies: ", + (EnableDarvasBox ? "DarvasBox " : ""), + (EnableEMASlopeDistance ? "EMASlope " : ""), + (EnableRSICrossOverReversal ? "RSICrossOver " : ""), + (EnableRSIMidPointHijack ? "RSIMidPoint " : ""), + (EnableRSIScalpingAPPL ? "RSIScalpingAPPL " : ""), + (EnableRSIScalpingBTCUSD ? "RSIScalpingBTCUSD " : ""), + (EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""), + (EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""), + (EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""), + (EnableRSIReversalAsianEURUSD ? "RSIReversalAsianEURUSD " : ""), + (EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : "")); + + return initResult; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(EnableDarvasBox) + DeinitDarvasBox(); + + if(EnableEMASlopeDistance) + DeinitEMASlopeDistance(); + + if(EnableRSICrossOverReversal) + DeinitRSICrossOverReversal(); + + if(EnableRSIMidPointHijack) + DeinitRSIMidPointHijack(); + + if(EnableRSIScalpingAPPL) + DeinitRSIScalping(rsAPPLData); + + if(EnableRSIScalpingBTCUSD) + DeinitRSIScalping(rsBTCUSDData); + + if(EnableRSIScalpingNVDA) + DeinitRSIScalping(rsNVDAData); + + if(EnableRSIScalpingTSLA) + DeinitRSIScalping(rsTSLAData); + + if(EnableRSIScalpingXAUUSD) + DeinitRSIScalping(rsXAUUSDData); + + if(EnableRSIReversalAsianEURUSD) + DeinitRSIReversalAsian(rraEURUSDData); + + if(EnableRSIReversalAsianAUDUSD) + DeinitRSIReversalAsian(rraAUDUSDData); + + Print("United EA deinitialized. Reason: ", reason); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + RefreshDynamicStrategyLots(); + + if(EnableDarvasBox) + ProcessDarvasBox(DB_Symbol); + + if(EnableEMASlopeDistance) + ProcessEMASlopeDistance(ES_Symbol); + + if(EnableRSICrossOverReversal) + ProcessRSICrossOverReversal(RC_Symbol); + + if(EnableRSIMidPointHijack) + ProcessRSIMidPointHijack(RM_Symbol); + + if(EnableRSIScalpingAPPL) + ProcessRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, + RS_APPL_RSI_Overbought, RS_APPL_RSI_Oversold, RS_APPL_RSI_Target_Buy, RS_APPL_RSI_Target_Sell, + RS_APPL_BarsToWait, + DynamicLotForSymbol(RS_APPL_Symbol, RS_APPL_LotSize, InpDynamicStockLotCap), + RS_APPL_MagicNumber); + + if(EnableRSIScalpingBTCUSD) + ProcessRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, + RS_BTCUSD_RSI_Overbought, RS_BTCUSD_RSI_Oversold, RS_BTCUSD_RSI_Target_Buy, RS_BTCUSD_RSI_Target_Sell, + RS_BTCUSD_BarsToWait, DynamicLotForSymbol(RS_BTCUSD_Symbol, RS_BTCUSD_LotSize), RS_BTCUSD_MagicNumber); + + if(EnableRSIScalpingNVDA) + ProcessRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, + RS_NVDA_RSI_Overbought, RS_NVDA_RSI_Oversold, RS_NVDA_RSI_Target_Buy, RS_NVDA_RSI_Target_Sell, + RS_NVDA_BarsToWait, + DynamicLotForSymbol(RS_NVDA_Symbol, RS_NVDA_LotSize, InpDynamicStockLotCap), + RS_NVDA_MagicNumber); + + if(EnableRSIScalpingTSLA) + ProcessRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, + RS_TSLA_RSI_Overbought, RS_TSLA_RSI_Oversold, RS_TSLA_RSI_Target_Buy, RS_TSLA_RSI_Target_Sell, + RS_TSLA_BarsToWait, + DynamicLotForSymbol(RS_TSLA_Symbol, RS_TSLA_LotSize, InpDynamicStockLotCap), + RS_TSLA_MagicNumber); + + if(EnableRSIScalpingXAUUSD) + ProcessRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, + RS_XAUUSD_RSI_Overbought, RS_XAUUSD_RSI_Oversold, RS_XAUUSD_RSI_Target_Buy, RS_XAUUSD_RSI_Target_Sell, + RS_XAUUSD_BarsToWait, DynamicLotForSymbol(RS_XAUUSD_Symbol, RS_XAUUSD_LotSize), RS_XAUUSD_MagicNumber); + + if(EnableRSIReversalAsianEURUSD) + ProcessRSIReversalAsian(rraEURUSDData, DynamicLotForSymbol(RRA_EURUSD_Symbol, RRA_EURUSD_MaxLotSize)); + + if(EnableRSIReversalAsianAUDUSD) + ProcessRSIReversalAsian(rraAUDUSDData, DynamicLotForSymbol(RRA_AUDUSD_Symbol, RRA_AUDUSD_MaxLotSize)); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic/report.png b/frontline/MQL5/_united_dynamic/report.png new file mode 100644 index 0000000..a174d73 Binary files /dev/null and b/frontline/MQL5/_united_dynamic/report.png differ diff --git a/frontline/MQL5/_united_dynamic/self-evaluate.mq5 b/frontline/MQL5/_united_dynamic/self-evaluate.mq5 new file mode 100644 index 0000000..99e078a --- /dev/null +++ b/frontline/MQL5/_united_dynamic/self-evaluate.mq5 @@ -0,0 +1,641 @@ +//+------------------------------------------------------------------+ +//| UnitedEA.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 strict + +#include +#include +#include +#include +#include "MagicNumberHelpers.mqh" +#include "PerformanceEvaluator.mqh" + +//+------------------------------------------------------------------+ +//| Strategy Enable/Disable Switches | +//+------------------------------------------------------------------+ +input group "=== Strategy Enable/Disable ===" +input bool EnableDarvasBox = true; +input bool EnableEMASlopeDistance = true; +input bool EnableRSICrossOverReversal = true; +input bool EnableRSIMidPointHijack = true; +input bool EnableRSIScalpingAPPL = true; +input bool EnableRSIScalpingBTCUSD = true; +input bool EnableRSIScalpingNVDA = true; +input bool EnableRSIScalpingTSLA = true; +input bool EnableRSIScalpingXAUUSD = true; + +//+------------------------------------------------------------------+ +//| Strategy 1: DarvasBoxXAUUSD | +//+------------------------------------------------------------------+ +input group "=== DarvasBox Strategy ===" +input string DB_Symbol = "XAUUSD"; +input int DB_BoxPeriod = 165; +input double DB_BoxDeviation = 30000; // Increased to allow larger ranges (was 25140) +input int DB_VolumeThreshold = 0; // Set to 0 to disable volume threshold check. Volume data from indicator used instead. +input double DB_StopLoss = 1665; +input double DB_TakeProfit = 3685; +input bool DB_EnableLogging = false; +input color DB_BoxColor = clrBlue; +input int DB_BoxWidth = 1; +input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2; +input int DB_MA_Period = 125; +input ENUM_MA_METHOD DB_MA_Method = MODE_EMA; +input ENUM_APPLIED_PRICE DB_MA_Price = PRICE_WEIGHTED; +input double DB_TrendThreshold = 4.94; +input int DB_VolumeMA_Period = 110; +input double DB_VolumeThresholdMultiplier = 1.5; +input int DB_MagicNumber = 135790; + +//+------------------------------------------------------------------+ +//| Strategy 2: EMASlopeDistanceCocktailXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== EMA Slope Distance Strategy ===" +input string ES_Symbol = "XAUUSD"; +input int ES_EMA_Periode = 46; +input double ES_PreisSchwelle = 600.0; +input double ES_SteigungSchwelle = 80.0; +input int ES_ÜberwachungTimeout = 800; +input double ES_TrailingStop = 250.0; +input double ES_LotGröße = 0.03; +input int ES_MagicNumber = 12350; +input bool ES_UseSpreadAdjustment = true; +input ENUM_TIMEFRAMES ES_Timeframe = PERIOD_H1; +input bool ES_UseBarData = true; +input int ES_MaxTradesPerCrossover = 9; +input int ES_ProfitCheckBars = 18; +input bool ES_CloseUnprofitableTrades = true; + +//+------------------------------------------------------------------+ +//| Strategy 3: RSICrossOverReversalXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== RSI CrossOver Reversal Strategy ===" +input string RC_Symbol = "XAUUSD"; +input int RC_MagicNumber = 7; +input int RC_rsiPeriod = 19; +input int RC_overboughtLevel = 93; +input int RC_oversoldLevel = 22; +input double RC_entryRSIBuySpread = 0; +input double RC_entryRSISellSpread = 0; +input double RC_lotSize = 0.01; +input int RC_slippage = 3; +input int RC_cooldownSeconds = 209; +input ENUM_TIMEFRAMES RC_TimeFrame1 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_TimeFrame2 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_BarTimeFrame = PERIOD_M12; +input int RC_emaPeriod = 140; +input double RC_emaSlopeThreshold = 105; +input double RC_exitBuyRSI = 86; +input double RC_exitSellRSI = 10; +input double RC_TrailingStop = 295; +input double RC_emaDistanceThreshold = 165; +input int RC_tradingHourOneBegin = 24; +input int RC_tradingHourOneEnd = 22; +input int RC_tradingHourTwoBegin = 6; +input int RC_tradingHourTwoEnd = 19; +input bool RC_Sunday = false; +input bool RC_Monday = false; +input bool RC_Tuesday = true; +input bool RC_Wednesday = true; +input bool RC_Thursday = true; +input bool RC_Friday = false; +input bool RC_Saturday = false; + +//+------------------------------------------------------------------+ +//| Strategy 4: RSIMidPointHijackXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== RSI MidPoint Hijack Strategy ===" +input string RM_Symbol = "XAUUSD"; +input ENUM_TIMEFRAMES RM_InpTimeframe = PERIOD_H1; +input double RM_InpLotSize = 0.02; +input int RM_InpMagicNumberRSIFollow = 1001; +input int RM_InpMagicNumberRSIReverse = 1002; +input int RM_InpMagicNumberEMACross = 1003; +input bool RM_InpEnableRSIFollow = true; +input bool RM_InpEnableRSIReverse = true; +input bool RM_InpEnableEMACross = true; +input bool RM_InpEnableStrategyLock = false; +input double RM_InpLockProfitThreshold = 0.0; +input bool RM_InpCloseOppositeTrades = false; +input int RM_InpRSIPeriod = 32; +input int RM_InpRSIOverbought = 78; +input int RM_InpRSIOversold = 46; +input int RM_InpRSIExitLevel = 44; +input int RM_InpRSIFollowStartHour = 23; +input int RM_InpRSIFollowEndHour = 8; +input bool RM_InpRSIFollowCloseOutsideHours = false; +input int RM_InpRSIReversePeriod = 59; +input int RM_InpRSIReverseOverbought = 51; +input int RM_InpRSIReverseOversold = 49; +input int RM_InpRSIReverseCrossLevel = 53; +input int RM_InpRSIReverseExitLevel = 48; +input int RM_InpRSIReverseStartHour = 7; +input int RM_InpRSIReverseEndHour = 13; +input bool RM_InpRSIReverseCloseOutsideHours = false; +input int RM_InpRSIReverseCooldownBars = 15; +input bool RM_InpRSIReverseCooldownOnLoss = true; +input int RM_InpEMAPeriod = 120; +input int RM_InpEMACrossStartHour = 8; +input int RM_InpEMACrossEndHour = 14; +input bool RM_InpEMACrossCloseOutsideHours = true; +input bool RM_InpUseEMADistanceEntry = true; +input double RM_InpEMADistancePips = 160.0; +input int RM_InpEMADistancePeriod = 26; + +//+------------------------------------------------------------------+ +//| Strategy 5-10: RSI Scalping Strategies | +//| Each RSI Scalping strategy trades on its own symbol: | +//| - APPL: Apple stock (AAPL) | +//| - BTCUSD: Bitcoin/USD | +//| - NVDA: NVIDIA stock | +//| - TSLA: Tesla stock | +//| - XAUUSD: Gold/USD | +//| | +//| PEPPERSTONE US SYMBOL FORMATS: | +//| - Stocks may use: "AAPL.US", "NASDAQ:AAPL", or just "AAPL" | +//| - To find correct symbols: | +//| 1. Open Market Watch (Ctrl+M) | +//| 2. Right-click > Show All | +//| 3. Search for the stock name | +//| 4. Use the exact symbol name shown | +//+------------------------------------------------------------------+ +input group "=== RSI Scalping APPL (AAPL) - Pepperstone US ===" +input string RS_APPL_Symbol = "AAPL.US"; // Try: "AAPL.US", "NASDAQ:AAPL", or "AAPL" +input ENUM_TIMEFRAMES RS_APPL_TimeFrame = PERIOD_M10; +input int RS_APPL_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_APPL_RSI_Applied_Price = PRICE_CLOSE; +input double RS_APPL_RSI_Overbought = 80; +input double RS_APPL_RSI_Oversold = 78; +input double RS_APPL_RSI_Target_Buy = 94; +input double RS_APPL_RSI_Target_Sell = 44; +input int RS_APPL_BarsToWait = 7; +input double RS_APPL_LotSize = 25; +input int RS_APPL_MagicNumber = 20001; +input int RS_APPL_Slippage = 3; + +input group "=== RSI Scalping BTCUSD ===" +input string RS_BTCUSD_Symbol = "BTCUSD"; // Pepperstone may use: "BTCUSD", "BTC/USD", or "BTCUSD.c" +input ENUM_TIMEFRAMES RS_BTCUSD_TimeFrame = PERIOD_H1; +input int RS_BTCUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_BTCUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_BTCUSD_RSI_Overbought = 90; +input double RS_BTCUSD_RSI_Oversold = 73; +input double RS_BTCUSD_RSI_Target_Buy = 88; +input double RS_BTCUSD_RSI_Target_Sell = 48; +input int RS_BTCUSD_BarsToWait = 6; +input double RS_BTCUSD_LotSize = 0.1; +input int RS_BTCUSD_MagicNumber = 123459123; +input int RS_BTCUSD_Slippage = 3; + +input group "=== RSI Scalping NVDA - Pepperstone US ===" +input string RS_NVDA_Symbol = "NVDA.US"; // Try: "NVDA.US", "NASDAQ:NVDA", or "NVDA" +input ENUM_TIMEFRAMES RS_NVDA_TimeFrame = PERIOD_M15; +input int RS_NVDA_RSI_Period = 8; +input ENUM_APPLIED_PRICE RS_NVDA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_NVDA_RSI_Overbought = 36; +input double RS_NVDA_RSI_Oversold = 38; +input double RS_NVDA_RSI_Target_Buy = 90; +input double RS_NVDA_RSI_Target_Sell = 70; +input int RS_NVDA_BarsToWait = 5; +input double RS_NVDA_LotSize = 50; +input int RS_NVDA_MagicNumber = 20003; +input int RS_NVDA_Slippage = 3; + +input group "=== RSI Scalping TSLA - Pepperstone US ===" +input string RS_TSLA_Symbol = "TSLA.US"; // Try: "TSLA.US", "NASDAQ:TSLA", or "TSLA" +input ENUM_TIMEFRAMES RS_TSLA_TimeFrame = PERIOD_H1; +input int RS_TSLA_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_TSLA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_TSLA_RSI_Overbought = 54; +input double RS_TSLA_RSI_Oversold = 73; +input double RS_TSLA_RSI_Target_Buy = 87; +input double RS_TSLA_RSI_Target_Sell = 33; +input int RS_TSLA_BarsToWait = 1; +input double RS_TSLA_LotSize = 50; +input int RS_TSLA_MagicNumber = 125421321; +input int RS_TSLA_Slippage = 3; + +input group "=== RSI Scalping XAUUSD ===" +input string RS_XAUUSD_Symbol = "XAUUSD"; +input ENUM_TIMEFRAMES RS_XAUUSD_TimeFrame = PERIOD_H1; +input int RS_XAUUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_XAUUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_XAUUSD_RSI_Overbought = 71; +input double RS_XAUUSD_RSI_Oversold = 57; +input double RS_XAUUSD_RSI_Target_Buy = 80; +input double RS_XAUUSD_RSI_Target_Sell = 57; +input int RS_XAUUSD_BarsToWait = 4; +input double RS_XAUUSD_LotSize = 0.1; +input int RS_XAUUSD_MagicNumber = 129102315; +input int RS_XAUUSD_Slippage = 3; + +//+------------------------------------------------------------------+ +//| Global Variables - DarvasBox | +//+------------------------------------------------------------------+ +struct DarvasBoxData { + string symbol; + bool isInitialized; + double boxHigh; + double boxLow; + bool boxFormed; + datetime lastBoxTime; + string boxName; + double minStopLevel; + double point; + CTrade trade; + int maHandle; + int volumeHandle; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - EMA Slope Distance | +//+------------------------------------------------------------------+ +struct EMASlopeData { + string symbol; + bool isInitialized; + int ema_handle; + double ema_array[]; + datetime letzte_überwachung_zeit; + bool überwachung_aktiv; + bool preis_trigger_aktiv; + bool steigung_trigger_aktiv; + int ticket; + CTrade trade; + int trades_in_current_crossover; + bool crossover_detected; + datetime trade_open_time; + datetime last_bar_time; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI CrossOver Reversal | +//+------------------------------------------------------------------+ +struct RSICrossOverData { + string symbol; + bool isInitialized; + int rsiHandle; + int emaHandle; + double previousRSIDef; + CTrade trade; + datetime lastTradeTime; + datetime bartime; + bool WeekDays[7]; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI MidPoint Hijack | +//+------------------------------------------------------------------+ +struct RSIMidPointData { + string symbol; + bool isInitialized; + int rsiHandle; + int rsiReverseHandle; + int emaHandle; + bool rsiOverbought; + bool rsiOversold; + bool rsiReverseOverbought; + bool rsiReverseOversold; + CTrade trade; + CPositionInfo positionInfo; + bool emaCrossBuySignal; + bool emaCrossSellSignal; + int emaCrossSignalBar; + datetime lastBarTime; + datetime rsiReverseLastCloseTime; + bool rsiReverseInCooldown; + double lastBarRSI; + double lastBarRSIReverse; + double lastBarEMA; + double lastBarClose; + double lastBarEMAPrev; + double lastBarClosePrev; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI Scalping | +//+------------------------------------------------------------------+ +struct RSIScalpingData { + string symbol; + bool isInitialized; + CTrade trade; + int rsi_handle; + double rsi_buffer[]; + double rsi_prev; + double rsi_current; + double rsi_two_bars_ago; + bool position_open; + ulong position_ticket; + ENUM_POSITION_TYPE current_position_type; + datetime last_bar_time; + bool rsi_against_position; + int bars_against_count; +}; + +//+------------------------------------------------------------------+ +//| Global Strategy Instances | +//+------------------------------------------------------------------+ +DarvasBoxData dbData; +EMASlopeData esData; +RSICrossOverData rcData; +RSIMidPointData rmData; +RSIScalpingData rsAPPLData; +RSIScalpingData rsBTCUSDData; +RSIScalpingData rsNVDAData; +RSIScalpingData rsTSLAData; +RSIScalpingData rsXAUUSDData; + +//+------------------------------------------------------------------+ +//| Global Variables for Dynamic Lot Sizes | +//+------------------------------------------------------------------+ +// All strategies start with minimum lot size for safety (will be adjusted by performance evaluator) +double g_DB_LotSize = 0.01; // DarvasBox uses fixed lot size +double g_ES_LotSize = 0.01; // EMA Slope Distance - start with minimum +double g_RC_LotSize = 0.01; // RSI CrossOver Reversal - start with minimum +double g_RM_LotSize = 0.01; // RSI MidPoint Hijack - start with minimum +double g_RS_APPL_LotSize = 5.0; // Stock - start with stock minimum (5.0) +double g_RS_BTCUSD_LotSize = 0.01; // Crypto - start with forex minimum (0.01) +double g_RS_NVDA_LotSize = 5.0; // Stock - start with stock minimum (5.0) +double g_RS_TSLA_LotSize = 5.0; // Stock - start with stock minimum (5.0) +double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01) + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + int initResult = INIT_SUCCEEDED; + + // Initialize Performance Evaluator + InitPerformanceTracking(); + + // Initialize strategies - log warnings but don't fail entire EA if symbol unavailable + if(EnableDarvasBox) + { + if(!InitDarvasBox(DB_Symbol)) + Print("Warning: DarvasBox strategy failed to initialize for symbol '", DB_Symbol, "'"); + else + RegisterStrategy("DarvasBox", DB_MagicNumber, 0.01, DB_Symbol); // Fixed lot size + } + + if(EnableEMASlopeDistance) + { + if(!InitEMASlopeDistance(ES_Symbol)) + Print("Warning: EMASlopeDistance strategy failed to initialize for symbol '", ES_Symbol, "'"); + else + { + RegisterStrategy("EMASlopeDistance", ES_MagicNumber, ES_LotGröße, ES_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(ES_Symbol); + g_ES_LotSize = minLot; + } + } + + if(EnableRSICrossOverReversal) + { + if(!InitRSICrossOverReversal(RC_Symbol)) + Print("Warning: RSICrossOverReversal strategy failed to initialize for symbol '", RC_Symbol, "'"); + else + { + RegisterStrategy("RSICrossOverReversal", RC_MagicNumber, RC_lotSize, RC_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RC_Symbol); + g_RC_LotSize = minLot; + } + } + + if(EnableRSIMidPointHijack) + { + if(!InitRSIMidPointHijack(RM_Symbol)) + Print("Warning: RSIMidPointHijack strategy failed to initialize for symbol '", RM_Symbol, "'"); + else + { + RegisterStrategy("RSIMidPointHijack", RM_InpMagicNumberRSIFollow, RM_InpLotSize, RM_Symbol); + RegisterStrategy("RSIMidPointHijack_Reverse", RM_InpMagicNumberRSIReverse, RM_InpLotSize, RM_Symbol); + RegisterStrategy("RSIMidPointHijack_EMACross", RM_InpMagicNumberEMACross, RM_InpLotSize, RM_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RM_Symbol); + g_RM_LotSize = minLot; + } + } + + // Initialize RSI Scalping strategies - don't fail entire EA if symbol unavailable + if(EnableRSIScalpingAPPL) + { + InitRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, RS_APPL_MagicNumber, RS_APPL_Slippage); + RegisterStrategy("RSIScalpingAPPL", RS_APPL_MagicNumber, RS_APPL_LotSize, RS_APPL_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_APPL_Symbol); + g_RS_APPL_LotSize = minLot; + } + + if(EnableRSIScalpingBTCUSD) + { + InitRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, RS_BTCUSD_MagicNumber, RS_BTCUSD_Slippage); + RegisterStrategy("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber, RS_BTCUSD_LotSize, RS_BTCUSD_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_BTCUSD_Symbol); + g_RS_BTCUSD_LotSize = minLot; + } + + if(EnableRSIScalpingNVDA) + { + InitRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, RS_NVDA_MagicNumber, RS_NVDA_Slippage); + RegisterStrategy("RSIScalpingNVDA", RS_NVDA_MagicNumber, RS_NVDA_LotSize, RS_NVDA_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_NVDA_Symbol); + g_RS_NVDA_LotSize = minLot; + } + + if(EnableRSIScalpingTSLA) + { + InitRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, RS_TSLA_MagicNumber, RS_TSLA_Slippage); + RegisterStrategy("RSIScalpingTSLA", RS_TSLA_MagicNumber, RS_TSLA_LotSize, RS_TSLA_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_TSLA_Symbol); + g_RS_TSLA_LotSize = minLot; + } + + if(EnableRSIScalpingXAUUSD) + { + InitRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, RS_XAUUSD_MagicNumber, RS_XAUUSD_Slippage); + RegisterStrategy("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber, RS_XAUUSD_LotSize, RS_XAUUSD_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_XAUUSD_Symbol); + g_RS_XAUUSD_LotSize = minLot; + } + + // Load adjusted lot sizes from performance evaluator + if(PE_EnableAutoAdjustment) + { + double adjustedLot; + adjustedLot = GetStrategyLotSize("EMASlopeDistance", ES_MagicNumber); + if(adjustedLot > 0) g_ES_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSICrossOverReversal", RC_MagicNumber); + if(adjustedLot > 0) g_RC_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIMidPointHijack", RM_InpMagicNumberRSIFollow); + if(adjustedLot > 0) g_RM_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingAPPL", RS_APPL_MagicNumber); + if(adjustedLot > 0) g_RS_APPL_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber); + if(adjustedLot > 0) g_RS_BTCUSD_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingNVDA", RS_NVDA_MagicNumber); + if(adjustedLot > 0) g_RS_NVDA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingTSLA", RS_TSLA_MagicNumber); + if(adjustedLot > 0) g_RS_TSLA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber); + if(adjustedLot > 0) g_RS_XAUUSD_LotSize = adjustedLot; + } + + Print("United EA initialized. Active strategies: ", + (EnableDarvasBox ? "DarvasBox " : ""), + (EnableEMASlopeDistance ? "EMASlope " : ""), + (EnableRSICrossOverReversal ? "RSICrossOver " : ""), + (EnableRSIMidPointHijack ? "RSIMidPoint " : ""), + (EnableRSIScalpingAPPL ? "RSIScalpingAPPL " : ""), + (EnableRSIScalpingBTCUSD ? "RSIScalpingBTCUSD " : ""), + (EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""), + (EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""), + (EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : "")); + + if(PE_EnableLogging) + Print(GetPerformanceSummary()); + + return initResult; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(EnableDarvasBox) + DeinitDarvasBox(); + + if(EnableEMASlopeDistance) + DeinitEMASlopeDistance(); + + if(EnableRSICrossOverReversal) + DeinitRSICrossOverReversal(); + + if(EnableRSIMidPointHijack) + DeinitRSIMidPointHijack(); + + if(EnableRSIScalpingAPPL) + DeinitRSIScalping(rsAPPLData); + + if(EnableRSIScalpingBTCUSD) + DeinitRSIScalping(rsBTCUSDData); + + if(EnableRSIScalpingNVDA) + DeinitRSIScalping(rsNVDAData); + + if(EnableRSIScalpingTSLA) + DeinitRSIScalping(rsTSLAData); + + if(EnableRSIScalpingXAUUSD) + DeinitRSIScalping(rsXAUUSDData); + + Print("United EA deinitialized. Reason: ", reason); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Process performance evaluation (checks for quarter end and adjusts lot sizes) + ProcessPerformanceEvaluation(); + + // Update lot sizes from performance evaluator if auto-adjustment is enabled + if(PE_EnableAutoAdjustment) + { + double adjustedLot; + adjustedLot = GetStrategyLotSize("EMASlopeDistance", ES_MagicNumber); + if(adjustedLot > 0) g_ES_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSICrossOverReversal", RC_MagicNumber); + if(adjustedLot > 0) g_RC_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIMidPointHijack", RM_InpMagicNumberRSIFollow); + if(adjustedLot > 0) g_RM_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingAPPL", RS_APPL_MagicNumber); + if(adjustedLot > 0) g_RS_APPL_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber); + if(adjustedLot > 0) g_RS_BTCUSD_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingNVDA", RS_NVDA_MagicNumber); + if(adjustedLot > 0) g_RS_NVDA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingTSLA", RS_TSLA_MagicNumber); + if(adjustedLot > 0) g_RS_TSLA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber); + if(adjustedLot > 0) g_RS_XAUUSD_LotSize = adjustedLot; + } + + if(EnableDarvasBox) + ProcessDarvasBox(DB_Symbol); + + if(EnableEMASlopeDistance) + ProcessEMASlopeDistance(ES_Symbol); + + if(EnableRSICrossOverReversal) + ProcessRSICrossOverReversal(RC_Symbol); + + if(EnableRSIMidPointHijack) + ProcessRSIMidPointHijack(RM_Symbol); + + if(EnableRSIScalpingAPPL) + ProcessRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, + RS_APPL_RSI_Overbought, RS_APPL_RSI_Oversold, RS_APPL_RSI_Target_Buy, RS_APPL_RSI_Target_Sell, + RS_APPL_BarsToWait, g_RS_APPL_LotSize, RS_APPL_MagicNumber); + + if(EnableRSIScalpingBTCUSD) + ProcessRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, + RS_BTCUSD_RSI_Overbought, RS_BTCUSD_RSI_Oversold, RS_BTCUSD_RSI_Target_Buy, RS_BTCUSD_RSI_Target_Sell, + RS_BTCUSD_BarsToWait, g_RS_BTCUSD_LotSize, RS_BTCUSD_MagicNumber); + + if(EnableRSIScalpingNVDA) + ProcessRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, + RS_NVDA_RSI_Overbought, RS_NVDA_RSI_Oversold, RS_NVDA_RSI_Target_Buy, RS_NVDA_RSI_Target_Sell, + RS_NVDA_BarsToWait, g_RS_NVDA_LotSize, RS_NVDA_MagicNumber); + + if(EnableRSIScalpingTSLA) + ProcessRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, + RS_TSLA_RSI_Overbought, RS_TSLA_RSI_Oversold, RS_TSLA_RSI_Target_Buy, RS_TSLA_RSI_Target_Sell, + RS_TSLA_BarsToWait, g_RS_TSLA_LotSize, RS_TSLA_MagicNumber); + + if(EnableRSIScalpingXAUUSD) + ProcessRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, + RS_XAUUSD_RSI_Overbought, RS_XAUUSD_RSI_Oversold, RS_XAUUSD_RSI_Target_Buy, RS_XAUUSD_RSI_Target_Sell, + RS_XAUUSD_BarsToWait, g_RS_XAUUSD_LotSize, RS_XAUUSD_MagicNumber); +} + +//+------------------------------------------------------------------+ +//| Include strategy implementations | +//+------------------------------------------------------------------+ +#include "Strategies/DarvasBoxStrategy.mqh" +#include "Strategies/EMASlopeDistanceStrategy.mqh" +#include "Strategies/RSICrossOverReversalStrategy.mqh" +#include "Strategies/RSIMidPointHijackStrategy.mqh" +#include "Strategies/RSIScalpingStrategy.mqh" + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/MagicNumberHelpers.mqh b/frontline/MQL5/_united_dynamic_cent/MagicNumberHelpers.mqh new file mode 100644 index 0000000..dc1fa31 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/MagicNumberHelpers.mqh @@ -0,0 +1,159 @@ +//+------------------------------------------------------------------+ +//| 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" + +//+------------------------------------------------------------------+ +//| Select position by symbol and magic number | +//+------------------------------------------------------------------+ +bool PositionSelectByMagic(string symbol, ulong magic_number) +{ + // First try to find position by symbol + if(!PositionSelect(symbol)) + return false; + + // Check if the selected position has the correct magic number + if(PositionGetInteger(POSITION_MAGIC) != magic_number) + { + // Position exists but wrong magic number, search all positions + 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; +} + +//+------------------------------------------------------------------+ +//| Select position by ticket and verify magic number and symbol | +//+------------------------------------------------------------------+ +bool PositionSelectByTicketAndMagic(ulong ticket, ulong magic_number) +{ + if(!PositionSelectByTicket(ticket)) + return false; + + return (PositionGetInteger(POSITION_MAGIC) == magic_number); +} + +//+------------------------------------------------------------------+ +//| Select position by ticket and verify symbol, magic number | +//+------------------------------------------------------------------+ +bool PositionSelectByTicketSymbolAndMagic(ulong ticket, string symbol, ulong magic_number) +{ + if(!PositionSelectByTicket(ticket)) + return false; + + return (PositionGetString(POSITION_SYMBOL) == symbol && + PositionGetInteger(POSITION_MAGIC) == magic_number); +} + +//+------------------------------------------------------------------+ +//| Check if position exists with correct magic number | +//+------------------------------------------------------------------+ +bool PositionExistsByMagic(string symbol, ulong magic_number) +{ + return PositionSelectByMagic(symbol, magic_number); +} + +//+------------------------------------------------------------------+ +//| Get position ticket by symbol and magic number | +//+------------------------------------------------------------------+ +ulong GetPositionTicketByMagic(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 ticket; + } + } + } + return 0; +} + +//+------------------------------------------------------------------+ +//| Close position by symbol and magic number | +//+------------------------------------------------------------------+ +bool ClosePositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number) +{ + ulong ticket = GetPositionTicketByMagic(symbol, magic_number); + if(ticket == 0) + return false; + + return trade_obj.PositionClose(ticket); +} + +//+------------------------------------------------------------------+ +//| Modify position by symbol and magic number | +//+------------------------------------------------------------------+ +bool ModifyPositionByMagic(CTrade &trade_obj, string symbol, ulong magic_number, + double sl, double tp) +{ + ulong ticket = GetPositionTicketByMagic(symbol, magic_number); + if(ticket == 0) + return false; + + return trade_obj.PositionModify(ticket, sl, tp); +} + +//+------------------------------------------------------------------+ +//| Get position profit by symbol and magic number | +//+------------------------------------------------------------------+ +double GetPositionProfitByMagic(string symbol, ulong magic_number) +{ + if(!PositionSelectByMagic(symbol, magic_number)) + return 0.0; + + return PositionGetDouble(POSITION_PROFIT); +} + +//+------------------------------------------------------------------+ +//| Get position type by symbol and magic number | +//+------------------------------------------------------------------+ +ENUM_POSITION_TYPE GetPositionTypeByMagic(string symbol, ulong magic_number) +{ + if(!PositionSelectByMagic(symbol, magic_number)) + return WRONG_VALUE; + + return (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); +} + +//+------------------------------------------------------------------+ +//| Count positions by symbol and magic number | +//+------------------------------------------------------------------+ +int CountPositionsByMagic(string symbol, ulong magic_number) +{ + int count = 0; + 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) + { + count++; + } + } + } + return count; +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/PEPPERSTONE_US_SETUP.md b/frontline/MQL5/_united_dynamic_cent/PEPPERSTONE_US_SETUP.md new file mode 100644 index 0000000..82dc835 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/PEPPERSTONE_US_SETUP.md @@ -0,0 +1,75 @@ +# Pepperstone US - Symbol Setup Guide + +## Finding Correct Symbol Names in MetaTrader 5 + +### Step-by-Step Instructions: + +1. **Open Market Watch Window** + - Press `Ctrl+M` or go to `View > Market Watch` + +2. **Show All Symbols** + - Right-click in the Market Watch window + - Select `Show All` or `Symbols` + - This shows all available symbols from your broker + +3. **Search for Your Symbols** + - Use the search box in the Market Watch window + - Search for: "AAPL", "MSFT", "NVDA", "TSLA", "BTCUSD", "XAUUSD" + +4. **Note the Exact Symbol Name** + - The symbol name shown in Market Watch is what you need to use + - Common formats for Pepperstone US: + - Stocks: `AAPL.US`, `MSFT.US`, `NVDA.US`, `TSLA.US` + - Or: `NASDAQ:AAPL`, `NASDAQ:MSFT`, etc. + - Or: Just `AAPL`, `MSFT`, etc. (if available) + +5. **Add to Market Watch** + - Double-click the symbol to add it to your Market Watch + - Or right-click and select `Show` + +6. **Update EA Inputs** + - Open the EA inputs in MetaTrader 5 + - Update each symbol parameter with the exact name from Market Watch + +## Common Pepperstone US Symbol Formats + +### US Stocks: +- **Apple**: `AAPL.US` or `NASDAQ:AAPL` or `AAPL` +- **Microsoft**: `MSFT.US` or `NASDAQ:MSFT` or `MSFT` +- **NVIDIA**: `NVDA.US` or `NASDAQ:NVDA` or `NVDA` +- **Tesla**: `TSLA.US` or `NASDAQ:TSLA` or `TSLA` + +### Cryptocurrencies: +- **Bitcoin**: `BTCUSD` or `BTC/USD` or `BTCUSD.c` + +### Precious Metals: +- **Gold**: `XAUUSD` or `GOLD` or `XAU/USD` + +## Important Notes: + +1. **Symbol Names are Case-Sensitive**: Use exact capitalization +2. **Add Symbols to Market Watch**: Symbols must be in Market Watch for the EA to access them +3. **Check Trading Hours**: US stocks trade during US market hours (9:30 AM - 4:00 PM ET) +4. **CFD vs Stock**: Pepperstone offers CFDs on stocks, not actual stocks +5. **Spread**: Check the spread for each symbol - some may have wider spreads + +## Troubleshooting: + +### If Symbol Not Found: +1. Check if you're connected to Pepperstone US server +2. Verify your account type supports the symbol +3. Contact Pepperstone support for symbol availability +4. Check if symbol requires special account permissions + +### If EA Shows "Symbol Not Available": +1. Make sure symbol is added to Market Watch +2. Verify symbol name matches exactly (including dots, colons, etc.) +3. Check broker connection status +4. Try different symbol format variations + +## Testing Symbols: + +You can test if a symbol works by: +1. Opening a chart with that symbol +2. If chart opens successfully, the symbol name is correct +3. Use that exact symbol name in the EA inputs diff --git a/frontline/MQL5/_united_dynamic_cent/PerformanceEvaluator.mqh b/frontline/MQL5/_united_dynamic_cent/PerformanceEvaluator.mqh new file mode 100644 index 0000000..1389540 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/PerformanceEvaluator.mqh @@ -0,0 +1,607 @@ +//+------------------------------------------------------------------+ +//| PerformanceEvaluator.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" + +//+------------------------------------------------------------------+ +//| Performance Metrics Structure | +//+------------------------------------------------------------------+ +struct StrategyPerformance { + string strategyName; + string symbol; // Store symbol to determine if it's a stock + int magicNumber; + double initialLotSize; + double currentLotSize; + double quarterProfit; + double quarterTrades; + double quarterWins; + double quarterLosses; + double maxDrawdown; + double winRate; + datetime quarterStart; + datetime quarterEnd; + bool isActive; + bool inPenaltyMode; // True if strategy is in penalty (worst performer) + double lotSizeBeforePenalty; // Store lot size before penalty + datetime penaltyStartTime; // When penalty started +}; + +//+------------------------------------------------------------------+ +//| Global Performance Tracking | +//+------------------------------------------------------------------+ +StrategyPerformance strategyPerformances[]; +int totalStrategies = 0; +datetime lastMonthCheck = 0; +datetime currentMonthStart = 0; +datetime currentMonthEnd = 0; + +//+------------------------------------------------------------------+ +//| Performance Adjustment Parameters | +//+------------------------------------------------------------------+ +input group "=== Performance Evaluation Settings ===" +input bool PE_EnableAutoAdjustment = true; // Enable automatic lot size adjustment +input double PE_LotSizeIncreasePercent = 10.0; // % increase for top-ranked strategies +input double PE_LotSizeDecreasePercent = 10.0; // % decrease for bottom-ranked strategies +input double PE_MinLotSize = 0.01; // Minimum lot size for forex/crypto +input double PE_MinLotSizeStocks = 5.0; // Minimum lot size for stocks (5-10 range) +input double PE_MaxLotSize = 100.0; // Maximum lot size after adjustment +input int PE_TopPerformersCount = 3; // Number of top strategies to increase lot size +input int PE_BottomPerformersCount = 3; // Number of bottom strategies to decrease lot size +input bool PE_UseWinRateWeight = true; // Consider win rate in ranking (50% profit, 50% win rate) +input bool PE_EnableBlitzPlay = true; // Enable blitz play: worst performer gets minimum lot size penalty +input bool PE_EnableLogging = true; // Enable performance logging + +//+------------------------------------------------------------------+ +//| Initialize Performance Tracking | +//+------------------------------------------------------------------+ +void InitPerformanceTracking() +{ + // Calculate current month dates + MqlDateTime dt; + TimeToStruct(TimeCurrent(), dt); + + // Determine month start (first day of current month) + dt.day = 1; + dt.hour = 0; + dt.min = 0; + dt.sec = 0; + currentMonthStart = StructToTime(dt); + + // Calculate month end (first day of next month - 1 second) + dt.mon += 1; + if(dt.mon > 12) + { + dt.mon = 1; + dt.year++; + } + currentMonthEnd = StructToTime(dt) - 1; // End of last day of month + + lastMonthCheck = TimeCurrent(); + + if(PE_EnableLogging) + { + Print("Performance Evaluator: Initialized"); + Print("Current Month Start: ", TimeToString(currentMonthStart)); + Print("Current Month End: ", TimeToString(currentMonthEnd)); + } +} + +//+------------------------------------------------------------------+ +//| Check if Symbol is a Stock | +//+------------------------------------------------------------------+ +bool IsStockSymbol(string symbol) +{ + // Check if symbol contains common stock indicators + if(StringFind(symbol, ".US") >= 0) return true; + if(StringFind(symbol, "NASDAQ:") >= 0) return true; + if(StringFind(symbol, "NYSE:") >= 0) return true; + + // Note: Symbol category check removed to avoid enum conversion issues + // String-based checks (.US, NASDAQ:, NYSE:, common tickers) are sufficient + + // Common stock tickers (without .US suffix) + string commonStocks[] = {"AAPL", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "AMD", "NFLX"}; + for(int i = 0; i < ArraySize(commonStocks); i++) + { + if(StringFind(symbol, commonStocks[i]) == 0) return true; + } + + return false; +} + +//+------------------------------------------------------------------+ +//| Get Minimum Lot Size for Symbol | +//+------------------------------------------------------------------+ +double GetMinLotSizeForSymbol(string symbol) +{ + if(IsStockSymbol(symbol)) + return PE_MinLotSizeStocks; + else + return PE_MinLotSize; +} + +//+------------------------------------------------------------------+ +//| Register Strategy for Performance Tracking | +//+------------------------------------------------------------------+ +void RegisterStrategy(string strategyName, int magicNumber, double initialLotSize, string symbol = "") +{ + // Check if strategy already registered + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].strategyName == strategyName && + strategyPerformances[i].magicNumber == magicNumber) + { + if(PE_EnableLogging) + Print("Performance Evaluator: Strategy '", strategyName, "' already registered"); + return; + } + } + + // Add new strategy + int newSize = ArraySize(strategyPerformances) + 1; + ArrayResize(strategyPerformances, newSize); + + strategyPerformances[newSize - 1].strategyName = strategyName; + strategyPerformances[newSize - 1].symbol = symbol; + strategyPerformances[newSize - 1].magicNumber = magicNumber; + strategyPerformances[newSize - 1].initialLotSize = initialLotSize; + // Start with minimum lot size for safety (symbol-specific minimum) + double minLot = GetMinLotSizeForSymbol(symbol); + strategyPerformances[newSize - 1].currentLotSize = minLot; + strategyPerformances[newSize - 1].quarterProfit = 0.0; + strategyPerformances[newSize - 1].quarterTrades = 0; + strategyPerformances[newSize - 1].quarterWins = 0; + strategyPerformances[newSize - 1].quarterLosses = 0; + strategyPerformances[newSize - 1].maxDrawdown = 0.0; + strategyPerformances[newSize - 1].winRate = 0.0; + strategyPerformances[newSize - 1].quarterStart = currentMonthStart; + strategyPerformances[newSize - 1].quarterEnd = currentMonthEnd; + strategyPerformances[newSize - 1].isActive = true; + strategyPerformances[newSize - 1].inPenaltyMode = false; + strategyPerformances[newSize - 1].lotSizeBeforePenalty = initialLotSize; + strategyPerformances[newSize - 1].penaltyStartTime = 0; + + totalStrategies = newSize; + + if(PE_EnableLogging) + Print("Performance Evaluator: Registered strategy '", strategyName, + "' (Magic: ", magicNumber, ", Initial Lot: ", initialLotSize, ")"); +} + +//+------------------------------------------------------------------+ +//| Update Strategy Performance Metrics | +//+------------------------------------------------------------------+ +void UpdateStrategyPerformance(string strategyName, int magicNumber) +{ + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].strategyName == strategyName && + strategyPerformances[i].magicNumber == magicNumber && + strategyPerformances[i].isActive) + { + // Calculate performance for current quarter + double totalProfit = 0.0; + int totalTrades = 0; + int wins = 0; + int losses = 0; + double maxDD = 0.0; + double peakBalance = 0.0; + + // Scan all closed deals in current quarter + datetime quarterStart = strategyPerformances[i].quarterStart; + datetime quarterEnd = strategyPerformances[i].quarterEnd; + + // Select history for the quarter + if(HistorySelect(quarterStart, quarterEnd)) + { + int totalDeals = HistoryDealsTotal(); + for(int j = 0; j < totalDeals; j++) + { + ulong ticket = HistoryDealGetTicket(j); + if(ticket > 0) + { + long dealMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC); + if(dealMagic == magicNumber) + { + double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); + double swap = HistoryDealGetDouble(ticket, DEAL_SWAP); + double commission = HistoryDealGetDouble(ticket, DEAL_COMMISSION); + double totalDealProfit = profit + swap + commission; + + totalProfit += totalDealProfit; + totalTrades++; + + if(totalDealProfit > 0) + wins++; + else if(totalDealProfit < 0) + losses++; + } + } + } + } + + // Calculate win rate + double winRate = 0.0; + if(totalTrades > 0) + winRate = (double)wins / (double)totalTrades * 100.0; + + // Update metrics + strategyPerformances[i].quarterProfit = totalProfit; + strategyPerformances[i].quarterTrades = totalTrades; + strategyPerformances[i].quarterWins = wins; + strategyPerformances[i].quarterLosses = losses; + strategyPerformances[i].winRate = winRate; + + break; + } + } +} + +//+------------------------------------------------------------------+ +//| Strategy Ranking Structure | +//+------------------------------------------------------------------+ +struct StrategyRank { + int index; + double score; +}; + +//+------------------------------------------------------------------+ +//| Calculate Strategy Score for Ranking | +//+------------------------------------------------------------------+ +double CalculateStrategyScore(int strategyIndex) +{ + double profit = strategyPerformances[strategyIndex].quarterProfit; + double winRate = strategyPerformances[strategyIndex].winRate; + double trades = strategyPerformances[strategyIndex].quarterTrades; + + // Normalize profit (scale to 0-100 range, assuming max profit of $1000) + double normalizedProfit = MathMin(profit / 10.0, 100.0); + if(profit < 0) normalizedProfit = profit / 5.0; // Penalize losses more + + // Calculate score + double score = 0.0; + if(PE_UseWinRateWeight) + { + // 50% profit, 50% win rate (if enough trades) + if(trades >= 5) + score = (normalizedProfit * 0.5) + (winRate * 0.5); + else + score = normalizedProfit; // Not enough trades, use profit only + } + else + { + // Profit only + score = normalizedProfit; + } + + return score; +} + +//+------------------------------------------------------------------+ +//| Check if Month Ended and Evaluate Performance | +//+------------------------------------------------------------------+ +void CheckMonthEnd() +{ + datetime now = TimeCurrent(); + + // Check if we've entered a new month + if(now >= currentMonthEnd) + { + if(PE_EnableLogging) + Print("Performance Evaluator: Month ended. Evaluating and ranking strategies..."); + + // Update performance metrics for all strategies + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + UpdateStrategyPerformance(strategyPerformances[i].strategyName, + strategyPerformances[i].magicNumber); + } + } + + // Rank strategies + int activeCount = 0; + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + activeCount++; + } + + if(activeCount > 0) + { + // Create ranking array + StrategyRank ranks[]; + ArrayResize(ranks, activeCount); + int rankIndex = 0; + + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + ranks[rankIndex].index = i; + ranks[rankIndex].score = CalculateStrategyScore(i); + rankIndex++; + } + } + + // Sort by score (descending - highest score first) + for(int i = 0; i < activeCount - 1; i++) + { + for(int j = i + 1; j < activeCount; j++) + { + if(ranks[j].score > ranks[i].score) + { + StrategyRank temp = ranks[i]; + ranks[i] = ranks[j]; + ranks[j] = temp; + } + } + } + + // Adjust lot sizes based on ranking + if(PE_EnableAutoAdjustment) + { + // Increase top performers (skip if in penalty mode) + int topCount = MathMin(PE_TopPerformersCount, activeCount); + for(int i = 0; i < topCount; i++) + { + int strategyIdx = ranks[i].index; + + // Skip if strategy is in penalty mode + if(strategyPerformances[strategyIdx].inPenaltyMode) + continue; + + double oldLotSize = strategyPerformances[strategyIdx].currentLotSize; + double newLotSize = oldLotSize * (1.0 + PE_LotSizeIncreasePercent / 100.0); + + if(newLotSize > PE_MaxLotSize) + newLotSize = PE_MaxLotSize; + + strategyPerformances[strategyIdx].currentLotSize = newLotSize; + + if(PE_EnableLogging) + Print("Performance Evaluator: Rank #", (i+1), " - Increasing '", + strategyPerformances[strategyIdx].strategyName, + "' lot size from ", oldLotSize, " to ", newLotSize, + " (Score: ", DoubleToString(ranks[i].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2), + ", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%)"); + } + + // Decrease bottom performers (skip worst one if blitz play is enabled) + int bottomCount = MathMin(PE_BottomPerformersCount, activeCount); + int startIdx = activeCount - bottomCount; + + // If blitz play is enabled, skip the worst performer (it will get minimum penalty) + if(PE_EnableBlitzPlay && activeCount > 0) + startIdx = activeCount - bottomCount + 1; + + for(int i = startIdx; i < activeCount; i++) + { + int strategyIdx = ranks[i].index; + + // Skip if strategy is in penalty mode + if(strategyPerformances[strategyIdx].inPenaltyMode) + continue; + + double oldLotSize = strategyPerformances[strategyIdx].currentLotSize; + double newLotSize = oldLotSize * (1.0 - PE_LotSizeDecreasePercent / 100.0); + + // Use symbol-specific minimum lot size + double minLot = GetMinLotSizeForSymbol(strategyPerformances[strategyIdx].symbol); + if(newLotSize < minLot) + newLotSize = minLot; + + strategyPerformances[strategyIdx].currentLotSize = newLotSize; + + if(PE_EnableLogging) + Print("Performance Evaluator: Rank #", (i+1), " - Decreasing '", + strategyPerformances[strategyIdx].strategyName, + "' lot size from ", oldLotSize, " to ", newLotSize, + " (Score: ", DoubleToString(ranks[i].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2), + ", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%)"); + } + } + + // Blitz Play: Apply penalty to worst performer + if(PE_EnableBlitzPlay && activeCount > 0) + { + // Find worst performer (last in ranking) + int worstIdx = ranks[activeCount - 1].index; + + // Remove penalty from previous worst performer (if any) + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive && strategyPerformances[i].inPenaltyMode) + { + // Check if penalty period has passed (one month) + if(now - strategyPerformances[i].penaltyStartTime >= 2592000) // ~30 days + { + // Restore lot size to before penalty + strategyPerformances[i].currentLotSize = strategyPerformances[i].lotSizeBeforePenalty; + strategyPerformances[i].inPenaltyMode = false; + strategyPerformances[i].penaltyStartTime = 0; + + if(PE_EnableLogging) + Print("Blitz Play: Penalty removed from '", strategyPerformances[i].strategyName, + "'. Lot size restored to ", strategyPerformances[i].currentLotSize); + } + } + } + + // Apply penalty to new worst performer + if(!strategyPerformances[worstIdx].inPenaltyMode) + { + strategyPerformances[worstIdx].lotSizeBeforePenalty = strategyPerformances[worstIdx].currentLotSize; + // Use symbol-specific minimum lot size + double minLot = GetMinLotSizeForSymbol(strategyPerformances[worstIdx].symbol); + strategyPerformances[worstIdx].currentLotSize = minLot; + strategyPerformances[worstIdx].inPenaltyMode = true; + strategyPerformances[worstIdx].penaltyStartTime = now; + + if(PE_EnableLogging) + Print("Blitz Play: WORST PERFORMER - '", strategyPerformances[worstIdx].strategyName, + "' penalized! Lot size reduced from ", strategyPerformances[worstIdx].lotSizeBeforePenalty, + " to minimum ", minLot, " (Score: ", DoubleToString(ranks[activeCount - 1].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[worstIdx].quarterProfit, 2), ")"); + } + } + + // Log performance report + if(PE_EnableLogging) + { + Print("=== Monthly Performance Ranking ==="); + for(int i = 0; i < activeCount; i++) + { + int strategyIdx = ranks[i].index; + Print("Rank #", (i+1), ": ", strategyPerformances[strategyIdx].strategyName, + " - Score: ", DoubleToString(ranks[i].score, 2), + ", Profit: $", DoubleToString(strategyPerformances[strategyIdx].quarterProfit, 2), + ", Win Rate: ", DoubleToString(strategyPerformances[strategyIdx].winRate, 2), "%", + ", Trades: ", (int)strategyPerformances[strategyIdx].quarterTrades, + ", Lot Size: ", DoubleToString(strategyPerformances[strategyIdx].currentLotSize, 2)); + } + Print("==================================="); + } + } + + // Reset month metrics for all strategies + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + strategyPerformances[i].quarterProfit = 0.0; + strategyPerformances[i].quarterTrades = 0; + strategyPerformances[i].quarterWins = 0; + strategyPerformances[i].quarterLosses = 0; + strategyPerformances[i].maxDrawdown = 0.0; + strategyPerformances[i].winRate = 0.0; + } + } + + // Update month dates + MqlDateTime dt; + TimeToStruct(now, dt); + + // First day of current month + dt.day = 1; + dt.hour = 0; + dt.min = 0; + dt.sec = 0; + currentMonthStart = StructToTime(dt); + + // First day of next month - 1 second + dt.mon += 1; + if(dt.mon > 12) + { + dt.mon = 1; + dt.year++; + } + currentMonthEnd = StructToTime(dt) - 1; + + // Update month dates for all strategies + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + strategyPerformances[i].quarterStart = currentMonthStart; + strategyPerformances[i].quarterEnd = currentMonthEnd; + } + + lastMonthCheck = now; + } +} + +//+------------------------------------------------------------------+ +//| Get Current Lot Size for Strategy | +//+------------------------------------------------------------------+ +double GetStrategyLotSize(string strategyName, int magicNumber) +{ + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].strategyName == strategyName && + strategyPerformances[i].magicNumber == magicNumber && + strategyPerformances[i].isActive) + { + return strategyPerformances[i].currentLotSize; + } + } + return 0.0; +} + +//+------------------------------------------------------------------+ +//| Process Performance Evaluation (call from OnTick) | +//+------------------------------------------------------------------+ +void ProcessPerformanceEvaluation() +{ + // Check if month ended + CheckMonthEnd(); + + // Check for penalty expiration (blitz play) + if(PE_EnableBlitzPlay) + { + datetime now = TimeCurrent(); + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive && strategyPerformances[i].inPenaltyMode) + { + // Check if penalty period has passed (one month = ~30 days) + if(now - strategyPerformances[i].penaltyStartTime >= 2592000) + { + // Restore lot size to before penalty + strategyPerformances[i].currentLotSize = strategyPerformances[i].lotSizeBeforePenalty; + strategyPerformances[i].inPenaltyMode = false; + strategyPerformances[i].penaltyStartTime = 0; + + if(PE_EnableLogging) + Print("Blitz Play: Penalty expired for '", strategyPerformances[i].strategyName, + "'. Lot size restored to ", strategyPerformances[i].currentLotSize); + } + } + } + } + + // Update performance metrics periodically (every hour) + static datetime lastUpdate = 0; + if(TimeCurrent() - lastUpdate >= 3600) + { + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + UpdateStrategyPerformance(strategyPerformances[i].strategyName, + strategyPerformances[i].magicNumber); + } + } + lastUpdate = TimeCurrent(); + } +} + +//+------------------------------------------------------------------+ +//| Get Performance Summary | +//+------------------------------------------------------------------+ +string GetPerformanceSummary() +{ + string summary = "\n=== Performance Summary ===\n"; + summary += "Current Month: " + TimeToString(currentMonthStart) + " to " + TimeToString(currentMonthEnd) + "\n\n"; + + for(int i = 0; i < ArraySize(strategyPerformances); i++) + { + if(strategyPerformances[i].isActive) + { + summary += strategyPerformances[i].strategyName + ":\n"; + summary += " Profit: $" + DoubleToString(strategyPerformances[i].quarterProfit, 2) + "\n"; + summary += " Trades: " + IntegerToString((int)strategyPerformances[i].quarterTrades) + "\n"; + summary += " Win Rate: " + DoubleToString(strategyPerformances[i].winRate, 2) + "%\n"; + summary += " Lot Size: " + DoubleToString(strategyPerformances[i].currentLotSize, 2) + "\n\n"; + } + } + + return summary; +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/STRATEGY_CONFIGURATION.md b/frontline/MQL5/_united_dynamic_cent/STRATEGY_CONFIGURATION.md new file mode 100644 index 0000000..799c760 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/STRATEGY_CONFIGURATION.md @@ -0,0 +1,76 @@ +# United EA Strategy Configuration Summary + +## Strategy Symbols and Magic Numbers + +### Strategy 1: DarvasBox +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 135790 + +### Strategy 2: EMASlopeDistance +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 12350 + +### Strategy 3: RSICrossOverReversal +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 7 + +### Strategy 4: RSIMidPointHijack +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Numbers**: + - RSIFollow: 1001 + - RSIReverse: 1002 + - EMACross: 1003 + +### Strategy 5: RSI Scalping APPL (Apple) +- **Symbol**: AAPL (Apple stock) +- **Magic Number**: 20001 +- **Note**: Changed from "APPL" to "AAPL" (correct ticker symbol) + +### Strategy 6: RSI Scalping BTCUSD +- **Symbol**: BTCUSD (Bitcoin/USD) +- **Magic Number**: 123459123 + +### Strategy 7: RSI Scalping MSFT +- **Symbol**: MSFT (Microsoft stock) +- **Magic Number**: 20002 + +### Strategy 8: RSI Scalping NVDA +- **Symbol**: NVDA (NVIDIA stock) +- **Magic Number**: 20003 + +### Strategy 9: RSI Scalping TSLA +- **Symbol**: TSLA (Tesla stock) +- **Magic Number**: 125421321 + +### Strategy 10: RSI Scalping XAUUSD +- **Symbol**: XAUUSD (Gold/USD) +- **Magic Number**: 129102315 + +## Important Notes + +1. **Stock Symbols**: Stock symbols (AAPL, MSFT, NVDA, TSLA) must be: + - Added to Market Watch in MetaTrader 5 + - Available from your broker + - Use the correct ticker symbol (e.g., "AAPL" not "APPL") + +2. **Magic Numbers**: All strategies have unique magic numbers to prevent interference: + - Each strategy can be identified by its magic number + - RSIMidPointHijack uses 3 magic numbers (one for each sub-strategy) + +3. **Symbol Configuration**: Each strategy trades on its own symbol: + - You can change symbols in the input parameters + - The EA will log warnings if a symbol is not available + - Strategies with unavailable symbols will be skipped (EA continues running) + +4. **RSI Scalping Strategies**: + - Each RSI Scalping variant trades on a different symbol + - They all use the same strategy logic but with different parameters + - Buy and sell signals are generated based on RSI levels for each symbol + +## Troubleshooting + +If stock symbols are not working: +1. Check if the symbol exists in your broker's symbol list +2. Add the symbol to Market Watch in MetaTrader 5 +3. Verify the symbol name matches your broker's naming convention +4. Some brokers use prefixes/suffixes (e.g., "NASDAQ:AAPL" or "AAPL.US") diff --git a/frontline/MQL5/_united_dynamic_cent/Strategies/DarvasBoxStrategy.mqh b/frontline/MQL5/_united_dynamic_cent/Strategies/DarvasBoxStrategy.mqh new file mode 100644 index 0000000..ff1d13b --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/Strategies/DarvasBoxStrategy.mqh @@ -0,0 +1,300 @@ +//+------------------------------------------------------------------+ +//| DarvasBoxStrategy.mqh | +//+------------------------------------------------------------------+ + +bool InitDarvasBox(string symbol) +{ + dbData.symbol = symbol; + dbData.boxHigh = 0; + dbData.boxLow = 0; + dbData.boxFormed = false; + dbData.lastBoxTime = 0; + dbData.boxName = "DarvasBox_" + IntegerToString(DB_MagicNumber) + "_"; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("DarvasBox: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + dbData.point = SymbolInfoDouble(symbol, SYMBOL_POINT); + dbData.minStopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * dbData.point; + + dbData.maHandle = iMA(symbol, DB_TrendTimeframe, DB_MA_Period, 0, DB_MA_Method, DB_MA_Price); + dbData.volumeHandle = iVolumes(symbol, PERIOD_CURRENT, VOLUME_TICK); + + if(dbData.maHandle == INVALID_HANDLE || dbData.volumeHandle == INVALID_HANDLE) + { + Print("DarvasBox: Error creating indicators for '", symbol, "'"); + return false; + } + + dbData.trade.SetDeviationInPoints(10); + dbData.trade.SetTypeFilling(ORDER_FILLING_IOC); + dbData.trade.SetAsyncMode(false); + dbData.trade.SetExpertMagicNumber(DB_MagicNumber); + + ObjectsDeleteAll(0, dbData.boxName); + dbData.isInitialized = true; + Print("DarvasBox: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitDarvasBox() +{ + if(dbData.maHandle != INVALID_HANDLE) IndicatorRelease(dbData.maHandle); + if(dbData.volumeHandle != INVALID_HANDLE) IndicatorRelease(dbData.volumeHandle); + ObjectsDeleteAll(0, dbData.boxName); +} + +void DrawDarvasBox() +{ + if(!dbData.boxFormed) return; + + datetime time1 = iTime(dbData.symbol, PERIOD_H1, DB_BoxPeriod); + datetime time2 = iTime(dbData.symbol, PERIOD_H1, 0); + + ObjectsDeleteAll(0, dbData.boxName); + + ObjectCreate(0, dbData.boxName + "Top", OBJ_TREND, 0, time1, dbData.boxHigh, time2, dbData.boxHigh); + ObjectCreate(0, dbData.boxName + "Bottom", OBJ_TREND, 0, time1, dbData.boxLow, time2, dbData.boxLow); + + ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_COLOR, DB_BoxColor); + ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_COLOR, DB_BoxColor); + ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_WIDTH, DB_BoxWidth); + ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_WIDTH, DB_BoxWidth); + ObjectSetInteger(0, dbData.boxName + "Top", OBJPROP_RAY_RIGHT, true); + ObjectSetInteger(0, dbData.boxName + "Bottom", OBJPROP_RAY_RIGHT, true); +} + +void CalculateDarvasBox() +{ + double high = 0; + double low = DBL_MAX; + + // Find highest high and lowest low in the period - EXACTLY like original + for(int i = 0; i < DB_BoxPeriod; i++) + { + high = MathMax(high, iHigh(dbData.symbol, PERIOD_H1, i)); + low = MathMin(low, iLow(dbData.symbol, PERIOD_H1, i)); + } + + double range = high - low; + double allowedRange = DB_BoxDeviation * dbData.point; // Use dbData.point instead of _Point + + if(DB_EnableLogging) + { + Print("DarvasBox: Box Calculation - High: ", high, " Low: ", low, " Range: ", range, " Allowed Range: ", allowedRange); + } + + // Check if box is formed - EXACTLY like original + if(range <= allowedRange) + { + dbData.boxHigh = high; + dbData.boxLow = low; + dbData.boxFormed = true; + dbData.lastBoxTime = iTime(dbData.symbol, PERIOD_CURRENT, 0); + + // Draw the box + DrawDarvasBox(); + + if(DB_EnableLogging) + Print("DarvasBox: Box Formed - High: ", dbData.boxHigh, " Low: ", dbData.boxLow, " Time: ", dbData.lastBoxTime); + } + else + { + dbData.boxFormed = false; + // Delete box if it exists + ObjectsDeleteAll(0, dbData.boxName); + } +} + +bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE orderType) +{ + double minSlDistance = MathMax(dbData.minStopLevel, DB_StopLoss * dbData.point); + double minTpDistance = MathMax(dbData.minStopLevel, DB_TakeProfit * dbData.point); + + if(orderType == ORDER_TYPE_BUY) + { + sl = price - minSlDistance; + tp = price + minTpDistance; + } + else + { + sl = price + minSlDistance; + tp = price - minTpDistance; + } + + return true; +} + +bool IsTrendFavorable(ENUM_ORDER_TYPE orderType) +{ + double ma[]; + ArraySetAsSeries(ma, true); + + if(CopyBuffer(dbData.maHandle, 0, 0, 2, ma) <= 0) + return false; + + double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK); + double trendStrength = MathAbs(currentPrice - ma[0]) / dbData.point; + + if(orderType == ORDER_TYPE_BUY) + return (currentPrice > ma[0] && trendStrength > DB_TrendThreshold); + else + return (currentPrice < ma[0] && trendStrength > DB_TrendThreshold); +} + +bool CheckVolumeConditions() +{ + double volumes[]; + ArraySetAsSeries(volumes, true); + + if(CopyBuffer(dbData.volumeHandle, 0, 0, DB_VolumeMA_Period + 1, volumes) <= 0) + return false; + + double volumeMA = 0; + for(int i = 1; i <= DB_VolumeMA_Period; i++) + volumeMA += volumes[i]; + volumeMA /= DB_VolumeMA_Period; + + double currentVolume = volumes[0]; + double volumeRatio = currentVolume / volumeMA; + + return (volumeRatio > DB_VolumeThresholdMultiplier); +} + +bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp) +{ + if(!ValidateStopLevels(price, sl, tp, orderType)) + { + if(DB_EnableLogging) + Print("DarvasBox: Order rejected - Stop levels validation failed"); + return false; + } + + if(!IsTrendFavorable(orderType)) + { + if(DB_EnableLogging) + Print("DarvasBox: Order rejected - Trend not favorable for ", EnumToString(orderType)); + return false; + } + + if(!CheckVolumeConditions()) + { + if(DB_EnableLogging) + Print("DarvasBox: Order rejected - Volume conditions not met"); + return false; + } + + bool result = false; + + // Use market price (0) instead of explicit price - this ensures market order execution + // In backtesting, explicit price might fail if price has moved + if(orderType == ORDER_TYPE_BUY) + result = dbData.trade.Buy(g_DB_LotSize, dbData.symbol, 0, sl, tp, "Darvas Box Breakout"); + else + result = dbData.trade.Sell(g_DB_LotSize, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown"); + + // Always log errors, success only if logging enabled + if(result) + { + if(DB_EnableLogging) + Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), " Order Placed Successfully"); + } + else + { + // Always log failures with detailed info + uint retcode_uint = dbData.trade.ResultRetcode(); + int retcode = (int)retcode_uint; + string desc = dbData.trade.ResultRetcodeDescription(); + ulong deal = dbData.trade.ResultDeal(); + ulong order = dbData.trade.ResultOrder(); + Print("DarvasBox: ", (orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), + " Order Failed - Retcode: ", retcode, + ", Description: ", desc, + ", Deal: ", deal, + ", Order: ", order, + ", Symbol: ", dbData.symbol, + ", Requested Price: ", price, + ", SL: ", sl, + ", TP: ", tp); + } + + return result; +} + +void ProcessDarvasBox(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!dbData.isInitialized) + return; + + dbData.symbol = symbol; // Update symbol in case it changed + + // Calculate new box levels - EXACTLY like original (called every tick) + CalculateDarvasBox(); + + // Check for trading signals - EXACTLY like original (checked every tick) + if(dbData.boxFormed) + { + double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK); + long currentVolume_long = iVolume(dbData.symbol, PERIOD_CURRENT, 0); + double currentVolume = (double)currentVolume_long; + + if(DB_EnableLogging) + { + Print("DarvasBox: Current Price: ", currentPrice, " Box High: ", dbData.boxHigh, " Box Low: ", dbData.boxLow); + Print("DarvasBox: Current Volume: ", currentVolume, " Volume Threshold: ", DB_VolumeThreshold); + } + + // Check for breakout above box - EXACTLY like original + if(currentPrice > dbData.boxHigh && currentVolume > DB_VolumeThreshold) + { + if(DB_EnableLogging) + Print("DarvasBox: Breakout Signal Detected - Price above box high"); + + // Buy signal + if(!PositionExistsByMagic(dbData.symbol, (ulong)DB_MagicNumber)) // No existing positions with our magic number + { + double sl = currentPrice - DB_StopLoss * dbData.point; + double tp = currentPrice + DB_TakeProfit * dbData.point; + + if(DB_EnableLogging) + Print("DarvasBox: Preparing Buy Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp); + + PlaceOrder(ORDER_TYPE_BUY, currentPrice, sl, tp); + } + else if(DB_EnableLogging) + Print("DarvasBox: Skipping Buy Signal - Position already exists"); + } + + // Check for breakdown below box - EXACTLY like original + if(currentPrice < dbData.boxLow && currentVolume > DB_VolumeThreshold) + { + if(DB_EnableLogging) + Print("DarvasBox: Breakdown Signal Detected - Price below box low"); + + // Sell signal + if(!PositionExistsByMagic(dbData.symbol, (ulong)DB_MagicNumber)) // No existing positions with our magic number + { + double sl = currentPrice + DB_StopLoss * dbData.point; + double tp = currentPrice - DB_TakeProfit * dbData.point; + + if(DB_EnableLogging) + Print("DarvasBox: Preparing Sell Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp); + + PlaceOrder(ORDER_TYPE_SELL, currentPrice, sl, tp); + } + else if(DB_EnableLogging) + Print("DarvasBox: Skipping Sell Signal - Position already exists"); + } + } + else if(DB_EnableLogging) + Print("DarvasBox: No Box Formed - Waiting for consolidation"); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/Strategies/EMASlopeDistanceStrategy.mqh b/frontline/MQL5/_united_dynamic_cent/Strategies/EMASlopeDistanceStrategy.mqh new file mode 100644 index 0000000..70e4502 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/Strategies/EMASlopeDistanceStrategy.mqh @@ -0,0 +1,496 @@ +//+------------------------------------------------------------------+ +//| EMASlopeDistanceStrategy.mqh | +//+------------------------------------------------------------------+ + +bool InitEMASlopeDistance(string symbol) +{ + esData.symbol = symbol; + esData.letzte_überwachung_zeit = 0; + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.steigung_trigger_aktiv = false; + esData.ticket = 0; + esData.trades_in_current_crossover = 0; + esData.crossover_detected = false; + esData.trade_open_time = 0; + esData.last_bar_time = 0; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("EMASlopeDistance: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + esData.trade.SetExpertMagicNumber(ES_MagicNumber); + esData.trade.SetDeviationInPoints(10); + esData.trade.SetTypeFilling(ORDER_FILLING_IOC); + + esData.ema_handle = iMA(symbol, ES_Timeframe, ES_EMA_Periode, 0, MODE_EMA, PRICE_CLOSE); + + if(esData.ema_handle == INVALID_HANDLE) + { + Print("EMASlopeDistance: Error creating EMA indicator for '", symbol, "'"); + return false; + } + + ArraySetAsSeries(esData.ema_array, true); + esData.isInitialized = true; + Print("EMASlopeDistance: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitEMASlopeDistance() +{ + if(esData.ema_handle != INVALID_HANDLE) + IndicatorRelease(esData.ema_handle); +} + +//+------------------------------------------------------------------+ +//| EMA Berechnung (EMA Calculation) | +//+------------------------------------------------------------------+ +void BerechneEMA() +{ + //--- EMA Werte vom Indicator kopieren (Copy EMA values from indicator) + int copied = CopyBuffer(esData.ema_handle, 0, 0, 3, esData.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]: ", esData.ema_array[0], " [1]: ", esData.ema_array[1], " [2]: ", esData.ema_array[2]); +} + +//+------------------------------------------------------------------+ +//| Trigger-Bedingungen prüfen (Check trigger conditions) | +//+------------------------------------------------------------------+ +void PrüfeTrigger() +{ + if(ArraySize(esData.ema_array) < 2) + { + Print("TRACE: Array zu klein - Größe: ", ArraySize(esData.ema_array)); + return; + } + + //--- Aktuelle Werte (Current values) + double aktueller_preis = SymbolInfoDouble(esData.symbol, SYMBOL_BID); + double aktueller_ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK); + double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0); + int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT); + double pips_multiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0; + + //--- EMA Werte in Variablen (EMA values in variables) + double ema_aktuell = esData.ema_array[0]; + double ema_vorher = esData.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) + { + esData.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: ", ES_PreisSchwelle, ")"); + Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell); + Print("TRACE: Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover); + + if(preis_abstand > ES_PreisSchwelle && !esData.preis_trigger_aktiv) + { + esData.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: ", ES_SteigungSchwelle, ")"); + + if(MathAbs(steigung) > ES_SteigungSchwelle && !esData.steigung_trigger_aktiv) + { + esData.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(esData.preis_trigger_aktiv && esData.steigung_trigger_aktiv && !esData.überwachung_aktiv) + { + esData.überwachung_aktiv = true; + + if(ES_UseBarData) + { + esData.letzte_überwachung_zeit = iTime(esData.symbol, ES_Timeframe, 0); // Aktuelle Bar-Zeit + Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Bar: ", TimeToString(esData.letzte_überwachung_zeit), ")"); + } + else + { + esData.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(esData.ü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(esData.trades_in_current_crossover >= ES_MaxTradesPerCrossover) + { + Print("TRACE: Trade-Limit erreicht (", ES_MaxTradesPerCrossover, ") - Kein neuer Trade"); + return; + } + + if(bullish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")"); + if(PlatziereTrade(ORDER_TYPE_BUY)) + { + esData.trades_in_current_crossover++; + } + } + else if(bearish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")"); + if(PlatziereTrade(ORDER_TYPE_SELL)) + { + esData.trades_in_current_crossover++; + } + } + else if(PositionExistsByMagic(esData.symbol, (ulong)ES_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: ", g_ES_LotSize); + + bool success = false; + + if(order_type == ORDER_TYPE_BUY) + { + success = esData.trade.Buy(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade"); + } + else + { + success = esData.trade.Sell(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade"); + } + + if(success) + { + esData.ticket = (int)esData.trade.ResultOrder(); + Print("TRACE: Trade erfolgreich platziert: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF", " Ticket: ", esData.ticket); + + //--- Trade-Öffnungszeit speichern (Save trade opening time) + esData.trade_open_time = iTime(esData.symbol, ES_Timeframe, 0); + Print("TRACE: Trade-Öffnungszeit: ", TimeToString(esData.trade_open_time)); + + //--- Überwachung zurücksetzen (Reset monitoring) + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.steigung_trigger_aktiv = false; + + return true; + } + else + { + Print("TRACE: Fehler beim Platzieren des Trades - Retcode: ", esData.trade.ResultRetcode()); + Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription()); + + return false; + } +} + +//+------------------------------------------------------------------+ +//| Trades verwalten (Manage trades) | +//+------------------------------------------------------------------+ +void VerwalteTrades() +{ + if(!PositionSelectByMagic(esData.symbol, (ulong)ES_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); + + int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT); + double pips_multiplier = (digits == 3 || digits == 5) ? 10.0 : 1.0; + double trailing_stop_pips = ES_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(esData.ema_array) >= 1) + { + double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0); + double ema_aktuell = esData.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 ", esData.trades_in_current_crossover); + } + } + + //--- Profit-Prüfung nach X Bars (Profit check after X bars) + if(ES_CloseUnprofitableTrades && esData.trade_open_time != 0 && PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + Print("TRACE: Profit-Prüfung aktiviert - CloseUnprofitableTrades: ", ES_CloseUnprofitableTrades); + PrüfeProfitNachBars(); + } + else if(!ES_CloseUnprofitableTrades) + { + Print("TRACE: Profit-Prüfung deaktiviert - CloseUnprofitableTrades: ", ES_CloseUnprofitableTrades); + } +} + +//+------------------------------------------------------------------+ +//| Profit-Prüfung nach X Bars (Profit check after X bars) | +//+------------------------------------------------------------------+ +void PrüfeProfitNachBars() +{ + if(!PositionSelectByMagic(esData.symbol, (ulong)ES_MagicNumber)) + { + return; // Keine Position offen + } + + datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0); + int bars_since_trade_open = iBarShift(esData.symbol, ES_Timeframe, esData.trade_open_time); + + Print("TRACE: Bars seit Trade-Öffnung: ", bars_since_trade_open, "/", ES_ProfitCheckBars); + + //--- Prüfe ob genügend Bars vergangen sind (Check if enough bars have passed) + if(bars_since_trade_open >= ES_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 ", ES_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) + esData.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) + esData.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(esData.trade, esData.symbol, (ulong)ES_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: ", esData.trade.ResultRetcode()); + Print("TRACE: Fehlerbeschreibung: ", esData.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(esData.trade, esData.symbol, (ulong)ES_MagicNumber); + + if(success) + { + Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason); + } + else + { + Print("TRACE: Fehler beim Schließen der Position - Retcode: ", esData.trade.ResultRetcode()); + Print("TRACE: Fehlerbeschreibung: ", esData.trade.ResultRetcodeDescription()); + } +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void ProcessEMASlopeDistance(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!esData.isInitialized) + return; + + esData.symbol = symbol; // Update symbol in case it changed + + //--- Bar-Daten oder Tick-Daten verwenden (Use bar data or tick data) + if(ES_UseBarData) + { + //--- Nur bei neuen Bars ausführen (Only execute on new bars) + datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0); + + if(current_bar_time == esData.last_bar_time) + { + return; // Kein neuer Bar, nichts tun + } + + esData.last_bar_time = current_bar_time; + } + + //--- EMA Werte berechnen (Calculate EMA values) + BerechneEMA(); + + //--- Debug: Aktuelle Werte ausgeben (Debug: Output current values) + if(ArraySize(esData.ema_array) > 0) + { + double aktueller_close = iClose(esData.symbol, ES_Timeframe, 0); + double ema_aktuell = esData.ema_array[0]; + double ema_vorher = esData.ema_array[1]; + int digits = (int)SymbolInfoInteger(esData.symbol, SYMBOL_DIGITS); + double point = SymbolInfoDouble(esData.symbol, SYMBOL_POINT); + double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / point; + double steigung = (ema_aktuell - ema_vorher) / point; + + if(ES_UseBarData) + { + Print("=== DEBUG INFO (Neuer Bar) ==="); + Print("Bar Zeit: ", TimeToString(iTime(esData.symbol, ES_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: ", esData.preis_trigger_aktiv, " Steigungs-Trigger: ", esData.steigung_trigger_aktiv); + Print("Überwachung aktiv: ", esData.überwachung_aktiv); + Print("Position offen: ", PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber)); + Print("Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover); + Print("=================="); + } + + //--- Überwachung prüfen (Check monitoring) + if(esData.überwachung_aktiv) + { + if(ES_UseBarData) + { + // Bar-basierte Überwachungszeit + int bars_since_monitoring = iBarShift(esData.symbol, ES_Timeframe, esData.letzte_überwachung_zeit); + int timeout_bars = (int)(ES_ÜberwachungTimeout / PeriodSeconds(ES_Timeframe)); + + if(bars_since_monitoring > timeout_bars) + { + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.steigung_trigger_aktiv = false; + Print("Überwachung beendet - Bar-basierte Zeitüberschreitung (", bars_since_monitoring, " Bars)"); + } + } + else + { + // Tick-basierte Überwachungszeit + if(TimeCurrent() - esData.letzte_überwachung_zeit > ES_ÜberwachungTimeout) + { + esData.überwachung_aktiv = false; + esData.preis_trigger_aktiv = false; + esData.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(); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/Strategies/RSICrossOverReversalStrategy.mqh b/frontline/MQL5/_united_dynamic_cent/Strategies/RSICrossOverReversalStrategy.mqh new file mode 100644 index 0000000..f182476 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/Strategies/RSICrossOverReversalStrategy.mqh @@ -0,0 +1,240 @@ +//+------------------------------------------------------------------+ +//| RSICrossOverReversalStrategy.mqh | +//+------------------------------------------------------------------+ + +void WeekDays_Init() +{ + rcData.WeekDays[0] = RC_Sunday; + rcData.WeekDays[1] = RC_Monday; + rcData.WeekDays[2] = RC_Tuesday; + rcData.WeekDays[3] = RC_Wednesday; + rcData.WeekDays[4] = RC_Thursday; + rcData.WeekDays[5] = RC_Friday; + rcData.WeekDays[6] = RC_Saturday; +} + +bool WeekDays_Check(datetime aTime) +{ + MqlDateTime stm; + TimeToStruct(aTime, stm); + return(rcData.WeekDays[stm.day_of_week]); +} + +int TimeHour(datetime when = 0) +{ + if(when == 0) when = TimeCurrent(); + MqlDateTime dt; + TimeToStruct(when, dt); + return dt.hour; +} + +bool InitRSICrossOverReversal(string symbol) +{ + WeekDays_Init(); + + rcData.symbol = symbol; + rcData.previousRSIDef = 0; + rcData.lastTradeTime = 0; + rcData.bartime = 0; + rcData.lastBarTime = 0; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSICrossOverReversal: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + rcData.rsiHandle = iRSI(symbol, RC_TimeFrame1, RC_rsiPeriod, PRICE_CLOSE); + if(rcData.rsiHandle == INVALID_HANDLE) + { + Print("RSICrossOverReversal: Error creating RSI handle for '", symbol, "'"); + return false; + } + + rcData.emaHandle = iMA(symbol, RC_TimeFrame2, RC_emaPeriod, 0, MODE_EMA, PRICE_CLOSE); + if(rcData.emaHandle == INVALID_HANDLE) + { + Print("RSICrossOverReversal: Error creating EMA handle for '", symbol, "'"); + return false; + } + + rcData.trade.SetExpertMagicNumber(RC_MagicNumber); + rcData.isInitialized = true; + Print("RSICrossOverReversal: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitRSICrossOverReversal() +{ + if(rcData.rsiHandle != INVALID_HANDLE) + IndicatorRelease(rcData.rsiHandle); + if(rcData.emaHandle != INVALID_HANDLE) + IndicatorRelease(rcData.emaHandle); +} + +void Close_Position_MN(ulong magicNumber) +{ + ClosePositionByMagic(rcData.trade, rcData.symbol, (int)magicNumber); +} + +void ApplyTrailingStop() +{ + if(!PositionSelectByMagic(rcData.symbol, RC_MagicNumber)) + return; + + ulong PositionTicket = PositionGetInteger(POSITION_TICKET); + ENUM_POSITION_TYPE trade_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + string symbol = rcData.symbol; + + double POINT = SymbolInfoDouble(symbol, SYMBOL_POINT); + int DIGIT = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + + if(trade_type == POSITION_TYPE_BUY) + { + double Bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), DIGIT); + + if(Bid - PositionGetDouble(POSITION_PRICE_OPEN) > NormalizeDouble(POINT * RC_TrailingStop, DIGIT)) + { + if(PositionGetDouble(POSITION_SL) < NormalizeDouble(Bid - POINT * RC_TrailingStop, DIGIT)) + { + ModifyPositionByMagic(rcData.trade, symbol, RC_MagicNumber, + NormalizeDouble(Bid - POINT * RC_TrailingStop, DIGIT), + PositionGetDouble(POSITION_TP)); + } + } + } + else if(trade_type == POSITION_TYPE_SELL) + { + double Ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), DIGIT); + + if((PositionGetDouble(POSITION_PRICE_OPEN) - Ask) > NormalizeDouble(POINT * RC_TrailingStop, DIGIT)) + { + if((PositionGetDouble(POSITION_SL) > NormalizeDouble(Ask + POINT * RC_TrailingStop, DIGIT)) || + (PositionGetDouble(POSITION_SL) == 0)) + { + ModifyPositionByMagic(rcData.trade, symbol, RC_MagicNumber, + NormalizeDouble(Ask + POINT * RC_TrailingStop, DIGIT), + PositionGetDouble(POSITION_TP)); + } + } + } +} + +void ProcessRSICrossOverReversal(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!rcData.isInitialized) + return; + + rcData.symbol = symbol; // Update symbol in case it changed + if(rcData.bartime == iTime(rcData.symbol, RC_BarTimeFrame, 0)) + return; + rcData.bartime = iTime(rcData.symbol, RC_BarTimeFrame, 0); + + double rsi[]; + if(CopyBuffer(rcData.rsiHandle, 0, 0, 2, rsi) <= 0) + return; + + double ema[]; + if(CopyBuffer(rcData.emaHandle, 0, 0, 2, ema) <= 0) + return; + + datetime currentTime = TimeCurrent(); + int currentHour = TimeHour(TimeCurrent()); + + if(!WeekDays_Check(TimeTradeServer())) + { + Close_Position_MN(RC_MagicNumber); + return; + } + + if(!((currentHour < RC_tradingHourOneEnd && currentHour > RC_tradingHourOneBegin) || + (currentHour < RC_tradingHourTwoEnd && currentHour > RC_tradingHourTwoBegin))) + { + Close_Position_MN(RC_MagicNumber); + return; + } + + bool hasPosition = PositionExistsByMagic(rcData.symbol, RC_MagicNumber); + + double currentRSI = rsi[0]; + double previousRSI = rsi[1]; + + if(rcData.previousRSIDef == 0) + { + rcData.previousRSIDef = currentRSI; + return; + } + + double currentEMA = ema[0]; + double previousEMA = ema[1]; + + double emaSlope = (currentEMA - previousEMA) * 100; + double closeCurr = iClose(Symbol(), Period(), 0); + double priceToEmaDistance = (closeCurr - currentEMA) * 10; + + bool isBuyPosition = false; + bool isSellPosition = false; + if(hasPosition) + { + if(PositionSelectByMagic(rcData.symbol, RC_MagicNumber)) + { + ENUM_POSITION_TYPE positionType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if(positionType == POSITION_TYPE_BUY) + isBuyPosition = true; + else if(positionType == POSITION_TYPE_SELL) + isSellPosition = true; + } + } + + ApplyTrailingStop(); + + bool cooldownPassed = (currentTime - rcData.lastTradeTime) >= RC_cooldownSeconds; + bool isTrendStrong = MathAbs(emaSlope) > RC_emaSlopeThreshold || MathAbs(priceToEmaDistance) > RC_emaDistanceThreshold; + + if(isBuyPosition && currentRSI > RC_exitBuyRSI) + { + Close_Position_MN(RC_MagicNumber); + rcData.lastTradeTime = currentTime; + } + + if(isSellPosition && currentRSI < RC_exitSellRSI) + { + Close_Position_MN(RC_MagicNumber); + rcData.lastTradeTime = currentTime; + } + + if(isTrendStrong) + { + Close_Position_MN(RC_MagicNumber); + rcData.lastTradeTime = currentTime; + return; + } + + if(currentRSI < RC_overboughtLevel - RC_entryRSISellSpread && rcData.previousRSIDef >= RC_overboughtLevel && + !isSellPosition && !hasPosition && cooldownPassed) + { + rcData.trade.SetExpertMagicNumber(RC_MagicNumber); + if(rcData.trade.Sell(g_RC_LotSize, rcData.symbol, 0.0, 0.0, 0.0, "Sell Order")) + { + rcData.lastTradeTime = currentTime; + } + } + + if(currentRSI > RC_oversoldLevel + RC_entryRSIBuySpread && rcData.previousRSIDef <= RC_oversoldLevel && + !isBuyPosition && !hasPosition && cooldownPassed) + { + rcData.trade.SetExpertMagicNumber(RC_MagicNumber); + if(rcData.trade.Buy(g_RC_LotSize, rcData.symbol, 0.0, 0.0, 0.0, "Buy Order")) + { + rcData.lastTradeTime = currentTime; + } + } + + rcData.previousRSIDef = currentRSI; +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/Strategies/RSIMidPointHijackStrategy.mqh b/frontline/MQL5/_united_dynamic_cent/Strategies/RSIMidPointHijackStrategy.mqh new file mode 100644 index 0000000..3db8db5 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/Strategies/RSIMidPointHijackStrategy.mqh @@ -0,0 +1,471 @@ +//+------------------------------------------------------------------+ +//| RSIMidPointHijackStrategy.mqh | +//+------------------------------------------------------------------+ + +bool IsNewBar(string symbol) +{ + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + { + if(time[0] != rmData.lastBarTime) + { + rmData.lastBarTime = time[0]; + return true; + } + } + return false; +} + +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); +} + +bool HasPosition(string symbol, int magic) +{ + return PositionExistsByMagic(symbol, magic); +} + +bool HasProfitablePosition(int excludeMagic) +{ + bool hasProfitable = false; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + if(rmData.positionInfo.SelectByIndex(i)) + { + if(rmData.positionInfo.Magic() != excludeMagic) + { + double profit = rmData.positionInfo.Profit(); + if(profit > RM_InpLockProfitThreshold * _Point) + { + hasProfitable = true; + if(RM_InpCloseOppositeTrades) + { + if((excludeMagic == RM_InpMagicNumberRSIFollow && rmData.positionInfo.Magic() == RM_InpMagicNumberRSIReverse) || + (excludeMagic == RM_InpMagicNumberRSIReverse && rmData.positionInfo.Magic() == RM_InpMagicNumberRSIFollow) || + (excludeMagic == RM_InpMagicNumberEMACross && (rmData.positionInfo.Magic() == RM_InpMagicNumberRSIReverse || rmData.positionInfo.Magic() == RM_InpMagicNumberRSIFollow)) || + ((excludeMagic == RM_InpMagicNumberRSIFollow || excludeMagic == RM_InpMagicNumberRSIReverse) && rmData.positionInfo.Magic() == RM_InpMagicNumberEMACross)) + { + ClosePosition(rmData.symbol, (int)rmData.positionInfo.Magic()); + } + } + } + } + } + } + return hasProfitable; +} + +bool IsRSIReverseInCooldown(string symbol) +{ + if(RM_InpRSIReverseCooldownBars <= 0) + return false; + + if(!rmData.rsiReverseInCooldown) + return false; + + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + { + datetime currentBarTime = time[0]; + datetime cooldownEndTime = rmData.rsiReverseLastCloseTime + RM_InpRSIReverseCooldownBars * PeriodSeconds(RM_InpTimeframe); + + if(currentBarTime >= cooldownEndTime) + { + rmData.rsiReverseInCooldown = false; + return false; + } + } + + return true; +} + +void CheckRSIFollowStrategy(string symbol) +{ + if(!IsWithinTradingHours(RM_InpRSIFollowStartHour, RM_InpRSIFollowEndHour)) + { + if(RM_InpRSIFollowCloseOutsideHours) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + ClosePosition(symbol, RM_InpMagicNumberRSIFollow); + } + return; + } + + if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberRSIFollow)) + return; + + if(rmData.lastBarRSI > RM_InpRSIOverbought) + rmData.rsiOverbought = true; + else if(rmData.lastBarRSI < RM_InpRSIOversold) + rmData.rsiOversold = true; + + if(rmData.rsiOverbought && rmData.lastBarRSI < RM_InpRSIExitLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow"); + } + rmData.rsiOverbought = false; + } + else if(rmData.rsiOversold && rmData.lastBarRSI > RM_InpRSIExitLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow"); + } + rmData.rsiOversold = false; + } +} + +void CheckRSIReverseStrategy(string symbol) +{ + if(!IsWithinTradingHours(RM_InpRSIReverseStartHour, RM_InpRSIReverseEndHour)) + { + if(RM_InpRSIReverseCloseOutsideHours) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + ClosePosition(symbol, RM_InpMagicNumberRSIReverse); + } + return; + } + + if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberRSIReverse)) + return; + + if(IsRSIReverseInCooldown(symbol)) + return; + + if(rmData.lastBarRSIReverse > RM_InpRSIReverseOverbought) + rmData.rsiReverseOverbought = true; + else if(rmData.lastBarRSIReverse < RM_InpRSIReverseOversold) + rmData.rsiReverseOversold = true; + + if(rmData.rsiReverseOverbought && rmData.lastBarRSIReverse < RM_InpRSIReverseCrossLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse"); + } + rmData.rsiReverseOverbought = false; + } + else if(rmData.rsiReverseOversold && rmData.lastBarRSIReverse > RM_InpRSIReverseCrossLevel) + { + if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse"); + } + rmData.rsiReverseOversold = false; + } +} + +void CheckEMACrossStrategy(string symbol) +{ + if(!IsWithinTradingHours(RM_InpEMACrossStartHour, RM_InpEMACrossEndHour)) + { + if(RM_InpEMACrossCloseOutsideHours) + { + if(HasPosition(symbol, RM_InpMagicNumberEMACross)) + ClosePosition(symbol, RM_InpMagicNumberEMACross); + } + return; + } + + if(RM_InpEnableStrategyLock && HasProfitablePosition(RM_InpMagicNumberEMACross)) + return; + + if(rmData.lastBarEMAPrev < rmData.lastBarClosePrev && rmData.lastBarEMA > rmData.lastBarClose) + { + rmData.emaCrossBuySignal = true; + rmData.emaCrossSellSignal = false; + rmData.emaCrossSignalBar = 0; + } + else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose) + { + rmData.emaCrossSellSignal = true; + rmData.emaCrossBuySignal = false; + rmData.emaCrossSignalBar = 0; + } + + if(RM_InpUseEMADistanceEntry) + { + if(rmData.emaCrossBuySignal) + { + bool distanceConditionMet = true; + double emaHistory[], closeHistory[]; + ArraySetAsSeries(emaHistory, true); + ArraySetAsSeries(closeHistory, true); + + if(CopyBuffer(rmData.emaHandle, 0, 0, RM_InpEMADistancePeriod, emaHistory) > 0 && + CopyClose(symbol, RM_InpTimeframe, 0, RM_InpEMADistancePeriod, closeHistory) > 0) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + for(int i = 0; i < RM_InpEMADistancePeriod; i++) + { + double distance = (closeHistory[i] - emaHistory[i]) / point; + if(distance < RM_InpEMADistancePips) + { + distanceConditionMet = false; + break; + } + } + + if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance"); + rmData.emaCrossBuySignal = false; + } + } + } + else if(rmData.emaCrossSellSignal) + { + bool distanceConditionMet = true; + double emaHistory[], closeHistory[]; + ArraySetAsSeries(emaHistory, true); + ArraySetAsSeries(closeHistory, true); + + if(CopyBuffer(rmData.emaHandle, 0, 0, RM_InpEMADistancePeriod, emaHistory) > 0 && + CopyClose(symbol, RM_InpTimeframe, 0, RM_InpEMADistancePeriod, closeHistory) > 0) + { + double point = SymbolInfoDouble(symbol, SYMBOL_POINT); + for(int i = 0; i < RM_InpEMADistancePeriod; i++) + { + double distance = (emaHistory[i] - closeHistory[i]) / point; + if(distance < RM_InpEMADistancePips) + { + distanceConditionMet = false; + break; + } + } + + if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance"); + rmData.emaCrossSellSignal = false; + } + } + } + } + else + { + if(rmData.lastBarEMAPrev < rmData.lastBarClosePrev && rmData.lastBarEMA > rmData.lastBarClose) + { + if(!HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross"); + } + } + else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose) + { + if(!HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross); + rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross"); + } + } + } + + if(rmData.emaCrossBuySignal || rmData.emaCrossSellSignal) + { + rmData.emaCrossSignalBar++; + if(rmData.emaCrossSignalBar > RM_InpEMADistancePeriod * 2) + { + rmData.emaCrossBuySignal = false; + rmData.emaCrossSellSignal = false; + } + } +} + +void CheckExitConditions(string symbol) +{ + if(RM_InpEnableRSIFollow) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIFollow)) + { + if(PositionSelectByMagic(symbol, RM_InpMagicNumberRSIFollow)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if((posType == POSITION_TYPE_BUY && rmData.lastBarRSI < RM_InpRSIExitLevel) || + (posType == POSITION_TYPE_SELL && rmData.lastBarRSI > RM_InpRSIExitLevel)) + { + ClosePosition(symbol, RM_InpMagicNumberRSIFollow); + } + } + } + } + + if(RM_InpEnableRSIReverse) + { + if(HasPosition(symbol, RM_InpMagicNumberRSIReverse)) + { + if(PositionSelectByMagic(symbol, RM_InpMagicNumberRSIReverse)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if((posType == POSITION_TYPE_BUY && rmData.lastBarRSIReverse < RM_InpRSIReverseExitLevel) || + (posType == POSITION_TYPE_SELL && rmData.lastBarRSIReverse > RM_InpRSIReverseExitLevel)) + { + ClosePosition(symbol, RM_InpMagicNumberRSIReverse); + } + } + } + } + + if(RM_InpEnableEMACross) + { + if(HasPosition(symbol, RM_InpMagicNumberEMACross)) + { + if(PositionSelectByMagic(symbol, RM_InpMagicNumberEMACross)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + if((posType == POSITION_TYPE_BUY && rmData.lastBarEMA > rmData.lastBarClose) || + (posType == POSITION_TYPE_SELL && rmData.lastBarEMA < rmData.lastBarClose)) + { + ClosePosition(symbol, RM_InpMagicNumberEMACross); + } + } + } + } +} + +void ClosePosition(string symbol, int magic) +{ + if(!PositionExistsByMagic(symbol, magic)) + return; + + ulong ticket = GetPositionTicketByMagic(symbol, magic); + if(ticket == 0) + return; + + if(magic == RM_InpMagicNumberRSIReverse) + { + if(PositionSelectByTicketSymbolAndMagic(ticket, symbol, magic)) + { + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + { + rmData.rsiReverseLastCloseTime = time[0]; + double profit = PositionGetDouble(POSITION_PROFIT); + if(!RM_InpRSIReverseCooldownOnLoss || profit < 0) + { + rmData.rsiReverseInCooldown = true; + } + } + } + } + + ClosePositionByMagic(rmData.trade, symbol, magic); +} + +bool InitRSIMidPointHijack(string symbol) +{ + rmData.symbol = symbol; + rmData.rsiOverbought = false; + rmData.rsiOversold = false; + rmData.rsiReverseOverbought = false; + rmData.rsiReverseOversold = false; + rmData.emaCrossBuySignal = false; + rmData.emaCrossSellSignal = false; + rmData.emaCrossSignalBar = 0; + rmData.rsiReverseInCooldown = false; + rmData.lastBarRSI = 0; + rmData.lastBarRSIReverse = 0; + rmData.lastBarEMA = 0; + rmData.lastBarClose = 0; + rmData.lastBarEMAPrev = 0; + rmData.lastBarClosePrev = 0; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSIMidPointHijack: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + Sleep(100); // Wait for symbol to be ready + + rmData.rsiHandle = iRSI(symbol, RM_InpTimeframe, RM_InpRSIPeriod, PRICE_CLOSE); + rmData.rsiReverseHandle = iRSI(symbol, RM_InpTimeframe, RM_InpRSIReversePeriod, PRICE_CLOSE); + rmData.emaHandle = iMA(symbol, RM_InpTimeframe, RM_InpEMAPeriod, 0, MODE_EMA, PRICE_CLOSE); + + if(rmData.rsiHandle == INVALID_HANDLE || rmData.rsiReverseHandle == INVALID_HANDLE || rmData.emaHandle == INVALID_HANDLE) + { + Print("RSIMidPointHijack: Error creating indicators for '", symbol, "'"); + return false; + } + + rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow); + rmData.trade.SetMarginMode(); + rmData.trade.SetTypeFillingBySymbol(symbol); + rmData.trade.SetDeviationInPoints(10); + + datetime time[]; + if(CopyTime(symbol, RM_InpTimeframe, 0, 1, time) > 0) + rmData.lastBarTime = time[0]; + + rmData.isInitialized = true; + Print("RSIMidPointHijack: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitRSIMidPointHijack() +{ + if(rmData.rsiHandle != INVALID_HANDLE) IndicatorRelease(rmData.rsiHandle); + if(rmData.rsiReverseHandle != INVALID_HANDLE) IndicatorRelease(rmData.rsiReverseHandle); + if(rmData.emaHandle != INVALID_HANDLE) IndicatorRelease(rmData.emaHandle); +} + +void ProcessRSIMidPointHijack(string symbol) +{ + // Skip if not initialized (symbol not available) + if(!rmData.isInitialized) + return; + + rmData.symbol = symbol; // Update symbol in case it changed + if(!IsNewBar(rmData.symbol)) + return; + + double rsi[], rsiReverse[], ema[], close[]; + ArraySetAsSeries(rsi, true); + ArraySetAsSeries(rsiReverse, true); + ArraySetAsSeries(ema, true); + ArraySetAsSeries(close, true); + + rmData.lastBarEMAPrev = rmData.lastBarEMA; + rmData.lastBarClosePrev = rmData.lastBarClose; + + if(CopyBuffer(rmData.rsiHandle, 0, 0, 1, rsi) > 0) + rmData.lastBarRSI = rsi[0]; + + if(CopyBuffer(rmData.rsiReverseHandle, 0, 0, 1, rsiReverse) > 0) + rmData.lastBarRSIReverse = rsiReverse[0]; + + if(CopyBuffer(rmData.emaHandle, 0, 0, 1, ema) > 0) + rmData.lastBarEMA = ema[0]; + + if(CopyClose(rmData.symbol, RM_InpTimeframe, 0, 1, close) > 0) + rmData.lastBarClose = close[0]; + + if(RM_InpEnableRSIFollow) + CheckRSIFollowStrategy(rmData.symbol); + if(RM_InpEnableRSIReverse) + CheckRSIReverseStrategy(rmData.symbol); + if(RM_InpEnableEMACross) + CheckEMACrossStrategy(rmData.symbol); + + CheckExitConditions(rmData.symbol); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/Strategies/RSIReversalAsianStrategy.mqh b/frontline/MQL5/_united_dynamic_cent/Strategies/RSIReversalAsianStrategy.mqh new file mode 100644 index 0000000..6762248 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/Strategies/RSIReversalAsianStrategy.mqh @@ -0,0 +1,493 @@ +//+------------------------------------------------------------------+ +//| RSIReversalAsianStrategy.mqh | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| RSI Reversal Asian Strategy Data Structure | +//+------------------------------------------------------------------+ +struct RSIReversalAsianData { + string symbol; + bool isInitialized; + int rsiHandle; + CTrade trade; + bool isPositionOpen; + double positionOpenPrice; + datetime positionOpenTime; + ENUM_POSITION_TYPE lastPositionType; + bool sessionCloseAttempted; + + // RSI crossover variables + double rsiCurrent; + double rsiPrevious; + double rsiPrevious2; + bool rsiCrossedOverbought; + bool rsiCrossedOversold; + bool rsiCrossedExitLevel; + + // Strategy parameters + int RSIPeriod; + double OverboughtLevel; + double OversoldLevel; + int TakeProfitPips; + int StopLossPips; + double MaxLotSize; + int MaxSpread; + int MaxDuration; + bool UseStopLoss; + bool UseTakeProfit; + bool UseRSIExit; + double RSIExitLevel; + bool CloseOutsideSession; + ENUM_TIMEFRAMES TimeFrame; + int MagicNumber; + int Slippage; + double point; +}; + +// Session times (UTC) +const int AsianSessionStart = 0; // 00:00 UTC +const int AsianSessionEnd = 8; // 08:00 UTC + +//+------------------------------------------------------------------+ +//| Check if current time is in Asian session | +//+------------------------------------------------------------------+ +bool IsAsianSession() +{ + datetime currentTime = TimeCurrent(); + MqlDateTime timeStruct; + TimeToStruct(currentTime, timeStruct); + + return (timeStruct.hour >= AsianSessionStart && timeStruct.hour < AsianSessionEnd); +} + +//+------------------------------------------------------------------+ +//| Check if trading is allowed for symbol | +//+------------------------------------------------------------------+ +bool IsTradingAllowed(RSIReversalAsianData& data) +{ + // Check if market is open + long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE); + if(tradeMode != SYMBOL_TRADE_MODE_FULL) + { + return false; + } + + // Check if we have enough money + if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0) + { + return false; + } + + return true; +} + +//+------------------------------------------------------------------+ +//| Check RSI crossover conditions | +//+------------------------------------------------------------------+ +void CheckRSICrossover(RSIReversalAsianData& data) +{ + // Reset crossover flags + data.rsiCrossedOverbought = false; + data.rsiCrossedOversold = false; + data.rsiCrossedExitLevel = false; + + // Check for overbought crossover (RSI crosses above overbought level) + if(data.rsiPrevious < data.OverboughtLevel && data.rsiCurrent >= data.OverboughtLevel) + { + data.rsiCrossedOverbought = true; + } + + // Check for oversold crossover (RSI crosses below oversold level) + if(data.rsiPrevious > data.OversoldLevel && data.rsiCurrent <= data.OversoldLevel) + { + data.rsiCrossedOversold = true; + } + + // Check for exit level crossover + if(data.rsiPrevious < data.RSIExitLevel && data.rsiCurrent >= data.RSIExitLevel) + { + data.rsiCrossedExitLevel = true; + } + else if(data.rsiPrevious > data.RSIExitLevel && data.rsiCurrent <= data.RSIExitLevel) + { + data.rsiCrossedExitLevel = true; + } +} + +//+------------------------------------------------------------------+ +//| Close all trades for the symbol | +//+------------------------------------------------------------------+ +bool CloseAllTrades(RSIReversalAsianData& data, string reason = "") +{ + bool allClosed = true; + int totalPositions = PositionsTotal(); + + if(totalPositions == 0) + return true; + + for(int i = totalPositions - 1; i >= 0; i--) + { + if(PositionGetSymbol(i) == data.symbol) + { + ulong ticket = PositionGetTicket(i); + if(ticket > 0 && PositionSelectByTicket(ticket)) + { + if(PositionGetInteger(POSITION_MAGIC) == (ulong)data.MagicNumber) + { + // Try to close position with retry logic + int retryCount = 0; + bool positionClosed = false; + + while(retryCount < 3 && !positionClosed) + { + if(data.trade.PositionClose(ticket)) + { + data.isPositionOpen = false; + positionClosed = true; + } + else + { + int error = GetLastError(); + + // If error is 4756 (Trade disabled), wait longer before retry + if(error == 4756) + { + Sleep(5000); // Wait 5 seconds before retry + retryCount++; + } + else + { + // For other errors, break the loop + break; + } + } + } + + if(!positionClosed) + { + allClosed = false; + } + } + } + } + } + + return allClosed; +} + +//+------------------------------------------------------------------+ +//| Initialize RSI Reversal Asian Strategy | +//+------------------------------------------------------------------+ +bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol, + int RSIPeriod, double OverboughtLevel, double OversoldLevel, + int TakeProfitPips, int StopLossPips, double MaxLotSize, + int MaxSpread, int MaxDuration, bool UseStopLoss, + bool UseTakeProfit, bool UseRSIExit, double RSIExitLevel, + bool CloseOutsideSession, ENUM_TIMEFRAMES TimeFrame, + int MagicNumber, int Slippage) +{ + data.symbol = symbol; + data.isInitialized = false; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSIReversalAsian: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; + } + + // Wait a bit for symbol to be ready + Sleep(100); + + // Get symbol point + data.point = SymbolInfoDouble(symbol, SYMBOL_POINT); + + // Store parameters + data.RSIPeriod = RSIPeriod; + data.OverboughtLevel = OverboughtLevel; + data.OversoldLevel = OversoldLevel; + data.TakeProfitPips = TakeProfitPips; + data.StopLossPips = StopLossPips; + data.MaxLotSize = MaxLotSize; + data.MaxSpread = MaxSpread; + data.MaxDuration = MaxDuration; + data.UseStopLoss = UseStopLoss; + data.UseTakeProfit = UseTakeProfit; + data.UseRSIExit = UseRSIExit; + data.RSIExitLevel = RSIExitLevel; + data.CloseOutsideSession = CloseOutsideSession; + data.TimeFrame = TimeFrame; + data.MagicNumber = MagicNumber; + data.Slippage = Slippage; + + // Initialize RSI indicator with retry logic (for insufficient history in backtesting) + data.rsiHandle = INVALID_HANDLE; + int retryCount = 0; + int maxRetries = 5; + + while(retryCount < maxRetries && data.rsiHandle == INVALID_HANDLE) + { + data.rsiHandle = iRSI(symbol, TimeFrame, RSIPeriod, PRICE_CLOSE); + + if(data.rsiHandle == INVALID_HANDLE) + { + int error = GetLastError(); + + // Error 4805 = insufficient history - wait longer and retry + if(error == 4805 && retryCount < maxRetries - 1) + { + Sleep(1000); // Wait 1 second for history to load + retryCount++; + continue; + } + + Print("RSIReversalAsian: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", error == 4805 ? "Insufficient history data" : "Unknown", ")"); + return false; + } + } + + if(data.rsiHandle == INVALID_HANDLE) + { + Print("RSIReversalAsian: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries"); + return false; + } + + // Wait a bit for the indicator to be ready + Sleep(100); + + // Initialize RSI values with retry logic + double rsi[]; + ArraySetAsSeries(rsi, true); + + retryCount = 0; + bool rsiInitialized = false; + + while(retryCount < 10 && !rsiInitialized) + { + int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi); + if(copied >= 3) + { + data.rsiCurrent = rsi[0]; + data.rsiPrevious = rsi[1]; + data.rsiPrevious2 = rsi[2]; + rsiInitialized = true; + } + else + { + retryCount++; + Sleep(100); + } + } + + if(!rsiInitialized) + { + // Don't fail initialization, just set default values + data.rsiCurrent = 50.0; + data.rsiPrevious = 50.0; + data.rsiPrevious2 = 50.0; + } + + // Set trade parameters + data.trade.SetExpertMagicNumber(MagicNumber); + data.trade.SetDeviationInPoints(Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_IOC); + + // Initialize state + data.isPositionOpen = false; + data.positionOpenPrice = 0; + data.positionOpenTime = 0; + data.lastPositionType = POSITION_TYPE_BUY; + data.sessionCloseAttempted = false; + data.rsiCrossedOverbought = false; + data.rsiCrossedOversold = false; + data.rsiCrossedExitLevel = false; + + data.isInitialized = true; + + Print("RSIReversalAsian: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +//+------------------------------------------------------------------+ +//| Deinitialize RSI Reversal Asian Strategy | +//+------------------------------------------------------------------+ +void DeinitRSIReversalAsian(RSIReversalAsianData& data) +{ + if(data.rsiHandle != INVALID_HANDLE) + IndicatorRelease(data.rsiHandle); +} + +//+------------------------------------------------------------------+ +//| Process RSI Reversal Asian Strategy | +//+------------------------------------------------------------------+ +void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize) +{ + if(!data.isInitialized) + return; + + // Check if trading is allowed + if(!IsTradingAllowed(data)) + { + return; + } + + // Check if we're in Asian session + if(!IsAsianSession()) + { + // Close all positions if outside Asian session and CloseOutsideSession is true + if(data.CloseOutsideSession && !data.sessionCloseAttempted) + { + CloseAllTrades(data, "Outside Asian session"); + data.sessionCloseAttempted = true; + } + return; + } + else + { + // Reset the session close attempt flag when we enter Asian session + data.sessionCloseAttempted = false; + } + + // Get current spread + double spread = SymbolInfoDouble(data.symbol, SYMBOL_ASK) - SymbolInfoDouble(data.symbol, SYMBOL_BID); + int spreadInPips = (int)(spread / data.point); + + // Check if spread is too high + if(spreadInPips > data.MaxSpread) + { + return; + } + + // Get RSI values from bar data + double rsi[]; + ArraySetAsSeries(rsi, true); + + int copied = CopyBuffer(data.rsiHandle, 0, 0, 3, rsi); + if(copied < 3) + { + return; + } + + // Update RSI values + data.rsiPrevious2 = data.rsiPrevious; + data.rsiPrevious = data.rsiCurrent; + data.rsiCurrent = rsi[0]; + + // Validate RSI values + if(data.rsiCurrent == 0 || data.rsiPrevious == 0) + { + return; + } + + // Check for RSI crossovers + CheckRSICrossover(data); + + // Get current prices + double currentBid = SymbolInfoDouble(data.symbol, SYMBOL_BID); + double currentAsk = SymbolInfoDouble(data.symbol, SYMBOL_ASK); + + // Check for open position + bool hasOpenPosition = PositionExistsByMagic(data.symbol, (ulong)data.MagicNumber); + + if(hasOpenPosition) + { + // Get position details + ulong ticket = GetPositionTicketByMagic(data.symbol, (ulong)data.MagicNumber); + if(ticket > 0 && PositionSelectByTicket(ticket)) + { + ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + datetime openTime = (datetime)PositionGetInteger(POSITION_TIME); + + // Check for RSI exit if enabled + if(data.UseRSIExit && data.rsiCrossedExitLevel) + { + bool shouldExit = false; + + // For long positions, exit when RSI crosses above exit level + if(posType == POSITION_TYPE_BUY && data.rsiCurrent >= data.RSIExitLevel && data.rsiPrevious < data.RSIExitLevel) + { + shouldExit = true; + } + // For short positions, exit when RSI crosses below exit level + else if(posType == POSITION_TYPE_SELL && data.rsiCurrent <= data.RSIExitLevel && data.rsiPrevious > data.RSIExitLevel) + { + shouldExit = true; + } + + if(shouldExit) + { + CloseAllTrades(data, "RSI Exit Crossover"); + return; + } + } + + // Check for timeout + if(TimeCurrent() - openTime > data.MaxDuration * 3600) + { + CloseAllTrades(data, "Timeout"); + return; + } + } + } + + // If no position is open, look for entry signals based on RSI crossover + if(!hasOpenPosition) + { + // Place buy order if RSI crosses below oversold level (oversold crossover) + if(data.rsiCrossedOversold) + { + double sl = data.UseStopLoss ? currentBid - data.StopLossPips * data.point : 0; + double tp = data.UseTakeProfit ? currentBid + data.TakeProfitPips * data.point : 0; + + if(data.UseStopLoss && sl >= currentBid) + return; + if(data.UseTakeProfit && tp <= currentBid) + return; + + // Set trade parameters + data.trade.SetDeviationInPoints(data.Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_IOC); + data.trade.SetExpertMagicNumber(data.MagicNumber); + + // Use dynamic lot size + double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize; + + // Place buy order using CTrade + if(data.trade.Buy(tradeLotSize, data.symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy")) + { + data.isPositionOpen = true; + data.positionOpenPrice = currentAsk; + data.positionOpenTime = TimeCurrent(); + data.lastPositionType = POSITION_TYPE_BUY; + } + } + // Place sell order if RSI crosses above overbought level (overbought crossover) + else if(data.rsiCrossedOverbought) + { + double sl = data.UseStopLoss ? currentAsk + data.StopLossPips * data.point : 0; + double tp = data.UseTakeProfit ? currentAsk - data.TakeProfitPips * data.point : 0; + + if(data.UseStopLoss && sl <= currentAsk) + return; + if(data.UseTakeProfit && tp >= currentAsk) + return; + + // Set trade parameters + data.trade.SetDeviationInPoints(data.Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_IOC); + data.trade.SetExpertMagicNumber(data.MagicNumber); + + // Use dynamic lot size + double tradeLotSize = lotSize > 0 ? lotSize : data.MaxLotSize; + + // Place sell order using CTrade + if(data.trade.Sell(tradeLotSize, data.symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell")) + { + data.isPositionOpen = true; + data.positionOpenPrice = currentBid; + data.positionOpenTime = TimeCurrent(); + data.lastPositionType = POSITION_TYPE_SELL; + } + } + } +} diff --git a/frontline/MQL5/_united_dynamic_cent/Strategies/RSIScalpingStrategy.mqh b/frontline/MQL5/_united_dynamic_cent/Strategies/RSIScalpingStrategy.mqh new file mode 100644 index 0000000..666dc1d --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/Strategies/RSIScalpingStrategy.mqh @@ -0,0 +1,451 @@ +//+------------------------------------------------------------------+ +//| RSIScalpingStrategy.mqh | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| RSI Scalping Strategy Data Structure | +//+------------------------------------------------------------------+ +struct RSIScalpingData { + string symbol; + bool isInitialized; + CTrade trade; + int rsi_handle; + double rsi_buffer[]; + double rsi_prev; + double rsi_current; + double rsi_two_bars_ago; + bool position_open; + ulong position_ticket; + ENUM_POSITION_TYPE current_position_type; + datetime last_bar_time; + bool rsi_against_position; + int bars_against_count; +}; + +string ErrorDescription(int errorCode) +{ + switch(errorCode) + { + case 4801: return "Symbol not found"; + case 4802: return "Symbol not selected"; + case 4803: return "Symbol not visible"; + case 4804: return "Symbol not available"; + case 4805: return "Cannot load indicator - insufficient history data"; + default: return "Unknown error " + IntegerToString(errorCode); + } +} + +bool InitRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period, + ENUM_APPLIED_PRICE RSI_Applied_Price, int MagicNumber, int Slippage) +{ + data.symbol = symbol; + data.isInitialized = false; + + // Check if symbol exists + if(!SymbolSelect(symbol, true)) + { + Print("RSIScalping: Symbol '", symbol, "' not available in Market Watch. Please add it to Market Watch or check symbol name."); + return false; // Return false but don't fail entire EA + } + + // Wait a bit for symbol to be ready + Sleep(100); + + // Try to create RSI indicator with retry logic (for insufficient history in backtesting) + data.rsi_handle = INVALID_HANDLE; + int retryCount = 0; + int maxRetries = 5; + + while(retryCount < maxRetries && data.rsi_handle == INVALID_HANDLE) + { + data.rsi_handle = iRSI(symbol, TimeFrame, RSI_Period, RSI_Applied_Price); + + if(data.rsi_handle == INVALID_HANDLE) + { + int error = GetLastError(); + + // Error 4805 = insufficient history - wait longer and retry + if(error == 4805 && retryCount < maxRetries - 1) + { + Sleep(1000); // Wait 1 second for history to load + retryCount++; + continue; + } + + Print("RSIScalping: Error creating RSI indicator for '", symbol, "' - Error: ", error, " (", ErrorDescription(error), ")"); + return false; // Return false but don't fail entire EA + } + } + + if(data.rsi_handle == INVALID_HANDLE) + { + Print("RSIScalping: Failed to create RSI indicator for '", symbol, "' after ", maxRetries, " retries"); + return false; + } + + data.trade.SetExpertMagicNumber(MagicNumber); + data.trade.SetDeviationInPoints(Slippage); + data.trade.SetTypeFilling(ORDER_FILLING_FOK); + + ArraySetAsSeries(data.rsi_buffer, true); + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + data.isInitialized = true; + + Print("RSIScalping: Successfully initialized for symbol '", symbol, "'"); + return true; +} + +void DeinitRSIScalping(RSIScalpingData& data) +{ + if(data.rsi_handle != INVALID_HANDLE) + IndicatorRelease(data.rsi_handle); +} + +bool UpdateRSI(RSIScalpingData& data) +{ + if(CopyBuffer(data.rsi_handle, 0, 0, 3, data.rsi_buffer) < 3) + return false; + + data.rsi_current = data.rsi_buffer[0]; + data.rsi_prev = data.rsi_buffer[1]; + data.rsi_two_bars_ago = data.rsi_buffer[2]; + + return true; +} + +void CheckExistingPosition(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber, + double RSI_Oversold, double RSI_Overbought, double RSI_Target_Buy, + double RSI_Target_Sell, int BarsToWait) +{ + // Always check if position exists, even if tracking says it doesn't + bool positionExists = PositionExistsByMagic(data.symbol, MagicNumber); + + if(!positionExists && data.position_open) + { + // Position was closed externally, reset tracking + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + return; + } + + if(!positionExists) + return; + + // Update tracking if we have a position but tracking was lost + if(!data.position_open && positionExists) + { + ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber); + if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber)) + { + data.position_ticket = ticket; + data.position_open = true; + data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + } + } + + // Verify our tracked position still exists + if(data.position_open && data.position_ticket > 0) + { + if(!PositionSelectByTicketSymbolAndMagic(data.position_ticket, data.symbol, MagicNumber)) + { + // Try to find the position again + ulong ticket = GetPositionTicketByMagic(data.symbol, MagicNumber); + if(ticket > 0 && PositionSelectByTicketSymbolAndMagic(ticket, data.symbol, MagicNumber)) + { + data.position_ticket = ticket; + data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + } + else + { + // Position doesn't exist, reset tracking + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + return; + } + } + else + { + // Update position type in case it changed (shouldn't happen, but be safe) + data.current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + } + } + + if(data.current_position_type == POSITION_TYPE_BUY) + { + if(data.rsi_current < RSI_Oversold) + { + if(!data.rsi_against_position) + { + data.rsi_against_position = true; + data.bars_against_count = 1; + } + else + { + data.bars_against_count++; + } + + if(data.bars_against_count >= BarsToWait) + { + ClosePosition(data, MagicNumber); + return; + } + } + else + { + if(data.rsi_against_position) + { + data.rsi_against_position = false; + data.bars_against_count = 0; + } + + if(data.rsi_current >= RSI_Target_Buy) + { + ClosePosition(data, MagicNumber); + } + } + } + else if(data.current_position_type == POSITION_TYPE_SELL) + { + if(data.rsi_current > RSI_Overbought) + { + if(!data.rsi_against_position) + { + data.rsi_against_position = true; + data.bars_against_count = 1; + } + else + { + data.bars_against_count++; + } + + if(data.bars_against_count >= BarsToWait) + { + ClosePosition(data, MagicNumber); + return; + } + } + else + { + if(data.rsi_against_position) + { + data.rsi_against_position = false; + data.bars_against_count = 0; + } + + if(data.rsi_current <= RSI_Target_Sell) + { + ClosePosition(data, MagicNumber); + } + } + } +} + +void CheckEntrySignals(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int MagicNumber, + double RSI_Oversold, double RSI_Overbought, double LotSize) +{ + if(data.rsi_two_bars_ago <= RSI_Oversold && data.rsi_prev > RSI_Oversold) + { + OpenBuyPosition(data, MagicNumber, LotSize); + } + + if(data.rsi_two_bars_ago >= RSI_Overbought && data.rsi_prev < RSI_Overbought) + { + OpenSellPosition(data, MagicNumber, LotSize); + } +} + +//+------------------------------------------------------------------+ +//| Normalize Lot Size According to Symbol Properties | +//+------------------------------------------------------------------+ +double NormalizeLotSize(string symbol, double lotSize) +{ + double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); + double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); + double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); + + // Round to lot step + if(lotStep > 0) + lotSize = MathFloor(lotSize / lotStep) * lotStep; + + // Apply min/max constraints + if(lotSize < minLot) + lotSize = minLot; + if(lotSize > maxLot) + lotSize = maxLot; + + return lotSize; +} + +void OpenBuyPosition(RSIScalpingData& data, int MagicNumber, double LotSize) +{ + if(PositionExistsByMagic(data.symbol, MagicNumber)) + return; + + // Normalize lot size according to symbol properties + double normalizedLot = NormalizeLotSize(data.symbol, LotSize); + + double ask = SymbolInfoDouble(data.symbol, SYMBOL_ASK); + + if(data.trade.Buy(normalizedLot, data.symbol, ask, 0, 0, "RSI Scalping Buy")) + { + ulong new_ticket = data.trade.ResultOrder(); + if(new_ticket > 0) + { + if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber)) + { + data.position_ticket = new_ticket; + data.position_open = true; + data.current_position_type = POSITION_TYPE_BUY; + } + } + } +} + +void OpenSellPosition(RSIScalpingData& data, int MagicNumber, double LotSize) +{ + if(PositionExistsByMagic(data.symbol, MagicNumber)) + return; + + // Normalize lot size according to symbol properties + double normalizedLot = NormalizeLotSize(data.symbol, LotSize); + + double bid = SymbolInfoDouble(data.symbol, SYMBOL_BID); + + if(data.trade.Sell(normalizedLot, data.symbol, bid, 0, 0, "RSI Scalping Sell")) + { + ulong new_ticket = data.trade.ResultOrder(); + if(new_ticket > 0) + { + if(PositionSelectByTicketSymbolAndMagic(new_ticket, data.symbol, MagicNumber)) + { + data.position_ticket = new_ticket; + data.position_open = true; + data.current_position_type = POSITION_TYPE_SELL; + } + } + } +} + +void ClosePosition(RSIScalpingData& data, int MagicNumber) +{ + // First verify position still exists + if(!PositionExistsByMagic(data.symbol, MagicNumber)) + { + // Position doesn't exist, reset tracking + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + return; + } + + // Try to close by ticket first (more reliable) + bool closed = false; + if(data.position_ticket > 0) + { + if(PositionSelectByTicket(data.position_ticket)) + { + // Verify it's our position + if(PositionGetString(POSITION_SYMBOL) == data.symbol && + PositionGetInteger(POSITION_MAGIC) == MagicNumber) + { + closed = data.trade.PositionClose(data.position_ticket); + if(!closed) + { + Print("RSIScalping: Failed to close position by ticket ", data.position_ticket, + " - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")"); + } + } + } + } + + // If ticket method failed, try magic number method + if(!closed) + { + closed = ClosePositionByMagic(data.trade, data.symbol, MagicNumber); + if(!closed) + { + Print("RSIScalping: Failed to close position by magic number for '", data.symbol, + "' - Error: ", data.trade.ResultRetcode(), " (", data.trade.ResultRetcodeDescription(), ")"); + } + } + + // Verify position is actually closed + if(closed) + { + // Wait a moment and verify + Sleep(50); + if(!PositionExistsByMagic(data.symbol, MagicNumber)) + { + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + Print("RSIScalping: Position successfully closed for '", data.symbol, "'"); + } + else + { + Print("RSIScalping: Warning - Close returned success but position still exists for '", data.symbol, "'"); + // Try one more time + Sleep(100); + if(PositionExistsByMagic(data.symbol, MagicNumber)) + { + ClosePositionByMagic(data.trade, data.symbol, MagicNumber); + } + // Reset tracking anyway to prevent getting stuck + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + } + } + else + { + // Close failed, but reset tracking to prevent getting stuck + // The position might have been closed externally + data.position_open = false; + data.position_ticket = 0; + data.rsi_against_position = false; + data.bars_against_count = 0; + } +} + +void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES TimeFrame, int RSI_Period, + ENUM_APPLIED_PRICE RSI_Applied_Price, double RSI_Overbought, + double RSI_Oversold, double RSI_Target_Buy, double RSI_Target_Sell, + int BarsToWait, double LotSize, int MagicNumber) +{ + // Skip if not initialized (symbol not available) + if(!data.isInitialized) + return; + + data.symbol = symbol; // Update symbol in case it changed + if(Bars(data.symbol, TimeFrame) < RSI_Period + 2) + return; + + datetime current_bar_time = iTime(data.symbol, TimeFrame, 0); + if(current_bar_time == data.last_bar_time) + return; + + data.last_bar_time = current_bar_time; + + if(!UpdateRSI(data)) + return; + + CheckExistingPosition(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought, + RSI_Target_Buy, RSI_Target_Sell, BarsToWait); + + if(!data.position_open && !PositionExistsByMagic(data.symbol, MagicNumber)) + { + CheckEntrySignals(data, TimeFrame, MagicNumber, RSI_Oversold, RSI_Overbought, LotSize); + } +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/main.mq5 b/frontline/MQL5/_united_dynamic_cent/main.mq5 new file mode 100644 index 0000000..191d017 --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/main.mq5 @@ -0,0 +1,736 @@ +//+------------------------------------------------------------------+ +//| UnitedEA.mq5 | +//| Cent ".c" symbols; InpDynamicRefDeposit MUST match ACCOUNT_ | +//| CURRENCY numbers (USC ~50k for ~$500, or USD ~500 — not mixed). | +//| Per-order max lots = broker spec 最大量 SYMBOL_VOLUME_MAX | +//| (often 1000 on *.c); EA cannot exceed it — see symbol contract. | +//| Copyright 2025, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2025, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.10" +#property strict + +#include +#include +#include +#include +#include "MagicNumberHelpers.mqh" + +// Lot globals must exist before strategy .mqh (Darvas uses g_DB_LotSize; EMA/RC/RM use g_ES/g_RC/g_RM) +double g_ES_LotSize; +double g_RC_LotSize; +double g_RM_LotSize; +double g_DB_LotSize; +double g_DynLotScaleLast = 1.0; // last applied scale: lots = baseLot * scale * InpLotSizeScale +double g_DynamicRefBaseline = 0.0; // when InpDynamicRefDeposit<=0, frozen ref = equity at first sizing call + +// Include strategy implementations early so structs are available +#include "Strategies/DarvasBoxStrategy.mqh" +#include "Strategies/EMASlopeDistanceStrategy.mqh" +#include "Strategies/RSICrossOverReversalStrategy.mqh" +#include "Strategies/RSIMidPointHijackStrategy.mqh" +#include "Strategies/RSIScalpingStrategy.mqh" +#include "Strategies/RSIReversalAsianStrategy.mqh" + +//+------------------------------------------------------------------+ +//| Strategy Enable/Disable Switches | +//+------------------------------------------------------------------+ +input group "=== Strategy Enable/Disable ===" +input bool EnableDarvasBox = true; +input bool EnableEMASlopeDistance = true; +input bool EnableRSICrossOverReversal = true; +input bool EnableRSIMidPointHijack = true; +input bool EnableRSIScalpingAPPL = true; +input bool EnableRSIScalpingBTCUSD = true; +input bool EnableRSIScalpingNVDA = true; +input bool EnableRSIScalpingTSLA = true; +input bool EnableRSIScalpingXAUUSD = true; +input bool EnableRSIReversalAsianEURUSD = true; +input bool EnableRSIReversalAsianAUDUSD = true; + +//+------------------------------------------------------------------+ +//| Dynamic lot sizing — 默认「按比例」:lots = base × (equity/ref) × lotScale | +//| 可选幂曲线:lots = base × (equity/ref)^exp × lotScale(旧行为) | +//| min/max 约束的是「比例系数」不是手数本身;max<=0 表示比例系数上不封顶 | +//| | +//| USC (deposit currency) vs "lot size": | +//| • Equity/ref for the multiplier are BOTH in account currency | +//| (USC). Same units → ratio is correct; no ×100 on the ratio. | +//| • Strategy base lots (e.g. DB_BaseLotSize) are ORDER VOLUME in | +//| lots, not "USC lots". Broker SYMBOL_VOLUME_* / contract define | +//| how much margin and P/L appear in USC. | +//| • Do not multiply lot inputs by 100 only because balance is USC. | +//| If DD too high: raise InpDynamicRefDeposit and/or lower base lots.| +//| If balance is USD ~500 but ref is ~50k–300k: mult→floor, lots→0.01.| +//| Ref<=0: 挂上时余额/净值为参考,之后手数随净值相对该基准的比例变化。 | +//| 单笔上限:品种规格里的「最大量」(SYMBOL_VOLUME_MAX),非 EA 参数。 | +//| InpMaxLotsPerOrder:EA 再截一刀,防止动态+scale 顶满 1000 爆仓。 | +//+------------------------------------------------------------------+ +enum ENUM_LOT_SCALE_CURVE +{ + LOT_CURVE_PROPORTIONAL = 0, // 按比例:scale = 净值/参考(线性) + LOT_CURVE_POWER = 1 // 幂:scale = (净值/参考)^exp +}; + +input group "=== Dynamic lot sizing (动态手数) ===" +input bool InpDynamicLotEnable = true; // Enable balance/equity-based scaling +input ENUM_LOT_SCALE_CURVE InpDynamicLotCurve = LOT_CURVE_PROPORTIONAL; // 默认按比例;幂曲线=旧 (equity/ref)^exp +input double InpDynamicRefDeposit = 0.0; // <=0: auto — ref=挂上时净值/余额(与测试器初始一致则手数随盈利涨); >0 手动参考金 +input bool InpDynamicRefEqualsEquity = false; // true: 比例系数固定为 1(只用基础手×lotScale) +input double InpDynamicExponent = 1.22; // 仅 LOT_CURVE_POWER 时:(净值/参考) 的指数 +input double InpDynamicMinMult = 0.0; // 比例系数下限;<=0 不抬(按比例时净值<参考会缩小手数) +input double InpDynamicMaxMult = 20.0; // 比例系数上限;<=0 不封顶(高风险) +input bool InpDynamicUseEquity = true; // true=ACCOUNT_EQUITY, false=ACCOUNT_BALANCE +input double InpDynamicStockLotCap = 0.0; // Max lots after scale (0=off); raise if InpLotSizeScale is large +input double InpLotSizeScale = 1.0; // 全局手数倍率; 曾用100易过大,默认1按需再加 +input double InpMaxLotsPerOrder = 20.0; // 单笔最大手数(0=仅券商SYMBOL_VOLUME_MAX); 保守可设5~10 + +//+------------------------------------------------------------------+ +//| Strategy 1: DarvasBoxXAUUSD (cent symbol) | +//+------------------------------------------------------------------+ +input group "=== DarvasBox Strategy ===" +input string DB_Symbol = "XAUUSD.c"; +input int DB_BoxPeriod = 165; +input double DB_BoxDeviation = 30000; // Increased to allow larger ranges (was 25140) +input int DB_VolumeThreshold = 0; // Set to 0 to disable volume threshold check. Volume data from indicator used instead. +input double DB_StopLoss = 1665; +input double DB_TakeProfit = 3685; +input bool DB_EnableLogging = false; +input color DB_BoxColor = clrBlue; +input int DB_BoxWidth = 1; +input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2; +input int DB_MA_Period = 125; +input ENUM_MA_METHOD DB_MA_Method = MODE_EMA; +input ENUM_APPLIED_PRICE DB_MA_Price = PRICE_WEIGHTED; +input double DB_TrendThreshold = 4.94; +input int DB_VolumeMA_Period = 110; +input double DB_VolumeThresholdMultiplier = 1.5; +input int DB_MagicNumber = 135790; +input double DB_BaseLotSize = 0.02; // Base lot at InpDynamicRefDeposit (Darvas) + +//+------------------------------------------------------------------+ +//| Strategy 2: EMASlopeDistanceCocktailXAUUSD | +//| Cent: gold is usually "XAUUSD.c" (verify in Market Watch). | +//+------------------------------------------------------------------+ +input group "=== EMA Slope Distance Strategy ===" +input string ES_Symbol = "XAUUSD.c"; +input int ES_EMA_Periode = 46; +input double ES_PreisSchwelle = 600.0; +input double ES_SteigungSchwelle = 80.0; +input int ES_ÜberwachungTimeout = 800; +input double ES_TrailingStop = 250.0; +input double ES_LotGröße = 0.05; +input int ES_MagicNumber = 12350; +input bool ES_UseSpreadAdjustment = true; +input ENUM_TIMEFRAMES ES_Timeframe = PERIOD_H1; +input bool ES_UseBarData = true; +input int ES_MaxTradesPerCrossover = 9; +input int ES_ProfitCheckBars = 18; +input bool ES_CloseUnprofitableTrades = true; + +//+------------------------------------------------------------------+ +//| Strategy 3: RSICrossOverReversalXAUUSD | +//| Cent: use "XAUUSD.c" if that is what the broker lists. | +//+------------------------------------------------------------------+ +input group "=== RSI CrossOver Reversal Strategy ===" +input string RC_Symbol = "XAUUSD.c"; +input int RC_MagicNumber = 7; +input int RC_rsiPeriod = 19; +input int RC_overboughtLevel = 93; +input int RC_oversoldLevel = 22; +input double RC_entryRSIBuySpread = 0; +input double RC_entryRSISellSpread = 0; +input double RC_lotSize = 0.02; +input int RC_slippage = 3; +input int RC_cooldownSeconds = 209; +input ENUM_TIMEFRAMES RC_TimeFrame1 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_TimeFrame2 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_BarTimeFrame = PERIOD_M12; +input int RC_emaPeriod = 140; +input double RC_emaSlopeThreshold = 105; +input double RC_exitBuyRSI = 86; +input double RC_exitSellRSI = 10; +input double RC_TrailingStop = 295; +input double RC_emaDistanceThreshold = 165; +input int RC_tradingHourOneBegin = 24; +input int RC_tradingHourOneEnd = 22; +input int RC_tradingHourTwoBegin = 6; +input int RC_tradingHourTwoEnd = 19; +input bool RC_Sunday = false; +input bool RC_Monday = false; +input bool RC_Tuesday = true; +input bool RC_Wednesday = true; +input bool RC_Thursday = true; +input bool RC_Friday = false; +input bool RC_Saturday = false; + +//+------------------------------------------------------------------+ +//| Strategy 4: RSIMidPointHijackXAUUSD | +//| Cent: use "XAUUSD.c" if that is what the broker lists. | +//+------------------------------------------------------------------+ +input group "=== RSI MidPoint Hijack Strategy ===" +input string RM_Symbol = "XAUUSD.c"; +input ENUM_TIMEFRAMES RM_InpTimeframe = PERIOD_H1; +input double RM_InpLotSize = 0.03; +input int RM_InpMagicNumberRSIFollow = 1001; +input int RM_InpMagicNumberRSIReverse = 1002; +input int RM_InpMagicNumberEMACross = 1003; +input bool RM_InpEnableRSIFollow = true; +input bool RM_InpEnableRSIReverse = true; +input bool RM_InpEnableEMACross = true; +input bool RM_InpEnableStrategyLock = false; +input double RM_InpLockProfitThreshold = 0.0; +input bool RM_InpCloseOppositeTrades = false; +input int RM_InpRSIPeriod = 32; +input int RM_InpRSIOverbought = 78; +input int RM_InpRSIOversold = 46; +input int RM_InpRSIExitLevel = 44; +input int RM_InpRSIFollowStartHour = 23; +input int RM_InpRSIFollowEndHour = 8; +input bool RM_InpRSIFollowCloseOutsideHours = false; +input int RM_InpRSIReversePeriod = 59; +input int RM_InpRSIReverseOverbought = 51; +input int RM_InpRSIReverseOversold = 49; +input int RM_InpRSIReverseCrossLevel = 53; +input int RM_InpRSIReverseExitLevel = 48; +input int RM_InpRSIReverseStartHour = 7; +input int RM_InpRSIReverseEndHour = 13; +input bool RM_InpRSIReverseCloseOutsideHours = false; +input int RM_InpRSIReverseCooldownBars = 15; +input bool RM_InpRSIReverseCooldownOnLoss = true; +input int RM_InpEMAPeriod = 120; +input int RM_InpEMACrossStartHour = 8; +input int RM_InpEMACrossEndHour = 14; +input bool RM_InpEMACrossCloseOutsideHours = true; +input bool RM_InpUseEMADistanceEntry = true; +input double RM_InpEMADistancePips = 160.0; +input int RM_InpEMADistancePeriod = 26; + +//+------------------------------------------------------------------+ +//| Strategy 5-10: RSI Scalping Strategies | +//| Each RSI Scalping strategy trades on its own symbol: | +//| - APPL: Apple stock (AAPL) | +//| - BTCUSD: Bitcoin/USD | +//| - NVDA: NVIDIA stock | +//| - TSLA: Tesla stock | +//| - XAUUSD: Gold/USD | +//| | +//| USC cent: many symbols end with ".c" — use Market Watch names. | +//| Stocks may be "AAPL.US.c" or unchanged; verify before live. | +//+------------------------------------------------------------------+ +input group "=== RSI Scalping APPL (AAPL) - cent ===" +input string RS_APPL_Symbol = "AAPL.US.c"; // If missing, try AAPL.US / NASDAQ:AAPL / AAPL +input ENUM_TIMEFRAMES RS_APPL_TimeFrame = PERIOD_M10; +input int RS_APPL_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_APPL_RSI_Applied_Price = PRICE_CLOSE; +input double RS_APPL_RSI_Overbought = 80; +input double RS_APPL_RSI_Oversold = 78; +input double RS_APPL_RSI_Target_Buy = 94; +input double RS_APPL_RSI_Target_Sell = 44; +input int RS_APPL_BarsToWait = 7; +input double RS_APPL_LotSize = 38; +input int RS_APPL_MagicNumber = 20001; +input int RS_APPL_Slippage = 3; + +input group "=== RSI Scalping BTCUSD ===" +input string RS_BTCUSD_Symbol = "BTCUSD.c"; // If missing, try BTCUSD or BTC/USD +input ENUM_TIMEFRAMES RS_BTCUSD_TimeFrame = PERIOD_H1; +input int RS_BTCUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_BTCUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_BTCUSD_RSI_Overbought = 90; +input double RS_BTCUSD_RSI_Oversold = 73; +input double RS_BTCUSD_RSI_Target_Buy = 88; +input double RS_BTCUSD_RSI_Target_Sell = 48; +input int RS_BTCUSD_BarsToWait = 6; +input double RS_BTCUSD_LotSize = 0.15; +input int RS_BTCUSD_MagicNumber = 123459123; +input int RS_BTCUSD_Slippage = 3; + +input group "=== RSI Scalping NVDA - cent ===" +input string RS_NVDA_Symbol = "NVDA.US.c"; // If missing, try NVDA.US / NASDAQ:NVDA / NVDA +input ENUM_TIMEFRAMES RS_NVDA_TimeFrame = PERIOD_M15; +input int RS_NVDA_RSI_Period = 8; +input ENUM_APPLIED_PRICE RS_NVDA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_NVDA_RSI_Overbought = 36; +input double RS_NVDA_RSI_Oversold = 38; +input double RS_NVDA_RSI_Target_Buy = 90; +input double RS_NVDA_RSI_Target_Sell = 70; +input int RS_NVDA_BarsToWait = 5; +input double RS_NVDA_LotSize = 75; +input int RS_NVDA_MagicNumber = 20003; +input int RS_NVDA_Slippage = 3; + +input group "=== RSI Scalping TSLA - cent ===" +input string RS_TSLA_Symbol = "TSLA.US.c"; // If missing, try TSLA.US / NASDAQ:TSLA / TSLA +input ENUM_TIMEFRAMES RS_TSLA_TimeFrame = PERIOD_H1; +input int RS_TSLA_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_TSLA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_TSLA_RSI_Overbought = 54; +input double RS_TSLA_RSI_Oversold = 73; +input double RS_TSLA_RSI_Target_Buy = 87; +input double RS_TSLA_RSI_Target_Sell = 33; +input int RS_TSLA_BarsToWait = 1; +input double RS_TSLA_LotSize = 75; +input int RS_TSLA_MagicNumber = 125421321; +input int RS_TSLA_Slippage = 3; + +input group "=== RSI Scalping XAUUSD ===" +input string RS_XAUUSD_Symbol = "XAUUSD.c"; +input ENUM_TIMEFRAMES RS_XAUUSD_TimeFrame = PERIOD_H1; +input int RS_XAUUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_XAUUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_XAUUSD_RSI_Overbought = 71; +input double RS_XAUUSD_RSI_Oversold = 57; +input double RS_XAUUSD_RSI_Target_Buy = 80; +input double RS_XAUUSD_RSI_Target_Sell = 57; +input int RS_XAUUSD_BarsToWait = 4; +input double RS_XAUUSD_LotSize = 0.15; +input int RS_XAUUSD_MagicNumber = 129102315; +input int RS_XAUUSD_Slippage = 3; + +//+------------------------------------------------------------------+ +//| Strategy 11-12: RSI Reversal Asian Strategies | +//| Each RSI Reversal Asian strategy trades on its own symbol: | +//| - EURUSD: Euro/USD | +//| - AUDUSD: Australian Dollar/USD | +//+------------------------------------------------------------------+ +input group "=== RSI Reversal Asian EURUSD ===" +input string RRA_EURUSD_Symbol = "EURUSD.c"; +input int RRA_EURUSD_RSIPeriod = 28; +input double RRA_EURUSD_OverboughtLevel = 60; +input double RRA_EURUSD_OversoldLevel = 8; +input int RRA_EURUSD_TakeProfitPips = 175; +input int RRA_EURUSD_StopLossPips = 5; +input double RRA_EURUSD_MaxLotSize = 0.15; +input int RRA_EURUSD_MaxSpread = 1000; +input int RRA_EURUSD_MaxDuration = 270; +input bool RRA_EURUSD_UseStopLoss = false; +input bool RRA_EURUSD_UseTakeProfit = false; +input bool RRA_EURUSD_UseRSIExit = true; +input double RRA_EURUSD_RSIExitLevel = 55; +input bool RRA_EURUSD_CloseOutsideSession = false; +input ENUM_TIMEFRAMES RRA_EURUSD_TimeFrame = PERIOD_M15; +input int RRA_EURUSD_MagicNumber = 30001; +input int RRA_EURUSD_Slippage = 3; + +input group "=== RSI Reversal Asian AUDUSD ===" +input string RRA_AUDUSD_Symbol = "AUDUSD.c"; +input int RRA_AUDUSD_RSIPeriod = 28; +input double RRA_AUDUSD_OverboughtLevel = 68; +input double RRA_AUDUSD_OversoldLevel = 30; +input int RRA_AUDUSD_TakeProfitPips = 175; +input int RRA_AUDUSD_StopLossPips = 5; +input double RRA_AUDUSD_MaxLotSize = 0.3; +input int RRA_AUDUSD_MaxSpread = 1000; +input int RRA_AUDUSD_MaxDuration = 340; +input bool RRA_AUDUSD_UseStopLoss = false; +input bool RRA_AUDUSD_UseTakeProfit = false; +input bool RRA_AUDUSD_UseRSIExit = true; +input double RRA_AUDUSD_RSIExitLevel = 48; +input bool RRA_AUDUSD_CloseOutsideSession = true; +input ENUM_TIMEFRAMES RRA_AUDUSD_TimeFrame = PERIOD_M15; +input int RRA_AUDUSD_MagicNumber = 30002; +input int RRA_AUDUSD_Slippage = 3; + +//+------------------------------------------------------------------+ +//| Global Variables - DarvasBox | +//+------------------------------------------------------------------+ +struct DarvasBoxData { + string symbol; + bool isInitialized; + double boxHigh; + double boxLow; + bool boxFormed; + datetime lastBoxTime; + string boxName; + double minStopLevel; + double point; + CTrade trade; + int maHandle; + int volumeHandle; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - EMA Slope Distance | +//+------------------------------------------------------------------+ +struct EMASlopeData { + string symbol; + bool isInitialized; + int ema_handle; + double ema_array[]; + datetime letzte_überwachung_zeit; + bool überwachung_aktiv; + bool preis_trigger_aktiv; + bool steigung_trigger_aktiv; + int ticket; + CTrade trade; + int trades_in_current_crossover; + bool crossover_detected; + datetime trade_open_time; + datetime last_bar_time; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI CrossOver Reversal | +//+------------------------------------------------------------------+ +struct RSICrossOverData { + string symbol; + bool isInitialized; + int rsiHandle; + int emaHandle; + double previousRSIDef; + CTrade trade; + datetime lastTradeTime; + datetime bartime; + bool WeekDays[7]; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI MidPoint Hijack | +//+------------------------------------------------------------------+ +struct RSIMidPointData { + string symbol; + bool isInitialized; + int rsiHandle; + int rsiReverseHandle; + int emaHandle; + bool rsiOverbought; + bool rsiOversold; + bool rsiReverseOverbought; + bool rsiReverseOversold; + CTrade trade; + CPositionInfo positionInfo; + bool emaCrossBuySignal; + bool emaCrossSellSignal; + int emaCrossSignalBar; + datetime lastBarTime; + datetime rsiReverseLastCloseTime; + bool rsiReverseInCooldown; + double lastBarRSI; + double lastBarRSIReverse; + double lastBarEMA; + double lastBarClose; + double lastBarEMAPrev; + double lastBarClosePrev; +}; + +//+------------------------------------------------------------------+ +//| Global Strategy Instances | +//+------------------------------------------------------------------+ +DarvasBoxData dbData; +EMASlopeData esData; +RSICrossOverData rcData; +RSIMidPointData rmData; +RSIScalpingData rsAPPLData; +RSIScalpingData rsBTCUSDData; +RSIScalpingData rsNVDAData; +RSIScalpingData rsTSLAData; +RSIScalpingData rsXAUUSDData; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI Reversal Asian | +//+------------------------------------------------------------------+ +RSIReversalAsianData rraEURUSDData; +RSIReversalAsianData rraAUDUSDData; + +//+------------------------------------------------------------------+ +//| Dynamic lot helpers | +//+------------------------------------------------------------------+ +double DynClamp(const double v, const double lo, const double hi) +{ + return MathMax(lo, MathMin(hi, v)); +} + +// Clamp scale factor (proportion or pow result). max<=0 = no upper clamp. +double ApplyDynamicScaleClamp(const double scaleRaw) +{ + double s = scaleRaw; + if(InpDynamicMinMult > 0.0) + s = MathMax(s, InpDynamicMinMult); + if(InpDynamicMaxMult > 0.0) + s = MathMin(s, InpDynamicMaxMult); + return s; +} + +// Reference for (equity/ref)^exp: manual deposit, or first-seen balance when input <= 0 +double GetDynamicRefForRatio() +{ + if(InpDynamicRefDeposit > 0.0) + return MathMax(InpDynamicRefDeposit, 1.0); + double capNow = InpDynamicUseEquity ? AccountInfoDouble(ACCOUNT_EQUITY) : AccountInfoDouble(ACCOUNT_BALANCE); + if(g_DynamicRefBaseline <= 0.0) + g_DynamicRefBaseline = MathMax(capNow, 1.0); + return MathMax(g_DynamicRefBaseline, 1.0); +} + +// Broker hard cap: 最大量 = SYMBOL_VOLUME_MAX (e.g. 1000 on EURUSD.c / XAUUSD.c) +double NormalizeVolumeForSymbol(const string symbol, double lots) +{ + double minL = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); + double maxL = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); + double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); + if(step > 0.0) + lots = MathFloor(lots / step + 1e-12) * step; + if(lots < minL) lots = minL; + if(lots > maxL) lots = maxL; + return lots; +} + +// Apply EA risk cap before broker min/step/max (InpMaxLotsPerOrder 0 = disabled) +double NormalizeVolumeForSymbolWithEACap(const string symbol, double lots) +{ + if(InpMaxLotsPerOrder > 0.0) + lots = MathMin(lots, InpMaxLotsPerOrder); + return NormalizeVolumeForSymbol(symbol, lots); +} + +// Scale factor for lots: baseLot * scale * InpLotSizeScale (then caps) +double GetDynamicLotScaleFactor() +{ + if(!InpDynamicLotEnable) + return 1.0; + if(InpDynamicRefEqualsEquity) + return ApplyDynamicScaleClamp(1.0); + double cap = InpDynamicUseEquity ? AccountInfoDouble(ACCOUNT_EQUITY) : AccountInfoDouble(ACCOUNT_BALANCE); + double refv = GetDynamicRefForRatio(); + if(cap <= 0.0) + cap = refv; + double ratio = cap / refv; + if(ratio <= 0.0) + ratio = 1.0; + double scaleRaw = ratio; + if(InpDynamicLotCurve == LOT_CURVE_POWER) + scaleRaw = MathPow(ratio, InpDynamicExponent); + return ApplyDynamicScaleClamp(scaleRaw); +} + +// baseLot = size at reference deposit; optionalCap 0 = no extra ceiling (broker min/max still apply) +double DynamicLotForSymbol(const string symbol, const double baseLot, const double optionalCap = 0.0) +{ + double scale = GetDynamicLotScaleFactor(); + g_DynLotScaleLast = scale; + double sc = (InpLotSizeScale > 0.0 ? InpLotSizeScale : 1.0); + double v = baseLot * scale * sc; + if(optionalCap > 0.0 && v > optionalCap) + v = optionalCap; + return NormalizeVolumeForSymbolWithEACap(symbol, v); +} + +void RefreshDynamicStrategyLots() +{ + double sc = (InpLotSizeScale > 0.0 ? InpLotSizeScale : 1.0); + if(!InpDynamicLotEnable) + { + g_ES_LotSize = NormalizeVolumeForSymbolWithEACap(ES_Symbol, ES_LotGröße * sc); + g_RC_LotSize = NormalizeVolumeForSymbolWithEACap(RC_Symbol, RC_lotSize * sc); + g_RM_LotSize = NormalizeVolumeForSymbolWithEACap(RM_Symbol, RM_InpLotSize * sc); + g_DB_LotSize = NormalizeVolumeForSymbolWithEACap(DB_Symbol, DB_BaseLotSize * sc); + g_DynLotScaleLast = 1.0; + return; + } + g_ES_LotSize = DynamicLotForSymbol(ES_Symbol, ES_LotGröße); + g_RC_LotSize = DynamicLotForSymbol(RC_Symbol, RC_lotSize); + g_RM_LotSize = DynamicLotForSymbol(RM_Symbol, RM_InpLotSize); + g_DB_LotSize = DynamicLotForSymbol(DB_Symbol, DB_BaseLotSize); +} + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + int initResult = INIT_SUCCEEDED; + + g_DynamicRefBaseline = 0.0; + + RefreshDynamicStrategyLots(); + + string acctCur = AccountInfoString(ACCOUNT_CURRENCY); + double eq0 = AccountInfoDouble(ACCOUNT_EQUITY); + if(InpDynamicLotEnable && !InpDynamicRefEqualsEquity && InpDynamicRefDeposit > 1000.0 && eq0 > 0.0 + && eq0 <= InpDynamicRefDeposit / 25.0) + Print("United EA: equity ", DoubleToString(eq0, 2), " ", acctCur, " vs ref ", InpDynamicRefDeposit, + " — dynamic mult is tiny; set InpDynamicRefDeposit to your balance in ", acctCur, + " (e.g. 500 for USD) or enable InpDynamicRefEqualsEquity. Else lots stay at broker minimum."); + + // Initialize strategies - log warnings but don't fail entire EA if symbol unavailable + if(EnableDarvasBox) + if(!InitDarvasBox(DB_Symbol)) + Print("Warning: DarvasBox strategy failed to initialize for symbol '", DB_Symbol, "'"); + + if(EnableEMASlopeDistance) + if(!InitEMASlopeDistance(ES_Symbol)) + Print("Warning: EMASlopeDistance strategy failed to initialize for symbol '", ES_Symbol, "'"); + + if(EnableRSICrossOverReversal) + if(!InitRSICrossOverReversal(RC_Symbol)) + Print("Warning: RSICrossOverReversal strategy failed to initialize for symbol '", RC_Symbol, "'"); + + if(EnableRSIMidPointHijack) + if(!InitRSIMidPointHijack(RM_Symbol)) + Print("Warning: RSIMidPointHijack strategy failed to initialize for symbol '", RM_Symbol, "'"); + + // Initialize RSI Scalping strategies - don't fail entire EA if symbol unavailable + if(EnableRSIScalpingAPPL) + InitRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, RS_APPL_MagicNumber, RS_APPL_Slippage); + + if(EnableRSIScalpingBTCUSD) + InitRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, RS_BTCUSD_MagicNumber, RS_BTCUSD_Slippage); + + if(EnableRSIScalpingNVDA) + InitRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, RS_NVDA_MagicNumber, RS_NVDA_Slippage); + + if(EnableRSIScalpingTSLA) + InitRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, RS_TSLA_MagicNumber, RS_TSLA_Slippage); + + if(EnableRSIScalpingXAUUSD) + InitRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, RS_XAUUSD_MagicNumber, RS_XAUUSD_Slippage); + + // Initialize RSI Reversal Asian strategies + if(EnableRSIReversalAsianEURUSD) + if(!InitRSIReversalAsian(rraEURUSDData, RRA_EURUSD_Symbol, RRA_EURUSD_RSIPeriod, RRA_EURUSD_OverboughtLevel, RRA_EURUSD_OversoldLevel, + RRA_EURUSD_TakeProfitPips, RRA_EURUSD_StopLossPips, RRA_EURUSD_MaxLotSize, + RRA_EURUSD_MaxSpread, RRA_EURUSD_MaxDuration, RRA_EURUSD_UseStopLoss, + RRA_EURUSD_UseTakeProfit, RRA_EURUSD_UseRSIExit, RRA_EURUSD_RSIExitLevel, + RRA_EURUSD_CloseOutsideSession, RRA_EURUSD_TimeFrame, RRA_EURUSD_MagicNumber, RRA_EURUSD_Slippage)) + Print("Warning: RSIReversalAsianEURUSD strategy failed to initialize for symbol '", RRA_EURUSD_Symbol, "'"); + + if(EnableRSIReversalAsianAUDUSD) + if(!InitRSIReversalAsian(rraAUDUSDData, RRA_AUDUSD_Symbol, RRA_AUDUSD_RSIPeriod, RRA_AUDUSD_OverboughtLevel, RRA_AUDUSD_OversoldLevel, + RRA_AUDUSD_TakeProfitPips, RRA_AUDUSD_StopLossPips, RRA_AUDUSD_MaxLotSize, + RRA_AUDUSD_MaxSpread, RRA_AUDUSD_MaxDuration, RRA_AUDUSD_UseStopLoss, + RRA_AUDUSD_UseTakeProfit, RRA_AUDUSD_UseRSIExit, RRA_AUDUSD_RSIExitLevel, + RRA_AUDUSD_CloseOutsideSession, RRA_AUDUSD_TimeFrame, RRA_AUDUSD_MagicNumber, RRA_AUDUSD_Slippage)) + Print("Warning: RSIReversalAsianAUDUSD strategy failed to initialize for symbol '", RRA_AUDUSD_Symbol, "'"); + + double refEffInit = GetDynamicRefForRatio(); + double capInit = InpDynamicUseEquity ? eq0 : AccountInfoDouble(ACCOUNT_BALANCE); + if(capInit <= 0.0) + capInit = refEffInit; + double ratioInit = capInit / refEffInit; + double powInit = MathPow(ratioInit, InpDynamicExponent); + string curveStr = (InpDynamicLotCurve == LOT_CURVE_POWER ? "POWER" : "PROP"); + Print("United EA v1.10 ", acctCur, " curve=", curveStr, " equity=", DoubleToString(eq0, 2), " refEff=", DoubleToString(refEffInit, 2), + " (inpRef=", InpDynamicRefDeposit, " baseline=", DoubleToString(g_DynamicRefBaseline, 2), ") equity/ref=", DoubleToString(ratioInit, 6), + " pow^exp=", DoubleToString(powInit, 6), " scaleOut=", DoubleToString(g_DynLotScaleLast, 6), + " minS=", InpDynamicMinMult, " maxS=", InpDynamicMaxMult, " lotScale=", InpLotSizeScale, " maxLots=", InpMaxLotsPerOrder, + " lots ES=", g_ES_LotSize, " RC=", g_RC_LotSize, " RM=", g_RM_LotSize, " DB=", g_DB_LotSize); + Print("United EA initialized. Active strategies: ", + (EnableDarvasBox ? "DarvasBox " : ""), + (EnableEMASlopeDistance ? "EMASlope " : ""), + (EnableRSICrossOverReversal ? "RSICrossOver " : ""), + (EnableRSIMidPointHijack ? "RSIMidPoint " : ""), + (EnableRSIScalpingAPPL ? "RSIScalpingAPPL " : ""), + (EnableRSIScalpingBTCUSD ? "RSIScalpingBTCUSD " : ""), + (EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""), + (EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""), + (EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""), + (EnableRSIReversalAsianEURUSD ? "RSIReversalAsianEURUSD " : ""), + (EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : "")); + + return initResult; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(EnableDarvasBox) + DeinitDarvasBox(); + + if(EnableEMASlopeDistance) + DeinitEMASlopeDistance(); + + if(EnableRSICrossOverReversal) + DeinitRSICrossOverReversal(); + + if(EnableRSIMidPointHijack) + DeinitRSIMidPointHijack(); + + if(EnableRSIScalpingAPPL) + DeinitRSIScalping(rsAPPLData); + + if(EnableRSIScalpingBTCUSD) + DeinitRSIScalping(rsBTCUSDData); + + if(EnableRSIScalpingNVDA) + DeinitRSIScalping(rsNVDAData); + + if(EnableRSIScalpingTSLA) + DeinitRSIScalping(rsTSLAData); + + if(EnableRSIScalpingXAUUSD) + DeinitRSIScalping(rsXAUUSDData); + + if(EnableRSIReversalAsianEURUSD) + DeinitRSIReversalAsian(rraEURUSDData); + + if(EnableRSIReversalAsianAUDUSD) + DeinitRSIReversalAsian(rraAUDUSDData); + + Print("United EA deinitialized. Reason: ", reason); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + RefreshDynamicStrategyLots(); + + if(EnableDarvasBox) + ProcessDarvasBox(DB_Symbol); + + if(EnableEMASlopeDistance) + ProcessEMASlopeDistance(ES_Symbol); + + if(EnableRSICrossOverReversal) + ProcessRSICrossOverReversal(RC_Symbol); + + if(EnableRSIMidPointHijack) + ProcessRSIMidPointHijack(RM_Symbol); + + if(EnableRSIScalpingAPPL) + ProcessRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, + RS_APPL_RSI_Overbought, RS_APPL_RSI_Oversold, RS_APPL_RSI_Target_Buy, RS_APPL_RSI_Target_Sell, + RS_APPL_BarsToWait, + DynamicLotForSymbol(RS_APPL_Symbol, RS_APPL_LotSize, InpDynamicStockLotCap), + RS_APPL_MagicNumber); + + if(EnableRSIScalpingBTCUSD) + ProcessRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, + RS_BTCUSD_RSI_Overbought, RS_BTCUSD_RSI_Oversold, RS_BTCUSD_RSI_Target_Buy, RS_BTCUSD_RSI_Target_Sell, + RS_BTCUSD_BarsToWait, DynamicLotForSymbol(RS_BTCUSD_Symbol, RS_BTCUSD_LotSize), RS_BTCUSD_MagicNumber); + + if(EnableRSIScalpingNVDA) + ProcessRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, + RS_NVDA_RSI_Overbought, RS_NVDA_RSI_Oversold, RS_NVDA_RSI_Target_Buy, RS_NVDA_RSI_Target_Sell, + RS_NVDA_BarsToWait, + DynamicLotForSymbol(RS_NVDA_Symbol, RS_NVDA_LotSize, InpDynamicStockLotCap), + RS_NVDA_MagicNumber); + + if(EnableRSIScalpingTSLA) + ProcessRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, + RS_TSLA_RSI_Overbought, RS_TSLA_RSI_Oversold, RS_TSLA_RSI_Target_Buy, RS_TSLA_RSI_Target_Sell, + RS_TSLA_BarsToWait, + DynamicLotForSymbol(RS_TSLA_Symbol, RS_TSLA_LotSize, InpDynamicStockLotCap), + RS_TSLA_MagicNumber); + + if(EnableRSIScalpingXAUUSD) + ProcessRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, + RS_XAUUSD_RSI_Overbought, RS_XAUUSD_RSI_Oversold, RS_XAUUSD_RSI_Target_Buy, RS_XAUUSD_RSI_Target_Sell, + RS_XAUUSD_BarsToWait, DynamicLotForSymbol(RS_XAUUSD_Symbol, RS_XAUUSD_LotSize), RS_XAUUSD_MagicNumber); + + if(EnableRSIReversalAsianEURUSD) + ProcessRSIReversalAsian(rraEURUSDData, DynamicLotForSymbol(RRA_EURUSD_Symbol, RRA_EURUSD_MaxLotSize)); + + if(EnableRSIReversalAsianAUDUSD) + ProcessRSIReversalAsian(rraAUDUSDData, DynamicLotForSymbol(RRA_AUDUSD_Symbol, RRA_AUDUSD_MaxLotSize)); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/MQL5/_united_dynamic_cent/report.png b/frontline/MQL5/_united_dynamic_cent/report.png new file mode 100644 index 0000000..a174d73 Binary files /dev/null and b/frontline/MQL5/_united_dynamic_cent/report.png differ diff --git a/frontline/MQL5/_united_dynamic_cent/self-evaluate.mq5 b/frontline/MQL5/_united_dynamic_cent/self-evaluate.mq5 new file mode 100644 index 0000000..99e078a --- /dev/null +++ b/frontline/MQL5/_united_dynamic_cent/self-evaluate.mq5 @@ -0,0 +1,641 @@ +//+------------------------------------------------------------------+ +//| UnitedEA.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 strict + +#include +#include +#include +#include +#include "MagicNumberHelpers.mqh" +#include "PerformanceEvaluator.mqh" + +//+------------------------------------------------------------------+ +//| Strategy Enable/Disable Switches | +//+------------------------------------------------------------------+ +input group "=== Strategy Enable/Disable ===" +input bool EnableDarvasBox = true; +input bool EnableEMASlopeDistance = true; +input bool EnableRSICrossOverReversal = true; +input bool EnableRSIMidPointHijack = true; +input bool EnableRSIScalpingAPPL = true; +input bool EnableRSIScalpingBTCUSD = true; +input bool EnableRSIScalpingNVDA = true; +input bool EnableRSIScalpingTSLA = true; +input bool EnableRSIScalpingXAUUSD = true; + +//+------------------------------------------------------------------+ +//| Strategy 1: DarvasBoxXAUUSD | +//+------------------------------------------------------------------+ +input group "=== DarvasBox Strategy ===" +input string DB_Symbol = "XAUUSD"; +input int DB_BoxPeriod = 165; +input double DB_BoxDeviation = 30000; // Increased to allow larger ranges (was 25140) +input int DB_VolumeThreshold = 0; // Set to 0 to disable volume threshold check. Volume data from indicator used instead. +input double DB_StopLoss = 1665; +input double DB_TakeProfit = 3685; +input bool DB_EnableLogging = false; +input color DB_BoxColor = clrBlue; +input int DB_BoxWidth = 1; +input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2; +input int DB_MA_Period = 125; +input ENUM_MA_METHOD DB_MA_Method = MODE_EMA; +input ENUM_APPLIED_PRICE DB_MA_Price = PRICE_WEIGHTED; +input double DB_TrendThreshold = 4.94; +input int DB_VolumeMA_Period = 110; +input double DB_VolumeThresholdMultiplier = 1.5; +input int DB_MagicNumber = 135790; + +//+------------------------------------------------------------------+ +//| Strategy 2: EMASlopeDistanceCocktailXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== EMA Slope Distance Strategy ===" +input string ES_Symbol = "XAUUSD"; +input int ES_EMA_Periode = 46; +input double ES_PreisSchwelle = 600.0; +input double ES_SteigungSchwelle = 80.0; +input int ES_ÜberwachungTimeout = 800; +input double ES_TrailingStop = 250.0; +input double ES_LotGröße = 0.03; +input int ES_MagicNumber = 12350; +input bool ES_UseSpreadAdjustment = true; +input ENUM_TIMEFRAMES ES_Timeframe = PERIOD_H1; +input bool ES_UseBarData = true; +input int ES_MaxTradesPerCrossover = 9; +input int ES_ProfitCheckBars = 18; +input bool ES_CloseUnprofitableTrades = true; + +//+------------------------------------------------------------------+ +//| Strategy 3: RSICrossOverReversalXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== RSI CrossOver Reversal Strategy ===" +input string RC_Symbol = "XAUUSD"; +input int RC_MagicNumber = 7; +input int RC_rsiPeriod = 19; +input int RC_overboughtLevel = 93; +input int RC_oversoldLevel = 22; +input double RC_entryRSIBuySpread = 0; +input double RC_entryRSISellSpread = 0; +input double RC_lotSize = 0.01; +input int RC_slippage = 3; +input int RC_cooldownSeconds = 209; +input ENUM_TIMEFRAMES RC_TimeFrame1 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_TimeFrame2 = PERIOD_M1; +input ENUM_TIMEFRAMES RC_BarTimeFrame = PERIOD_M12; +input int RC_emaPeriod = 140; +input double RC_emaSlopeThreshold = 105; +input double RC_exitBuyRSI = 86; +input double RC_exitSellRSI = 10; +input double RC_TrailingStop = 295; +input double RC_emaDistanceThreshold = 165; +input int RC_tradingHourOneBegin = 24; +input int RC_tradingHourOneEnd = 22; +input int RC_tradingHourTwoBegin = 6; +input int RC_tradingHourTwoEnd = 19; +input bool RC_Sunday = false; +input bool RC_Monday = false; +input bool RC_Tuesday = true; +input bool RC_Wednesday = true; +input bool RC_Thursday = true; +input bool RC_Friday = false; +input bool RC_Saturday = false; + +//+------------------------------------------------------------------+ +//| Strategy 4: RSIMidPointHijackXAUUSD | +//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" | +//+------------------------------------------------------------------+ +input group "=== RSI MidPoint Hijack Strategy ===" +input string RM_Symbol = "XAUUSD"; +input ENUM_TIMEFRAMES RM_InpTimeframe = PERIOD_H1; +input double RM_InpLotSize = 0.02; +input int RM_InpMagicNumberRSIFollow = 1001; +input int RM_InpMagicNumberRSIReverse = 1002; +input int RM_InpMagicNumberEMACross = 1003; +input bool RM_InpEnableRSIFollow = true; +input bool RM_InpEnableRSIReverse = true; +input bool RM_InpEnableEMACross = true; +input bool RM_InpEnableStrategyLock = false; +input double RM_InpLockProfitThreshold = 0.0; +input bool RM_InpCloseOppositeTrades = false; +input int RM_InpRSIPeriod = 32; +input int RM_InpRSIOverbought = 78; +input int RM_InpRSIOversold = 46; +input int RM_InpRSIExitLevel = 44; +input int RM_InpRSIFollowStartHour = 23; +input int RM_InpRSIFollowEndHour = 8; +input bool RM_InpRSIFollowCloseOutsideHours = false; +input int RM_InpRSIReversePeriod = 59; +input int RM_InpRSIReverseOverbought = 51; +input int RM_InpRSIReverseOversold = 49; +input int RM_InpRSIReverseCrossLevel = 53; +input int RM_InpRSIReverseExitLevel = 48; +input int RM_InpRSIReverseStartHour = 7; +input int RM_InpRSIReverseEndHour = 13; +input bool RM_InpRSIReverseCloseOutsideHours = false; +input int RM_InpRSIReverseCooldownBars = 15; +input bool RM_InpRSIReverseCooldownOnLoss = true; +input int RM_InpEMAPeriod = 120; +input int RM_InpEMACrossStartHour = 8; +input int RM_InpEMACrossEndHour = 14; +input bool RM_InpEMACrossCloseOutsideHours = true; +input bool RM_InpUseEMADistanceEntry = true; +input double RM_InpEMADistancePips = 160.0; +input int RM_InpEMADistancePeriod = 26; + +//+------------------------------------------------------------------+ +//| Strategy 5-10: RSI Scalping Strategies | +//| Each RSI Scalping strategy trades on its own symbol: | +//| - APPL: Apple stock (AAPL) | +//| - BTCUSD: Bitcoin/USD | +//| - NVDA: NVIDIA stock | +//| - TSLA: Tesla stock | +//| - XAUUSD: Gold/USD | +//| | +//| PEPPERSTONE US SYMBOL FORMATS: | +//| - Stocks may use: "AAPL.US", "NASDAQ:AAPL", or just "AAPL" | +//| - To find correct symbols: | +//| 1. Open Market Watch (Ctrl+M) | +//| 2. Right-click > Show All | +//| 3. Search for the stock name | +//| 4. Use the exact symbol name shown | +//+------------------------------------------------------------------+ +input group "=== RSI Scalping APPL (AAPL) - Pepperstone US ===" +input string RS_APPL_Symbol = "AAPL.US"; // Try: "AAPL.US", "NASDAQ:AAPL", or "AAPL" +input ENUM_TIMEFRAMES RS_APPL_TimeFrame = PERIOD_M10; +input int RS_APPL_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_APPL_RSI_Applied_Price = PRICE_CLOSE; +input double RS_APPL_RSI_Overbought = 80; +input double RS_APPL_RSI_Oversold = 78; +input double RS_APPL_RSI_Target_Buy = 94; +input double RS_APPL_RSI_Target_Sell = 44; +input int RS_APPL_BarsToWait = 7; +input double RS_APPL_LotSize = 25; +input int RS_APPL_MagicNumber = 20001; +input int RS_APPL_Slippage = 3; + +input group "=== RSI Scalping BTCUSD ===" +input string RS_BTCUSD_Symbol = "BTCUSD"; // Pepperstone may use: "BTCUSD", "BTC/USD", or "BTCUSD.c" +input ENUM_TIMEFRAMES RS_BTCUSD_TimeFrame = PERIOD_H1; +input int RS_BTCUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_BTCUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_BTCUSD_RSI_Overbought = 90; +input double RS_BTCUSD_RSI_Oversold = 73; +input double RS_BTCUSD_RSI_Target_Buy = 88; +input double RS_BTCUSD_RSI_Target_Sell = 48; +input int RS_BTCUSD_BarsToWait = 6; +input double RS_BTCUSD_LotSize = 0.1; +input int RS_BTCUSD_MagicNumber = 123459123; +input int RS_BTCUSD_Slippage = 3; + +input group "=== RSI Scalping NVDA - Pepperstone US ===" +input string RS_NVDA_Symbol = "NVDA.US"; // Try: "NVDA.US", "NASDAQ:NVDA", or "NVDA" +input ENUM_TIMEFRAMES RS_NVDA_TimeFrame = PERIOD_M15; +input int RS_NVDA_RSI_Period = 8; +input ENUM_APPLIED_PRICE RS_NVDA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_NVDA_RSI_Overbought = 36; +input double RS_NVDA_RSI_Oversold = 38; +input double RS_NVDA_RSI_Target_Buy = 90; +input double RS_NVDA_RSI_Target_Sell = 70; +input int RS_NVDA_BarsToWait = 5; +input double RS_NVDA_LotSize = 50; +input int RS_NVDA_MagicNumber = 20003; +input int RS_NVDA_Slippage = 3; + +input group "=== RSI Scalping TSLA - Pepperstone US ===" +input string RS_TSLA_Symbol = "TSLA.US"; // Try: "TSLA.US", "NASDAQ:TSLA", or "TSLA" +input ENUM_TIMEFRAMES RS_TSLA_TimeFrame = PERIOD_H1; +input int RS_TSLA_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_TSLA_RSI_Applied_Price = PRICE_CLOSE; +input double RS_TSLA_RSI_Overbought = 54; +input double RS_TSLA_RSI_Oversold = 73; +input double RS_TSLA_RSI_Target_Buy = 87; +input double RS_TSLA_RSI_Target_Sell = 33; +input int RS_TSLA_BarsToWait = 1; +input double RS_TSLA_LotSize = 50; +input int RS_TSLA_MagicNumber = 125421321; +input int RS_TSLA_Slippage = 3; + +input group "=== RSI Scalping XAUUSD ===" +input string RS_XAUUSD_Symbol = "XAUUSD"; +input ENUM_TIMEFRAMES RS_XAUUSD_TimeFrame = PERIOD_H1; +input int RS_XAUUSD_RSI_Period = 14; +input ENUM_APPLIED_PRICE RS_XAUUSD_RSI_Applied_Price = PRICE_CLOSE; +input double RS_XAUUSD_RSI_Overbought = 71; +input double RS_XAUUSD_RSI_Oversold = 57; +input double RS_XAUUSD_RSI_Target_Buy = 80; +input double RS_XAUUSD_RSI_Target_Sell = 57; +input int RS_XAUUSD_BarsToWait = 4; +input double RS_XAUUSD_LotSize = 0.1; +input int RS_XAUUSD_MagicNumber = 129102315; +input int RS_XAUUSD_Slippage = 3; + +//+------------------------------------------------------------------+ +//| Global Variables - DarvasBox | +//+------------------------------------------------------------------+ +struct DarvasBoxData { + string symbol; + bool isInitialized; + double boxHigh; + double boxLow; + bool boxFormed; + datetime lastBoxTime; + string boxName; + double minStopLevel; + double point; + CTrade trade; + int maHandle; + int volumeHandle; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - EMA Slope Distance | +//+------------------------------------------------------------------+ +struct EMASlopeData { + string symbol; + bool isInitialized; + int ema_handle; + double ema_array[]; + datetime letzte_überwachung_zeit; + bool überwachung_aktiv; + bool preis_trigger_aktiv; + bool steigung_trigger_aktiv; + int ticket; + CTrade trade; + int trades_in_current_crossover; + bool crossover_detected; + datetime trade_open_time; + datetime last_bar_time; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI CrossOver Reversal | +//+------------------------------------------------------------------+ +struct RSICrossOverData { + string symbol; + bool isInitialized; + int rsiHandle; + int emaHandle; + double previousRSIDef; + CTrade trade; + datetime lastTradeTime; + datetime bartime; + bool WeekDays[7]; + datetime lastBarTime; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI MidPoint Hijack | +//+------------------------------------------------------------------+ +struct RSIMidPointData { + string symbol; + bool isInitialized; + int rsiHandle; + int rsiReverseHandle; + int emaHandle; + bool rsiOverbought; + bool rsiOversold; + bool rsiReverseOverbought; + bool rsiReverseOversold; + CTrade trade; + CPositionInfo positionInfo; + bool emaCrossBuySignal; + bool emaCrossSellSignal; + int emaCrossSignalBar; + datetime lastBarTime; + datetime rsiReverseLastCloseTime; + bool rsiReverseInCooldown; + double lastBarRSI; + double lastBarRSIReverse; + double lastBarEMA; + double lastBarClose; + double lastBarEMAPrev; + double lastBarClosePrev; +}; + +//+------------------------------------------------------------------+ +//| Global Variables - RSI Scalping | +//+------------------------------------------------------------------+ +struct RSIScalpingData { + string symbol; + bool isInitialized; + CTrade trade; + int rsi_handle; + double rsi_buffer[]; + double rsi_prev; + double rsi_current; + double rsi_two_bars_ago; + bool position_open; + ulong position_ticket; + ENUM_POSITION_TYPE current_position_type; + datetime last_bar_time; + bool rsi_against_position; + int bars_against_count; +}; + +//+------------------------------------------------------------------+ +//| Global Strategy Instances | +//+------------------------------------------------------------------+ +DarvasBoxData dbData; +EMASlopeData esData; +RSICrossOverData rcData; +RSIMidPointData rmData; +RSIScalpingData rsAPPLData; +RSIScalpingData rsBTCUSDData; +RSIScalpingData rsNVDAData; +RSIScalpingData rsTSLAData; +RSIScalpingData rsXAUUSDData; + +//+------------------------------------------------------------------+ +//| Global Variables for Dynamic Lot Sizes | +//+------------------------------------------------------------------+ +// All strategies start with minimum lot size for safety (will be adjusted by performance evaluator) +double g_DB_LotSize = 0.01; // DarvasBox uses fixed lot size +double g_ES_LotSize = 0.01; // EMA Slope Distance - start with minimum +double g_RC_LotSize = 0.01; // RSI CrossOver Reversal - start with minimum +double g_RM_LotSize = 0.01; // RSI MidPoint Hijack - start with minimum +double g_RS_APPL_LotSize = 5.0; // Stock - start with stock minimum (5.0) +double g_RS_BTCUSD_LotSize = 0.01; // Crypto - start with forex minimum (0.01) +double g_RS_NVDA_LotSize = 5.0; // Stock - start with stock minimum (5.0) +double g_RS_TSLA_LotSize = 5.0; // Stock - start with stock minimum (5.0) +double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01) + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + int initResult = INIT_SUCCEEDED; + + // Initialize Performance Evaluator + InitPerformanceTracking(); + + // Initialize strategies - log warnings but don't fail entire EA if symbol unavailable + if(EnableDarvasBox) + { + if(!InitDarvasBox(DB_Symbol)) + Print("Warning: DarvasBox strategy failed to initialize for symbol '", DB_Symbol, "'"); + else + RegisterStrategy("DarvasBox", DB_MagicNumber, 0.01, DB_Symbol); // Fixed lot size + } + + if(EnableEMASlopeDistance) + { + if(!InitEMASlopeDistance(ES_Symbol)) + Print("Warning: EMASlopeDistance strategy failed to initialize for symbol '", ES_Symbol, "'"); + else + { + RegisterStrategy("EMASlopeDistance", ES_MagicNumber, ES_LotGröße, ES_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(ES_Symbol); + g_ES_LotSize = minLot; + } + } + + if(EnableRSICrossOverReversal) + { + if(!InitRSICrossOverReversal(RC_Symbol)) + Print("Warning: RSICrossOverReversal strategy failed to initialize for symbol '", RC_Symbol, "'"); + else + { + RegisterStrategy("RSICrossOverReversal", RC_MagicNumber, RC_lotSize, RC_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RC_Symbol); + g_RC_LotSize = minLot; + } + } + + if(EnableRSIMidPointHijack) + { + if(!InitRSIMidPointHijack(RM_Symbol)) + Print("Warning: RSIMidPointHijack strategy failed to initialize for symbol '", RM_Symbol, "'"); + else + { + RegisterStrategy("RSIMidPointHijack", RM_InpMagicNumberRSIFollow, RM_InpLotSize, RM_Symbol); + RegisterStrategy("RSIMidPointHijack_Reverse", RM_InpMagicNumberRSIReverse, RM_InpLotSize, RM_Symbol); + RegisterStrategy("RSIMidPointHijack_EMACross", RM_InpMagicNumberEMACross, RM_InpLotSize, RM_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RM_Symbol); + g_RM_LotSize = minLot; + } + } + + // Initialize RSI Scalping strategies - don't fail entire EA if symbol unavailable + if(EnableRSIScalpingAPPL) + { + InitRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, RS_APPL_MagicNumber, RS_APPL_Slippage); + RegisterStrategy("RSIScalpingAPPL", RS_APPL_MagicNumber, RS_APPL_LotSize, RS_APPL_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_APPL_Symbol); + g_RS_APPL_LotSize = minLot; + } + + if(EnableRSIScalpingBTCUSD) + { + InitRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, RS_BTCUSD_MagicNumber, RS_BTCUSD_Slippage); + RegisterStrategy("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber, RS_BTCUSD_LotSize, RS_BTCUSD_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_BTCUSD_Symbol); + g_RS_BTCUSD_LotSize = minLot; + } + + if(EnableRSIScalpingNVDA) + { + InitRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, RS_NVDA_MagicNumber, RS_NVDA_Slippage); + RegisterStrategy("RSIScalpingNVDA", RS_NVDA_MagicNumber, RS_NVDA_LotSize, RS_NVDA_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_NVDA_Symbol); + g_RS_NVDA_LotSize = minLot; + } + + if(EnableRSIScalpingTSLA) + { + InitRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, RS_TSLA_MagicNumber, RS_TSLA_Slippage); + RegisterStrategy("RSIScalpingTSLA", RS_TSLA_MagicNumber, RS_TSLA_LotSize, RS_TSLA_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_TSLA_Symbol); + g_RS_TSLA_LotSize = minLot; + } + + if(EnableRSIScalpingXAUUSD) + { + InitRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, RS_XAUUSD_MagicNumber, RS_XAUUSD_Slippage); + RegisterStrategy("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber, RS_XAUUSD_LotSize, RS_XAUUSD_Symbol); + // Start with minimum lot size (will be adjusted by performance evaluator) + double minLot = GetMinLotSizeForSymbol(RS_XAUUSD_Symbol); + g_RS_XAUUSD_LotSize = minLot; + } + + // Load adjusted lot sizes from performance evaluator + if(PE_EnableAutoAdjustment) + { + double adjustedLot; + adjustedLot = GetStrategyLotSize("EMASlopeDistance", ES_MagicNumber); + if(adjustedLot > 0) g_ES_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSICrossOverReversal", RC_MagicNumber); + if(adjustedLot > 0) g_RC_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIMidPointHijack", RM_InpMagicNumberRSIFollow); + if(adjustedLot > 0) g_RM_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingAPPL", RS_APPL_MagicNumber); + if(adjustedLot > 0) g_RS_APPL_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber); + if(adjustedLot > 0) g_RS_BTCUSD_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingNVDA", RS_NVDA_MagicNumber); + if(adjustedLot > 0) g_RS_NVDA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingTSLA", RS_TSLA_MagicNumber); + if(adjustedLot > 0) g_RS_TSLA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber); + if(adjustedLot > 0) g_RS_XAUUSD_LotSize = adjustedLot; + } + + Print("United EA initialized. Active strategies: ", + (EnableDarvasBox ? "DarvasBox " : ""), + (EnableEMASlopeDistance ? "EMASlope " : ""), + (EnableRSICrossOverReversal ? "RSICrossOver " : ""), + (EnableRSIMidPointHijack ? "RSIMidPoint " : ""), + (EnableRSIScalpingAPPL ? "RSIScalpingAPPL " : ""), + (EnableRSIScalpingBTCUSD ? "RSIScalpingBTCUSD " : ""), + (EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""), + (EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""), + (EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : "")); + + if(PE_EnableLogging) + Print(GetPerformanceSummary()); + + return initResult; +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(EnableDarvasBox) + DeinitDarvasBox(); + + if(EnableEMASlopeDistance) + DeinitEMASlopeDistance(); + + if(EnableRSICrossOverReversal) + DeinitRSICrossOverReversal(); + + if(EnableRSIMidPointHijack) + DeinitRSIMidPointHijack(); + + if(EnableRSIScalpingAPPL) + DeinitRSIScalping(rsAPPLData); + + if(EnableRSIScalpingBTCUSD) + DeinitRSIScalping(rsBTCUSDData); + + if(EnableRSIScalpingNVDA) + DeinitRSIScalping(rsNVDAData); + + if(EnableRSIScalpingTSLA) + DeinitRSIScalping(rsTSLAData); + + if(EnableRSIScalpingXAUUSD) + DeinitRSIScalping(rsXAUUSDData); + + Print("United EA deinitialized. Reason: ", reason); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + // Process performance evaluation (checks for quarter end and adjusts lot sizes) + ProcessPerformanceEvaluation(); + + // Update lot sizes from performance evaluator if auto-adjustment is enabled + if(PE_EnableAutoAdjustment) + { + double adjustedLot; + adjustedLot = GetStrategyLotSize("EMASlopeDistance", ES_MagicNumber); + if(adjustedLot > 0) g_ES_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSICrossOverReversal", RC_MagicNumber); + if(adjustedLot > 0) g_RC_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIMidPointHijack", RM_InpMagicNumberRSIFollow); + if(adjustedLot > 0) g_RM_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingAPPL", RS_APPL_MagicNumber); + if(adjustedLot > 0) g_RS_APPL_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingBTCUSD", RS_BTCUSD_MagicNumber); + if(adjustedLot > 0) g_RS_BTCUSD_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingNVDA", RS_NVDA_MagicNumber); + if(adjustedLot > 0) g_RS_NVDA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingTSLA", RS_TSLA_MagicNumber); + if(adjustedLot > 0) g_RS_TSLA_LotSize = adjustedLot; + + adjustedLot = GetStrategyLotSize("RSIScalpingXAUUSD", RS_XAUUSD_MagicNumber); + if(adjustedLot > 0) g_RS_XAUUSD_LotSize = adjustedLot; + } + + if(EnableDarvasBox) + ProcessDarvasBox(DB_Symbol); + + if(EnableEMASlopeDistance) + ProcessEMASlopeDistance(ES_Symbol); + + if(EnableRSICrossOverReversal) + ProcessRSICrossOverReversal(RC_Symbol); + + if(EnableRSIMidPointHijack) + ProcessRSIMidPointHijack(RM_Symbol); + + if(EnableRSIScalpingAPPL) + ProcessRSIScalping(rsAPPLData, RS_APPL_Symbol, RS_APPL_TimeFrame, RS_APPL_RSI_Period, RS_APPL_RSI_Applied_Price, + RS_APPL_RSI_Overbought, RS_APPL_RSI_Oversold, RS_APPL_RSI_Target_Buy, RS_APPL_RSI_Target_Sell, + RS_APPL_BarsToWait, g_RS_APPL_LotSize, RS_APPL_MagicNumber); + + if(EnableRSIScalpingBTCUSD) + ProcessRSIScalping(rsBTCUSDData, RS_BTCUSD_Symbol, RS_BTCUSD_TimeFrame, RS_BTCUSD_RSI_Period, RS_BTCUSD_RSI_Applied_Price, + RS_BTCUSD_RSI_Overbought, RS_BTCUSD_RSI_Oversold, RS_BTCUSD_RSI_Target_Buy, RS_BTCUSD_RSI_Target_Sell, + RS_BTCUSD_BarsToWait, g_RS_BTCUSD_LotSize, RS_BTCUSD_MagicNumber); + + if(EnableRSIScalpingNVDA) + ProcessRSIScalping(rsNVDAData, RS_NVDA_Symbol, RS_NVDA_TimeFrame, RS_NVDA_RSI_Period, RS_NVDA_RSI_Applied_Price, + RS_NVDA_RSI_Overbought, RS_NVDA_RSI_Oversold, RS_NVDA_RSI_Target_Buy, RS_NVDA_RSI_Target_Sell, + RS_NVDA_BarsToWait, g_RS_NVDA_LotSize, RS_NVDA_MagicNumber); + + if(EnableRSIScalpingTSLA) + ProcessRSIScalping(rsTSLAData, RS_TSLA_Symbol, RS_TSLA_TimeFrame, RS_TSLA_RSI_Period, RS_TSLA_RSI_Applied_Price, + RS_TSLA_RSI_Overbought, RS_TSLA_RSI_Oversold, RS_TSLA_RSI_Target_Buy, RS_TSLA_RSI_Target_Sell, + RS_TSLA_BarsToWait, g_RS_TSLA_LotSize, RS_TSLA_MagicNumber); + + if(EnableRSIScalpingXAUUSD) + ProcessRSIScalping(rsXAUUSDData, RS_XAUUSD_Symbol, RS_XAUUSD_TimeFrame, RS_XAUUSD_RSI_Period, RS_XAUUSD_RSI_Applied_Price, + RS_XAUUSD_RSI_Overbought, RS_XAUUSD_RSI_Oversold, RS_XAUUSD_RSI_Target_Buy, RS_XAUUSD_RSI_Target_Sell, + RS_XAUUSD_BarsToWait, g_RS_XAUUSD_LotSize, RS_XAUUSD_MagicNumber); +} + +//+------------------------------------------------------------------+ +//| Include strategy implementations | +//+------------------------------------------------------------------+ +#include "Strategies/DarvasBoxStrategy.mqh" +#include "Strategies/EMASlopeDistanceStrategy.mqh" +#include "Strategies/RSICrossOverReversalStrategy.mqh" +#include "Strategies/RSIMidPointHijackStrategy.mqh" +#include "Strategies/RSIScalpingStrategy.mqh" + +//+------------------------------------------------------------------+ diff --git a/lab/EAs/RSIMidPointHijackBTCUSD/RSIMidPointHijackBTCUSD_optimize.set b/lab/EAs/RSIMidPointHijackBTCUSD/RSIMidPointHijackBTCUSD_optimize.set new file mode 100644 index 0000000..9cdfe0e --- /dev/null +++ b/lab/EAs/RSIMidPointHijackBTCUSD/RSIMidPointHijackBTCUSD_optimize.set @@ -0,0 +1,45 @@ +; 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 diff --git a/lab/EAs/RSIMidPointHijackBTCUSD/main.mq5 b/lab/EAs/RSIMidPointHijackBTCUSD/main.mq5 new file mode 100644 index 0000000..161c0bd --- /dev/null +++ b/lab/EAs/RSIMidPointHijackBTCUSD/main.mq5 @@ -0,0 +1,604 @@ +//+------------------------------------------------------------------+ +//| 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 +#include +#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); +} diff --git a/lab/EAs/RSIMidPointHijackBTCUSD/report.html b/lab/EAs/RSIMidPointHijackBTCUSD/report.html new file mode 100644 index 0000000..a731713 Binary files /dev/null and b/lab/EAs/RSIMidPointHijackBTCUSD/report.html differ diff --git a/lab/EAs/RSIMidPointHijackBTCUSD/report.png b/lab/EAs/RSIMidPointHijackBTCUSD/report.png new file mode 100644 index 0000000..71b2983 Binary files /dev/null and b/lab/EAs/RSIMidPointHijackBTCUSD/report.png differ