UpDATE
@@ -0,0 +1,386 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| EURUSD_H1_ActionEA.mq5 |
|
||||
//| ai/eurusd1h/main.py — 24 features, 5-class softmax |
|
||||
//| Classes: 0=HOLD 1=BUY 2=SELL_SHORT 3=CLOSE_LONG 4=CLOSE_SHORT |
|
||||
//| Entry: strict trio winner among p0,p1,p2 only. |
|
||||
//| Exit: unique 5-class argmax != held side (1 long, 2 short). |
|
||||
//| No SL / TP / ATR stops. Attach EURUSD H1. |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Profitable EA Project"
|
||||
#property version "1.00"
|
||||
#property description "EURUSD H1 action ONNX; ordinal entry/exit; no fixed SL/TP"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
#resource "models\\EURUSD_H1_action.onnx" as uchar ExtModel[]
|
||||
|
||||
#define FEAT_COUNT 24
|
||||
#define REL_EPS 1e-9
|
||||
|
||||
input group "Model"
|
||||
input int InpLookback = 48;
|
||||
input int InpSessionHourOffset = 0;
|
||||
input string InpFeatMinStr = "";
|
||||
input string InpFeatMaxStr = "";
|
||||
|
||||
input group "Timing"
|
||||
input int InpMinBarsInTrade = 1; // model exit only after this many bars in position (0=off)
|
||||
|
||||
input group "Trade"
|
||||
input double InpLotSize = 0.01;
|
||||
input int InpMagic = 902601;
|
||||
input int InpSlippage = 30;
|
||||
|
||||
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 InitDefaultScalerFromMeta()
|
||||
{
|
||||
// EURUSD_H1_action_meta.json scaler_feature_min / max (train fit)
|
||||
double def_min[FEAT_COUNT] = {
|
||||
0.9539399743080139,
|
||||
0.9559400081634521,
|
||||
0.9536200165748596,
|
||||
0.9538999795913696,
|
||||
9.999999974752427e-07,
|
||||
0.07019035518169403,
|
||||
-0.02143237181007862,
|
||||
-0.028110405430197716,
|
||||
0.0002704667276702821,
|
||||
-0.02017582766711712,
|
||||
1.0,
|
||||
0.0004555500054266304,
|
||||
0.0002461568801663816,
|
||||
0.022001149132847786,
|
||||
0.11231997609138489,
|
||||
-0.5339273810386658,
|
||||
-1.623793125152588,
|
||||
-1.837566614151001,
|
||||
6.83732741890708e-06,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
};
|
||||
double def_max[FEAT_COUNT] = {
|
||||
1.493149995803833,
|
||||
1.4938499927520752,
|
||||
1.4904999732971191,
|
||||
1.493190050125122,
|
||||
0.06699500232934952,
|
||||
0.9350273013114929,
|
||||
0.028008731082081795,
|
||||
0.03147505968809128,
|
||||
0.009249407798051834,
|
||||
0.01742853783071041,
|
||||
1.0232577323913574,
|
||||
0.02111775055527687,
|
||||
7.801275253295898,
|
||||
0.9864169955253601,
|
||||
0.8837512731552124,
|
||||
0.49708572030067444,
|
||||
1.8055412769317627,
|
||||
1.927569031715393,
|
||||
0.8700546026229858,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
};
|
||||
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[];
|
||||
if(StringSplit(s, ',', parts) != FEAT_COUNT) return false;
|
||||
for(int i = 0; i < FEAT_COUNT; i++)
|
||||
arr[i] = StringToDouble(parts[i]);
|
||||
return true;
|
||||
}
|
||||
|
||||
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 spr = (r0 - rv7) / 50.0;
|
||||
if(spr > 1.0) spr = 1.0;
|
||||
if(spr < -1.0) spr = -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)spr;
|
||||
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;
|
||||
}
|
||||
|
||||
int TrioStrictWinner012(const double p0, const double p1, const double p2)
|
||||
{
|
||||
if(p0 > p1 + REL_EPS && p0 > p2 + REL_EPS) return 0;
|
||||
if(p1 > p0 + REL_EPS && p1 > p2 + REL_EPS) return 1;
|
||||
if(p2 > p0 + REL_EPS && p2 > p1 + REL_EPS) return 2;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FiveStrictWinner01234(const double p0, const double p1, const double p2, const double p3, const double p4)
|
||||
{
|
||||
const double p[5] = {p0, p1, p2, p3, p4};
|
||||
int best = 0;
|
||||
for(int k = 1; k < 5; k++)
|
||||
if(p[k] > p[best])
|
||||
best = k;
|
||||
const double m = p[best];
|
||||
int cnt = 0;
|
||||
for(int k = 0; k < 5; k++)
|
||||
if(p[k] + REL_EPS >= m)
|
||||
cnt++;
|
||||
if(cnt != 1)
|
||||
return -1;
|
||||
return best;
|
||||
}
|
||||
|
||||
bool SelectOurPosition()
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return false;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int PositionBarsInTrade()
|
||||
{
|
||||
if(!SelectOurPosition())
|
||||
return 0;
|
||||
const datetime tOpen = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
const int sh = iBarShift(_Symbol, PERIOD_CURRENT, tOpen, false);
|
||||
if(sh < 0)
|
||||
return 9999;
|
||||
return sh + 1;
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
InitDefaultScalerFromMeta();
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
trade.SetDeviationInPoints(InpSlippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
|
||||
if(StringLen(InpFeatMinStr) > 0 && ParseFeatCsv(InpFeatMinStr, g_feat_min))
|
||||
Print("EURUSD Action EA: loaded InpFeatMinStr");
|
||||
if(StringLen(InpFeatMaxStr) > 0 && ParseFeatCsv(InpFeatMaxStr, g_feat_max))
|
||||
Print("EURUSD Action EA: loaded InpFeatMaxStr");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if(_Period != PERIOD_H1)
|
||||
Print("EURUSD_H1_ActionEA: chart period is ", EnumToString((ENUM_TIMEFRAMES)_Period),
|
||||
" — training is H1; mismatch may hurt.");
|
||||
|
||||
Print("EURUSD_H1_ActionEA: ONNX OK. Ordinal entry/exit, no SL/TP. Lookback=", InpLookback);
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int r)
|
||||
{
|
||||
if(g_onnx != INVALID_HANDLE)
|
||||
OnnxRelease(g_onnx);
|
||||
}
|
||||
|
||||
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("EURUSD Action EA: PrepareMatrix failed");
|
||||
return;
|
||||
}
|
||||
vectorf out;
|
||||
out.Resize(5);
|
||||
if(!OnnxRun(g_onnx, ONNX_NO_CONVERSION, Min, out))
|
||||
{
|
||||
Print("OnnxRun failed ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
const double p0 = out[0], p1 = out[1], p2 = out[2], p3 = out[3], p4 = out[4];
|
||||
|
||||
if(!SelectOurPosition())
|
||||
{
|
||||
const int w3 = TrioStrictWinner012(p0, p1, p2);
|
||||
if(w3 == 1)
|
||||
trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "EURUSD act BUY");
|
||||
else if(w3 == 2)
|
||||
trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "EURUSD act SELL");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool allow = (InpMinBarsInTrade <= 0) || (PositionBarsInTrade() >= InpMinBarsInTrade);
|
||||
if(!allow)
|
||||
return;
|
||||
|
||||
const int w5 = FiveStrictWinner01234(p0, p1, p2, p3, p4);
|
||||
const long typ = (long)PositionGetInteger(POSITION_TYPE);
|
||||
bool close_it = false;
|
||||
if(typ == POSITION_TYPE_BUY)
|
||||
close_it = (w5 != -1 && w5 != 1);
|
||||
else if(typ == POSITION_TYPE_SELL)
|
||||
close_it = (w5 != -1 && w5 != 2);
|
||||
|
||||
if(close_it)
|
||||
trade.PositionClose(_Symbol);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
# EURUSD H1 — ONNX action model (full MT5 history)
|
||||
|
||||
Same methodology as `ai/yt/train_article_split.py`:
|
||||
|
||||
- **24 features** + **5 softmax classes** (`ai/xauusd_h1/features.py`, `labeling.py`).
|
||||
- **MinMaxScaler** is fit on **every** valid feature row MT5 returns (no 2010–2020 cut).
|
||||
- **Training sequences**: all but a **chronological tail** (default **12%**) used only for `val_loss` / EarlyStopping (does not remove data from the scaler).
|
||||
- Optional **KMeans** on forward-return fingerprints + class-balanced `sample_weight` on the train split.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
cd ai/eurusd1h
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
```
|
||||
|
||||
Requires MetaTrader 5 with **EURUSD H1** history downloaded (Tools → History Center).
|
||||
|
||||
## Environment overrides
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|-----------------|-----------|----------------------------------------------|
|
||||
| `EUR_SYMBOL` | `EURUSD` | MT5 symbol |
|
||||
| `EUR_LOOKBACK` | `48` | Sequence length |
|
||||
| `EUR_EPOCHS` | `40` | Max epochs |
|
||||
| `EUR_BATCH` | `64` | Batch size |
|
||||
| `EUR_CLUSTERS` | `12` | KMeans clusters (`0` = off) |
|
||||
| `EUR_VAL_FRAC` | `0.12` | Fraction of sequences at **end** for val |
|
||||
|
||||
## Outputs
|
||||
|
||||
- `models/EURUSD_H1_action.onnx`
|
||||
- `models/EURUSD_H1_action_meta.json`
|
||||
- `models/EURUSD_H1_action_scaler.pkl`
|
||||
|
||||
Deploy like `ai/yt/US500_H1_ArticleEA.mq5`: embed ONNX, set `InpLookback`, paste `scaler_feature_min` / `max` from the meta JSON into the EA inputs.
|
||||
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
EURUSD H1 — same stack as ai/yt (24 features, 5-class softmax, optional KMeans weights).
|
||||
|
||||
MinMaxScaler is fit on **all** feature rows returned by MT5 (full downloaded history).
|
||||
A chronological **tail** slice (default 12%% of sequences) is used only for val_loss /
|
||||
EarlyStopping; all earlier sequences are used for training.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import tensorflow as tf
|
||||
import tf2onnx
|
||||
import onnx
|
||||
from sklearn.cluster import KMeans
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
from tensorflow import keras
|
||||
from tensorflow.keras import layers
|
||||
from tqdm import tqdm
|
||||
|
||||
_XH1 = Path(__file__).resolve().parent.parent / "xauusd_h1"
|
||||
sys.path.insert(0, str(_XH1))
|
||||
from features import NUM_FEATURES, prepare_features_full # noqa: E402
|
||||
from labeling import atr_series, class_weights, compute_action_labels # noqa: E402
|
||||
|
||||
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 EURUSD 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 forward_return_fingerprints(
|
||||
df: pd.DataFrame,
|
||||
feat_index: pd.DatetimeIndex,
|
||||
horizons: tuple[int, ...] = (1, 2, 4, 8, 16),
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
close = df["close"].to_numpy(dtype=np.float64)
|
||||
atr = atr_series(df, 14).to_numpy(dtype=np.float64)
|
||||
pos = df.index.get_indexer(feat_index)
|
||||
n = len(feat_index)
|
||||
d = len(horizons)
|
||||
M = np.zeros((n, d), dtype=np.float64)
|
||||
valid = np.ones(n, dtype=bool)
|
||||
max_h = max(horizons)
|
||||
for j, i in enumerate(pos):
|
||||
if i < 0 or i + max_h >= len(close):
|
||||
valid[j] = False
|
||||
continue
|
||||
a = float(atr[i]) if np.isfinite(atr[i]) and atr[i] > 0 else close[i] * 1e-4
|
||||
for k, h in enumerate(horizons):
|
||||
if i + h >= len(close):
|
||||
valid[j] = False
|
||||
break
|
||||
M[j, k] = (close[i + h] - close[i]) / a
|
||||
return M, valid
|
||||
|
||||
|
||||
def create_sequences(
|
||||
X: np.ndarray,
|
||||
y: np.ndarray,
|
||||
times: np.ndarray,
|
||||
lookback: int,
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
xs, ys, t_end = [], [], []
|
||||
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])
|
||||
t_end.append(times[i])
|
||||
return (
|
||||
np.asarray(xs, dtype=np.float32),
|
||||
np.asarray(ys, dtype=np.int64),
|
||||
np.asarray(t_end),
|
||||
)
|
||||
|
||||
|
||||
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("EUR_SYMBOL", "EURUSD")
|
||||
lookback = int(os.environ.get("EUR_LOOKBACK", "48"))
|
||||
epochs = int(os.environ.get("EUR_EPOCHS", "40"))
|
||||
batch_size = int(os.environ.get("EUR_BATCH", "64"))
|
||||
n_clusters = int(os.environ.get("EUR_CLUSTERS", "12"))
|
||||
val_frac = float(os.environ.get("EUR_VAL_FRAC", "0.12"))
|
||||
val_frac = min(max(val_frac, 0.05), 0.35)
|
||||
|
||||
fetch_start = datetime(1990, 1, 1)
|
||||
fetch_end = datetime(2030, 12, 31)
|
||||
|
||||
out_dir = Path(__file__).resolve().parent / "models"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
onnx_path = out_dir / f"{symbol}_H1_action.onnx"
|
||||
meta_path = out_dir / f"{symbol}_H1_action_meta.json"
|
||||
|
||||
print(f"Symbol={symbol} H1 | fetch [{fetch_start.date()} .. {fetch_end.date()}]")
|
||||
print("Scaler: ALL bars | Val: chronological tail for early stopping only")
|
||||
print("Fetching MT5 …")
|
||||
try:
|
||||
raw = fetch_mt5_range(symbol, mt5.TIMEFRAME_H1, fetch_start, fetch_end)
|
||||
finally:
|
||||
mt5.shutdown()
|
||||
|
||||
if len(raw) < 500:
|
||||
print("ERROR: Not enough H1 bars — check EURUSD history in MT5.")
|
||||
return 1
|
||||
|
||||
print(f"Bars: {len(raw)} range: {raw.index[0]} → {raw.index[-1]}")
|
||||
|
||||
feat = prepare_features_full(raw)
|
||||
labels = compute_action_labels(raw).loc[feat.index]
|
||||
y = labels.values.astype(np.int64)
|
||||
X_raw = feat.values.astype(np.float32)
|
||||
times = feat.index.to_numpy()
|
||||
|
||||
valid = np.isfinite(X_raw).all(axis=1) & (y >= 0) & (y < NUM_CLASSES)
|
||||
X_raw = X_raw[valid]
|
||||
y = y[valid]
|
||||
times = times[valid]
|
||||
|
||||
scaler = MinMaxScaler()
|
||||
scaler.fit(X_raw)
|
||||
Xn = scaler.transform(X_raw).astype(np.float32)
|
||||
|
||||
X_seq, y_seq, t_end = create_sequences(Xn, y, times, lookback)
|
||||
if len(X_seq) < 500:
|
||||
print("ERROR: Too few sequences.")
|
||||
return 1
|
||||
|
||||
n_seq = len(X_seq)
|
||||
split_i = int(n_seq * (1.0 - val_frac))
|
||||
split_i = max(split_i, lookback + 100)
|
||||
split_i = min(split_i, n_seq - 200)
|
||||
train_m = np.zeros(n_seq, dtype=bool)
|
||||
train_m[:split_i] = True
|
||||
val_m = ~train_m
|
||||
|
||||
X_train, y_train = X_seq[train_m], y_seq[train_m]
|
||||
X_val, y_val = X_seq[val_m], y_seq[val_m]
|
||||
print(
|
||||
f"Sequences train={len(X_train)} val_tail={len(X_val)} ({100*val_frac:.1f}%%) "
|
||||
f"lookback={lookback}"
|
||||
)
|
||||
|
||||
cw = class_weights(y_train, NUM_CLASSES)
|
||||
tr_idx = np.flatnonzero(train_m)
|
||||
base_w = np.array([cw[int(y_seq[i])] for i in tr_idx], dtype=np.float32)
|
||||
sample_w = base_w.copy()
|
||||
|
||||
if n_clusters > 1:
|
||||
fp, fp_ok = forward_return_fingerprints(raw, pd.DatetimeIndex(times))
|
||||
fp_seq = fp[lookback - 1 :]
|
||||
ok_seq = fp_ok[lookback - 1 :]
|
||||
fp_tr = fp_seq[tr_idx]
|
||||
ok_tr = ok_seq[tr_idx]
|
||||
fit_mask = ok_tr & np.isfinite(fp_tr).all(axis=1)
|
||||
if int(fit_mask.sum()) >= n_clusters * 5:
|
||||
km = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
|
||||
km.fit(fp_tr[fit_mask])
|
||||
labels_tr = np.full(len(tr_idx), -1, dtype=np.int32)
|
||||
labels_tr[fit_mask] = km.predict(fp_tr[fit_mask])
|
||||
counts = np.zeros(n_clusters, dtype=np.float64)
|
||||
for c in labels_tr:
|
||||
if 0 <= c < n_clusters:
|
||||
counts[c] += 1.0
|
||||
counts = np.maximum(counts, 1.0)
|
||||
total_assigned = max(int((labels_tr >= 0).sum()), 1)
|
||||
w_cl = np.ones(len(tr_idx), dtype=np.float32)
|
||||
for j in range(len(tr_idx)):
|
||||
c = int(labels_tr[j])
|
||||
if c >= 0:
|
||||
w_cl[j] = float(total_assigned / (n_clusters * counts[c]))
|
||||
sample_w = base_w * w_cl
|
||||
sample_w *= len(sample_w) / float(np.sum(sample_w))
|
||||
print(f"KMeans clusters={n_clusters} (train subset only)")
|
||||
else:
|
||||
print("Skipping KMeans: not enough valid fingerprints.")
|
||||
|
||||
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=10, restore_best_weights=True
|
||||
),
|
||||
keras.callbacks.ReduceLROnPlateau(
|
||||
monitor="val_loss", factor=0.5, patience=4, min_lr=1e-6
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
loss, acc = model.evaluate(X_val, y_val, verbose=0)
|
||||
print(f"Tail val_loss={loss:.4f} val_accuracy={acc:.4f}")
|
||||
|
||||
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, str(onnx_path))
|
||||
|
||||
with open(str(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,
|
||||
"mt5_bar_range": [str(raw.index[0]), str(raw.index[-1])],
|
||||
"scaler_fit_on": "all_valid_feature_rows_full_mt5_range",
|
||||
"validation_split": {
|
||||
"mode": "chronological_tail_fraction",
|
||||
"val_fraction": val_frac,
|
||||
"train_sequences": int(train_m.sum()),
|
||||
"val_sequences": int(val_m.sum()),
|
||||
},
|
||||
"clustering": (
|
||||
f"KMeans n={n_clusters} on forward returns (1,2,4,8,16); train-only fit"
|
||||
if n_clusters > 1
|
||||
else "disabled"
|
||||
),
|
||||
"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,
|
||||
"tail_val_accuracy": float(acc),
|
||||
"tail_val_loss": float(loss),
|
||||
"ea_note": "Copy ai/yt/US500_H1_ArticleEA.mq5 pattern: #resource ONNX + paste scaler from meta.",
|
||||
}
|
||||
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 (%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}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"symbol": "EURUSD",
|
||||
"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"
|
||||
],
|
||||
"mt5_bar_range": [
|
||||
"2010-03-17 23:00:00",
|
||||
"2026-04-24 23:00:00"
|
||||
],
|
||||
"scaler_fit_on": "all_valid_feature_rows_full_mt5_range",
|
||||
"validation_split": {
|
||||
"mode": "chronological_tail_fraction",
|
||||
"val_fraction": 0.12,
|
||||
"train_sequences": 87914,
|
||||
"val_sequences": 11989
|
||||
},
|
||||
"clustering": "KMeans n=12 on forward returns (1,2,4,8,16); train-only fit",
|
||||
"scaler_feature_min": [
|
||||
0.9539399743080139,
|
||||
0.9559400081634521,
|
||||
0.9536200165748596,
|
||||
0.9538999795913696,
|
||||
9.999999974752427e-07,
|
||||
0.07019035518169403,
|
||||
-0.02143237181007862,
|
||||
-0.028110405430197716,
|
||||
0.0002704667276702821,
|
||||
-0.02017582766711712,
|
||||
1.0,
|
||||
0.0004555500054266304,
|
||||
0.0002461568801663816,
|
||||
0.022001149132847786,
|
||||
0.11231997609138489,
|
||||
-0.5339273810386658,
|
||||
-1.623793125152588,
|
||||
-1.837566614151001,
|
||||
6.83732741890708e-06,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scaler_feature_max": [
|
||||
1.493149995803833,
|
||||
1.4938499927520752,
|
||||
1.4904999732971191,
|
||||
1.493190050125122,
|
||||
0.06699500232934952,
|
||||
0.9350273013114929,
|
||||
0.028008731082081795,
|
||||
0.03147505968809128,
|
||||
0.009249407798051834,
|
||||
0.01742853783071041,
|
||||
1.0232577323913574,
|
||||
0.02111775055527687,
|
||||
7.801275253295898,
|
||||
0.9864169955253601,
|
||||
0.8837512731552124,
|
||||
0.49708572030067444,
|
||||
1.8055412769317627,
|
||||
1.927569031715393,
|
||||
0.8700546026229858,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scaler_scale": [
|
||||
1.854564905166626,
|
||||
1.8590470552444458,
|
||||
1.8626137971878052,
|
||||
1.8542896509170532,
|
||||
14.926709175109863,
|
||||
1.1562873125076294,
|
||||
20.226085662841797,
|
||||
16.782615661621094,
|
||||
111.3717041015625,
|
||||
26.5926570892334,
|
||||
42.99645233154297,
|
||||
48.39755630493164,
|
||||
0.12818820774555206,
|
||||
1.03689706325531,
|
||||
1.296291708946228,
|
||||
0.969919741153717,
|
||||
0.2916017770767212,
|
||||
0.2655946612358093,
|
||||
1.1493622064590454,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"tail_val_accuracy": 0.2594878673553467,
|
||||
"tail_val_loss": 1.5548540353775024,
|
||||
"ea_note": "Copy ai/yt/US500_H1_ArticleEA.mq5 pattern: #resource ONNX + paste scaler from meta."
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
-r ../xauusd_h1/requirements.txt
|
||||
@@ -0,0 +1,663 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| US500_H1_ArticleEA.mq5 |
|
||||
//| ai/yt: article-split ONNX (train 2010–2019 / OOS 2020–2024) |
|
||||
//| Train: python train_article_split.py → models/*.onnx |
|
||||
//| Attach to US500 (or broker equivalent) H1 chart. |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Profitable EA Project"
|
||||
#property version "1.05"
|
||||
#property description "Embedded US500 H1 article-split ONNX; scaler from US500_H1_article_split_meta.json"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
#resource "models\\US500_H1_article_split.onnx" as uchar ExtModel[]
|
||||
|
||||
#define FEAT_COUNT 24
|
||||
#define PRED_HIST_CAP 32
|
||||
#define REL_EPS 1e-9
|
||||
|
||||
input group "Model"
|
||||
input int InpLookback = 48;
|
||||
input int InpEntryMode = 1;
|
||||
input double InpProbBuy = 0.18;
|
||||
input double InpProbSell = 0.18;
|
||||
input double InpMinBeatHold = 0.0;
|
||||
input int InpExitMode = 1; // 0=fixed prob; 1/2=close must beat HOLD and stay-in-trade (2 legacy; old 2 vs-HOLD-only removed)
|
||||
input double InpProbCloseL = 0.18;
|
||||
input double InpProbCloseS = 0.18;
|
||||
input double InpMinCloseBeatHold = 0.0;
|
||||
input int InpMinBarsInTradeModelExit = 1; // min bars before model exit (0=off); pure mode uses 5-class winner
|
||||
input bool InpPureRelative = true; // true: no prob cutoffs/edges — entry=trio strict winner, exit=5-class strict winner != side
|
||||
input bool InpUseCloseHeadExit = true; // legacy only when InpPureRelative=false (CL/CS vs HOLD/stay; see InpExitMode)
|
||||
input bool InpUseDirFlipExit = true; // legacy only when InpPureRelative=false (gap edges InpFlipExitEdge)
|
||||
input double InpFlipExitEdge = 0.03; // legacy dir-flip min gap (ignored when InpPureRelative)
|
||||
input int InpMinBarsAfterExit = 6; // after any close, wait this many flat bars before a new entry (0=off)
|
||||
input int InpCooldownBarsAfterAdverse = 12; // extra flat-bar pause after adverse (ATR) stop; 0 = use only MinBarsAfterExit
|
||||
|
||||
input group "Decision (aggregate + sample, lowers trade churn)"
|
||||
input int InpSampleEveryNBars = 2; // run ONNX / refresh history every N new bars (>=1)
|
||||
input int InpAggWindow = 4; // rolling mean over last K samples (>=1)
|
||||
input int InpMinAggSamples = 2; // need this many samples in window before new entries
|
||||
input int InpMinBarsBetweenEntries = 0; // after an open, wait this many flat bars before next entry (0=off)
|
||||
input double InpMinDirEdge = 0.03; // legacy entry mode 1 only (ignored when InpPureRelative)
|
||||
input bool InpRequireStayOverClose = true; // legacy entry (ignored when InpPureRelative)
|
||||
|
||||
input group "Session (match Python SESSION_HOUR_OFFSET)"
|
||||
input int InpSessionHourOffset = 0;
|
||||
|
||||
input group "Scaler override (empty = use built-in US500 train split)"
|
||||
input string InpFeatMinStr = "";
|
||||
input string InpFeatMaxStr = "";
|
||||
|
||||
input group "Risk"
|
||||
input double InpLotSize = 0.01;
|
||||
input int InpMagic = 902503;
|
||||
input int InpSlippage = 30;
|
||||
|
||||
input group "Hard exits (fixed ATR in price — optional)"
|
||||
input bool InpUseAdverseAtrExit = false; // stop by adverse move in ATR multiples (off = model-only risk)
|
||||
input bool InpUseProfitAtrExit = false; // take-profit in ATR multiples (needs InpTakeProfitATR > 0)
|
||||
input double InpMaxAdverseATR = 3.5;
|
||||
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;
|
||||
|
||||
double g_pred_hist[PRED_HIST_CAP][5];
|
||||
int g_pred_hist_len = 0;
|
||||
double g_smooth[5] = {0.2, 0.2, 0.2, 0.2, 0.2};
|
||||
ulong g_bar_index = 0;
|
||||
int g_entry_cooldown_bars = 0;
|
||||
int g_agg_w = 4;
|
||||
int g_sample_n = 2;
|
||||
int g_min_agg_samples = 2;
|
||||
|
||||
void InitDefaultScalerBounds()
|
||||
{
|
||||
// MinMax bounds from ai/yt/models/US500_H1_article_split_meta.json (train-only scaler)
|
||||
double def_min[FEAT_COUNT] = {
|
||||
1352.5,
|
||||
1352.5999755859375,
|
||||
1347.9000244140625,
|
||||
1352.0999755859375,
|
||||
0.0,
|
||||
0.04497450217604637,
|
||||
-0.030356179922819138,
|
||||
-0.04839427396655083,
|
||||
0.00028562467196024954,
|
||||
-0.047754231840372086,
|
||||
1.0,
|
||||
0.00017100000695791095,
|
||||
0.0,
|
||||
0.01168255414813757,
|
||||
0.0760856345295906,
|
||||
-0.49618232250213623,
|
||||
-1.6348180770874023,
|
||||
-1.731970191001892,
|
||||
0.000006116794793342706,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
};
|
||||
double def_max[FEAT_COUNT] = {
|
||||
3250.199951171875,
|
||||
3251.5,
|
||||
3249.5,
|
||||
3250.199951171875,
|
||||
26050000896.0,
|
||||
0.887104868888855,
|
||||
0.09538312256336212,
|
||||
0.10739167034626007,
|
||||
0.02898731827735901,
|
||||
0.036042287945747375,
|
||||
1.0754634141921997,
|
||||
6759499776.0,
|
||||
20.0,
|
||||
0.9637425541877747,
|
||||
0.8267387747764587,
|
||||
0.5573697686195374,
|
||||
1.2618913650512695,
|
||||
1.8784747123718262,
|
||||
0.9100509881973267,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
};
|
||||
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("US500 Article EA: loaded InpFeatMinStr (24)");
|
||||
if(StringLen(InpFeatMaxStr) > 0 && ParseFeatCsv(InpFeatMaxStr, g_feat_max))
|
||||
Print("US500 Article EA: 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;
|
||||
}
|
||||
|
||||
g_agg_w = MathMax(1, MathMin(InpAggWindow, PRED_HIST_CAP));
|
||||
g_sample_n = MathMax(1, InpSampleEveryNBars);
|
||||
g_min_agg_samples = MathMax(1, MathMin(InpMinAggSamples, g_agg_w));
|
||||
g_pred_hist_len = 0;
|
||||
g_bar_index = 0;
|
||||
g_entry_cooldown_bars = 0;
|
||||
for(int k = 0; k < 5; k++)
|
||||
g_smooth[k] = 0.2;
|
||||
|
||||
const bool has_atr = InpUseAdverseAtrExit || (InpUseProfitAtrExit && InpTakeProfitATR > 0.0);
|
||||
const bool has_model_exit = InpPureRelative || InpUseCloseHeadExit || InpUseDirFlipExit;
|
||||
if(!has_atr && !has_model_exit)
|
||||
Print("US500_H1_ArticleEA: WARNING — no exit path enabled (enable InpPureRelative and/or legacy exits / ATR)");
|
||||
|
||||
Print("US500_H1_ArticleEA: ONNX OK. Chart TF=", EnumToString(PERIOD_CURRENT), "; Lookback=", InpLookback,
|
||||
" sampleEvery=", g_sample_n, " aggWindow=", g_agg_w, " minAggSamples=", g_min_agg_samples,
|
||||
" pureRelative=", InpPureRelative,
|
||||
" entryCooldownBars=", InpMinBarsBetweenEntries, " minDirEdge=", InpMinDirEdge,
|
||||
" stayOverClose=", InpRequireStayOverClose,
|
||||
" exitMode=", InpExitMode, " minBarsInTradeModelExit=", InpMinBarsInTradeModelExit,
|
||||
" closeHeadExit=", InpUseCloseHeadExit, " dirFlipExit=", InpUseDirFlipExit, " flipExitEdge=", InpFlipExitEdge,
|
||||
" minBarsAfterExit=", InpMinBarsAfterExit, " cooldownAfterAdverse=", InpCooldownBarsAfterAdverse,
|
||||
" useAdverseATR=", InpUseAdverseAtrExit, " useProfitATR=", InpUseProfitAtrExit,
|
||||
" maxAdverseATR=", InpMaxAdverseATR, " takeProfitATR=", InpTakeProfitATR);
|
||||
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)
|
||||
{
|
||||
if(!InpUseAdverseAtrExit || InpMaxAdverseATR <= 0.0)
|
||||
return false;
|
||||
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(!InpUseProfitAtrExit || 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);
|
||||
// Modes 1/2 (and default): close-long must beat HOLD and stay-long (BUY). Old mode-2 "vs HOLD only" fired almost every bar on softmax.
|
||||
return (p3 > p0 + InpMinCloseBeatHold && p3 > p1);
|
||||
}
|
||||
|
||||
bool ModelCloseShort(const double p0, const double p2, const double p4)
|
||||
{
|
||||
if(InpExitMode == 0)
|
||||
return (p4 >= InpProbCloseS);
|
||||
return (p4 > p0 + InpMinCloseBeatHold && p4 > p2);
|
||||
}
|
||||
|
||||
bool ModelDirFlipExitLong(const double p0, const double p1, const double p2)
|
||||
{
|
||||
if(!InpUseDirFlipExit)
|
||||
return false;
|
||||
const double e = MathMax(0.0, InpFlipExitEdge);
|
||||
return (p2 > p1 + e && p2 > p0 + InpMinBeatHold);
|
||||
}
|
||||
|
||||
bool ModelDirFlipExitShort(const double p0, const double p1, const double p2)
|
||||
{
|
||||
if(!InpUseDirFlipExit)
|
||||
return false;
|
||||
const double e = MathMax(0.0, InpFlipExitEdge);
|
||||
return (p1 > p2 + e && p1 > p0 + InpMinBeatHold);
|
||||
}
|
||||
|
||||
int TrioStrictWinner012(const double p0, const double p1, const double p2)
|
||||
{
|
||||
if(p0 > p1 + REL_EPS && p0 > p2 + REL_EPS)
|
||||
return 0;
|
||||
if(p1 > p0 + REL_EPS && p1 > p2 + REL_EPS)
|
||||
return 1;
|
||||
if(p2 > p0 + REL_EPS && p2 > p1 + REL_EPS)
|
||||
return 2;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int FiveStrictWinner01234(const double p0, const double p1, const double p2, const double p3, const double p4)
|
||||
{
|
||||
const double p[5] = {p0, p1, p2, p3, p4};
|
||||
int best = 0;
|
||||
for(int k = 1; k < 5; k++)
|
||||
if(p[k] > p[best])
|
||||
best = k;
|
||||
const double m = p[best];
|
||||
int cnt = 0;
|
||||
for(int k = 0; k < 5; k++)
|
||||
if(p[k] + REL_EPS >= m)
|
||||
cnt++;
|
||||
if(cnt != 1)
|
||||
return -1;
|
||||
return best;
|
||||
}
|
||||
|
||||
int PositionBarsInTrade()
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return 0;
|
||||
const datetime tOpen = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
const int sh = iBarShift(_Symbol, PERIOD_CURRENT, tOpen, false);
|
||||
if(sh < 0)
|
||||
return 9999;
|
||||
return sh + 1;
|
||||
}
|
||||
|
||||
void ApplyExitCooldown(const bool adverse_stop)
|
||||
{
|
||||
int b = MathMax(0, InpMinBarsAfterExit);
|
||||
if(adverse_stop)
|
||||
b = MathMax(b, MathMax(0, InpCooldownBarsAfterAdverse));
|
||||
if(b > 0)
|
||||
g_entry_cooldown_bars = MathMax(g_entry_cooldown_bars, b);
|
||||
}
|
||||
|
||||
void PushPrediction(const double p0, const double p1, const double p2, const double p3, const double p4, const int maxKeep)
|
||||
{
|
||||
for(int i = PRED_HIST_CAP - 1; i > 0; i--)
|
||||
for(int k = 0; k < 5; k++)
|
||||
g_pred_hist[i][k] = g_pred_hist[i - 1][k];
|
||||
g_pred_hist[0][0] = p0;
|
||||
g_pred_hist[0][1] = p1;
|
||||
g_pred_hist[0][2] = p2;
|
||||
g_pred_hist[0][3] = p3;
|
||||
g_pred_hist[0][4] = p4;
|
||||
int cap = MathMax(1, MathMin(maxKeep, PRED_HIST_CAP));
|
||||
g_pred_hist_len = MathMin(g_pred_hist_len + 1, cap);
|
||||
}
|
||||
|
||||
void RecomputeSmooth(const int aggWindow)
|
||||
{
|
||||
int w = MathMax(1, MathMin(aggWindow, PRED_HIST_CAP));
|
||||
int n = MathMin(w, g_pred_hist_len);
|
||||
if(n < 1)
|
||||
return;
|
||||
for(int k = 0; k < 5; k++)
|
||||
{
|
||||
double s = 0.0;
|
||||
for(int i = 0; i < n; i++)
|
||||
s += g_pred_hist[i][k];
|
||||
g_smooth[k] = s / (double)n;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const bool had_pos = PositionSelect(_Symbol);
|
||||
const bool flat = !had_pos;
|
||||
if(flat && g_entry_cooldown_bars > 0)
|
||||
g_entry_cooldown_bars--;
|
||||
|
||||
g_bar_index++;
|
||||
const bool do_sample = (g_sample_n < 2) || ((g_bar_index % (ulong)g_sample_n) == 0);
|
||||
bool fresh_predict = false;
|
||||
|
||||
if(do_sample)
|
||||
{
|
||||
matrixf Min;
|
||||
if(!PrepareMatrix(Min))
|
||||
{
|
||||
Print("US500 Article EA: PrepareMatrix failed");
|
||||
if(!had_pos)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
vectorf out;
|
||||
out.Resize(5);
|
||||
if(!OnnxRun(g_onnx, ONNX_NO_CONVERSION, Min, out))
|
||||
{
|
||||
Print("OnnxRun failed ", GetLastError());
|
||||
if(!had_pos)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
PushPrediction(out[0], out[1], out[2], out[3], out[4], g_agg_w);
|
||||
RecomputeSmooth(g_agg_w);
|
||||
fresh_predict = true;
|
||||
Print("US500 Article H1 raw HOLD=", out[0], " BUY=", out[1], " SELL=", out[2], " CL=", out[3], " CS=", out[4],
|
||||
" | smooth HOLD=", g_smooth[0], " BUY=", g_smooth[1], " SELL=", g_smooth[2], " CL=", g_smooth[3], " CS=", g_smooth[4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const double p0 = g_smooth[0];
|
||||
const double p1 = g_smooth[1];
|
||||
const double p2 = g_smooth[2];
|
||||
const double p3 = g_smooth[3];
|
||||
const double p4 = g_smooth[4];
|
||||
|
||||
if(flat)
|
||||
{
|
||||
if(!do_sample || !fresh_predict)
|
||||
return;
|
||||
if(g_pred_hist_len < g_min_agg_samples)
|
||||
return;
|
||||
if(g_entry_cooldown_bars > 0)
|
||||
return;
|
||||
|
||||
if(InpEntryMode == 1)
|
||||
{
|
||||
if(InpPureRelative)
|
||||
{
|
||||
const int w3 = TrioStrictWinner012(p0, p1, p2);
|
||||
if(w3 == 1)
|
||||
{
|
||||
if(trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "US500 article BUY"))
|
||||
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
|
||||
}
|
||||
else if(w3 == 2)
|
||||
{
|
||||
if(trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "US500 article SELL"))
|
||||
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double dir = MathMax(p1, p2);
|
||||
if(dir <= p0 + InpMinBeatHold)
|
||||
return;
|
||||
const double edge = MathMax(0.0, InpMinDirEdge);
|
||||
const bool stay_ok_buy = (!InpRequireStayOverClose) || (p1 > p3);
|
||||
const bool stay_ok_sell = (!InpRequireStayOverClose) || (p2 > p4);
|
||||
if(p1 >= p2 && p1 > p0 + InpMinBeatHold && (p1 - p2) >= edge && stay_ok_buy)
|
||||
{
|
||||
if(trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "US500 article BUY"))
|
||||
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
|
||||
}
|
||||
else if(p2 > p1 && p2 > p0 + InpMinBeatHold && (p2 - p1) >= edge && stay_ok_sell)
|
||||
{
|
||||
if(trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "US500 article SELL"))
|
||||
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(p1 >= InpProbBuy && p1 >= p2)
|
||||
{
|
||||
if(trade.Buy(InpLotSize, _Symbol, 0, 0, 0, "US500 article BUY"))
|
||||
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
|
||||
}
|
||||
else if(p2 >= InpProbSell && p2 > p1)
|
||||
{
|
||||
if(trade.Sell(InpLotSize, _Symbol, 0, 0, 0, "US500 article SELL"))
|
||||
g_entry_cooldown_bars = MathMax(0, InpMinBarsBetweenEntries);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
long typ = (long)PositionGetInteger(POSITION_TYPE);
|
||||
double opn = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
if(AdverseExit(typ, opn))
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
ApplyExitCooldown(true);
|
||||
return;
|
||||
}
|
||||
if(ProfitExit(typ, opn))
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
ApplyExitCooldown(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const int bars_in = PositionBarsInTrade();
|
||||
const bool allow_model_exit = (InpMinBarsInTradeModelExit <= 0) || (bars_in >= InpMinBarsInTradeModelExit);
|
||||
if(allow_model_exit)
|
||||
{
|
||||
bool want_close = false;
|
||||
if(InpPureRelative)
|
||||
{
|
||||
const int w5 = FiveStrictWinner01234(p0, p1, p2, p3, p4);
|
||||
if(typ == POSITION_TYPE_BUY)
|
||||
want_close = (w5 != -1 && w5 != 1);
|
||||
else
|
||||
want_close = (w5 != -1 && w5 != 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(typ == POSITION_TYPE_BUY)
|
||||
{
|
||||
const bool head = InpUseCloseHeadExit && ModelCloseLong(p0, p1, p3);
|
||||
const bool flip = ModelDirFlipExitLong(p0, p1, p2);
|
||||
want_close = (head || flip);
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool head = InpUseCloseHeadExit && ModelCloseShort(p0, p2, p4);
|
||||
const bool flip = ModelDirFlipExitShort(p0, p1, p2);
|
||||
want_close = (head || flip);
|
||||
}
|
||||
}
|
||||
if(want_close)
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
ApplyExitCooldown(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
; US500_H1_ArticleEA — Strategy Tester preset (fixed inputs, no optimization)
|
||||
; Copy to: MetaQuotes\Terminal\<instance>\MQL5\Profiles\Tester\
|
||||
; In Tester: Inputs tab → right‑click → Load → pick this file
|
||||
;
|
||||
; Notes:
|
||||
; - InpLookback must stay 48 unless you retrain/re‑embed ONNX with another lookback.
|
||||
; - Use micro lot (0.01) for tests; 1.0 lot caused very large exposure on US500.
|
||||
; - InpMinBeatHold / InpMinCloseBeatHold > 0 reduce churn when softmax is flat.
|
||||
;
|
||||
; Model
|
||||
InpLookback=48||48||1||48||N
|
||||
InpEntryMode=1||1||1||1||N
|
||||
InpProbBuy=0.18||0.18||0.02||0.30||N
|
||||
InpProbSell=0.18||0.18||0.02||0.30||N
|
||||
InpMinBeatHold=0.04||0.04||0.01||0.10||N
|
||||
InpExitMode=2||2||1||2||N
|
||||
InpProbCloseL=0.18||0.18||0.02||0.30||N
|
||||
InpProbCloseS=0.18||0.18||0.02||0.30||N
|
||||
InpMinCloseBeatHold=0.03||0.03||0.01||0.08||N
|
||||
; Session (match Python SESSION_HOUR_OFFSET)
|
||||
InpSessionHourOffset=0||0||1||1||N
|
||||
; Scaler override (empty = built‑in scaler from US500_H1_article_split_meta.json)
|
||||
InpFeatMinStr=
|
||||
InpFeatMaxStr=
|
||||
; Risk
|
||||
InpLotSize=0.01||0.01||0.01||0.10||N
|
||||
InpMagic=902503||902503||1||902503||N
|
||||
InpSlippage=30||30||1||300||N
|
||||
InpMaxAdverseATR=2.0||2.0||0.25||4.0||N
|
||||
InpTakeProfitATR=0.0||0.0||0.25||3.0||N
|
||||
@@ -0,0 +1,24 @@
|
||||
; US500_H1_ArticleEA — genetic / slow optimization preset
|
||||
; InpLookback fixed at 48 (must match embedded ONNX). Copy to MQL5\Profiles\Tester\
|
||||
;
|
||||
; Model
|
||||
InpLookback=48||48||1||48||N
|
||||
InpEntryMode=1||0||1||1||Y
|
||||
InpProbBuy=0.18||0.12||0.02||0.28||Y
|
||||
InpProbSell=0.18||0.12||0.02||0.28||Y
|
||||
InpMinBeatHold=0.04||0.0||0.01||0.10||Y
|
||||
InpExitMode=2||0||1||2||Y
|
||||
InpProbCloseL=0.18||0.12||0.02||0.28||Y
|
||||
InpProbCloseS=0.18||0.12||0.02||0.28||Y
|
||||
InpMinCloseBeatHold=0.03||0.0||0.01||0.08||Y
|
||||
; Session (match Python SESSION_HOUR_OFFSET)
|
||||
InpSessionHourOffset=0||-2||1||2||N
|
||||
; Scaler override (leave empty unless you paste new train bounds)
|
||||
InpFeatMinStr=
|
||||
InpFeatMaxStr=
|
||||
; Risk
|
||||
InpLotSize=0.01||0.01||0.01||0.10||N
|
||||
InpMagic=902503||902503||1||902503||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
|
||||
@@ -0,0 +1,30 @@
|
||||
# ENKS / clustering article — notes → training in this repo
|
||||
|
||||
Summary of the methodology described in the article (MQL5 / ENKS trader clusters):
|
||||
|
||||
- Models are trained in **Python**, then converted to **ENKS** for the MetaTrader include/bot stack. This repository does **not** ship an ENKS encoder; training here exports **ONNX + JSON meta + scaler** like `ai/xauusd_h1/`. Convert ENKS with the author’s tool or workflow from the article.
|
||||
- **Clustering** (article: Cayley / trade matching): use **forward-return fingerprints** per bar and **KMeans** on the in-sample window only, then optional **per-cluster balancing** of sample weights during training (see `train_article_split.py`).
|
||||
- **Windows**: train **2010-01-01 → 2019-12-31**; out-of-sample / forward **2020-01-01 → 2024-12-31**. Scaler is fit **only** on the train window (no leakage).
|
||||
- **Capital / Capodon-style US H1**: default symbol `US500` on **H1**; override with `YT_SYMBOL`. The article notes models can be attached on other timeframes; EA SL/TP and filters are tuned separately.
|
||||
- **Includes (`tendq`, etc.)**: not present in this repo; wire your ONNX EA to the exported `*_meta.json` and scaler like the existing XAUUSD H1 action EA.
|
||||
|
||||
## Run training
|
||||
|
||||
From `ai/yt` (MetaTrader 5 must be installed and history available for the symbol):
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python train_article_split.py
|
||||
```
|
||||
|
||||
Environment overrides:
|
||||
|
||||
| Variable | Default | Meaning |
|
||||
|----------------|----------------|----------------------------------|
|
||||
| `YT_SYMBOL` | `US500` | MT5 symbol |
|
||||
| `YT_LOOKBACK` | `48` | Sequence length (bars) |
|
||||
| `YT_EPOCHS` | `40` | Max epochs |
|
||||
| `YT_BATCH` | `64` | Batch size |
|
||||
| `YT_CLUSTERS` | `12` | KMeans clusters (0 = disable) |
|
||||
|
||||
Outputs: `ai/yt/models/<SYMBOL>_H1_article_split.onnx`, scaler `.pkl`, `*_meta.json`.
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"symbol": "US500",
|
||||
"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"
|
||||
],
|
||||
"train_window": [
|
||||
"2010-01-01",
|
||||
"2020-01-01"
|
||||
],
|
||||
"oos_window": [
|
||||
"2020-01-01",
|
||||
"2025-01-01"
|
||||
],
|
||||
"scaler_fit_on": "train_only_rows_before_2020",
|
||||
"clustering": "KMeans n=12 on forward returns (1,2,4,8,16) ATR-norm; sample reweight train",
|
||||
"scaler_feature_min": [
|
||||
1352.5,
|
||||
1352.5999755859375,
|
||||
1347.9000244140625,
|
||||
1352.0999755859375,
|
||||
0.0,
|
||||
0.04497450217604637,
|
||||
-0.030356179922819138,
|
||||
-0.04839427396655083,
|
||||
0.00028562467196024954,
|
||||
-0.047754231840372086,
|
||||
1.0,
|
||||
0.00017100000695791095,
|
||||
0.0,
|
||||
0.01168255414813757,
|
||||
0.0760856345295906,
|
||||
-0.49618232250213623,
|
||||
-1.6348180770874023,
|
||||
-1.731970191001892,
|
||||
6.116794793342706e-06,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"scaler_feature_max": [
|
||||
3250.199951171875,
|
||||
3251.5,
|
||||
3249.5,
|
||||
3250.199951171875,
|
||||
26050000896.0,
|
||||
0.887104868888855,
|
||||
0.09538312256336212,
|
||||
0.10739167034626007,
|
||||
0.02898731827735901,
|
||||
0.036042287945747375,
|
||||
1.0754634141921997,
|
||||
6759499776.0,
|
||||
20.0,
|
||||
0.9637425541877747,
|
||||
0.8267387747764587,
|
||||
0.5573697686195374,
|
||||
1.2618913650512695,
|
||||
1.8784747123718262,
|
||||
0.9100509881973267,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"scaler_scale": [
|
||||
0.0005269537214189768,
|
||||
0.0005266206571832299,
|
||||
0.0005258729797787964,
|
||||
0.0005268426612019539,
|
||||
3.8387713147125524e-11,
|
||||
1.1874645948410034,
|
||||
7.952962398529053,
|
||||
6.419064044952393,
|
||||
34.84115219116211,
|
||||
11.933670043945312,
|
||||
13.25145435333252,
|
||||
1.4793993807771244e-10,
|
||||
0.05000000074505806,
|
||||
1.05035400390625,
|
||||
1.332173228263855,
|
||||
0.949169933795929,
|
||||
0.34521928429603577,
|
||||
0.2769741714000702,
|
||||
1.0988469123840332,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"oos_val_accuracy": 0.293920636177063,
|
||||
"oos_val_loss": 1.5438036918640137,
|
||||
"notes": "ONNX for repo EAs; convert to ENKS externally if required."
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
-r ../xauusd_h1/requirements.txt
|
||||
@@ -0,0 +1,315 @@
|
||||
"""
|
||||
Article-style training: chronological train (2010–2019) vs OOS (2020–2024),
|
||||
optional KMeans on forward-return fingerprints (trade-shape clustering),
|
||||
ONNX export compatible with the repo's MT5 ONNX EAs.
|
||||
|
||||
Reuses feature + label definitions from ai/xauusd_h1 (24 features, 5 classes).
|
||||
ENKS conversion is out of scope — use the article's tooling after ONNX if needed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import MetaTrader5 as mt5
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import tensorflow as tf
|
||||
import tf2onnx
|
||||
import onnx
|
||||
from sklearn.cluster import KMeans
|
||||
from sklearn.preprocessing import MinMaxScaler
|
||||
from tensorflow import keras
|
||||
from tensorflow.keras import layers
|
||||
from tqdm import tqdm
|
||||
|
||||
# Reuse XAUUSD H1 stack (same 24 dims / 5 classes as published EAs).
|
||||
_XH1 = Path(__file__).resolve().parent.parent / "xauusd_h1"
|
||||
sys.path.insert(0, str(_XH1))
|
||||
from features import NUM_FEATURES, prepare_features_full # noqa: E402
|
||||
from labeling import atr_series, class_weights, compute_action_labels # noqa: E402
|
||||
|
||||
NUM_CLASSES = 5
|
||||
CLASS_NAMES = ["HOLD", "BUY", "SELL_SHORT", "CLOSE_LONG", "CLOSE_SHORT"]
|
||||
|
||||
TRAIN_START = pd.Timestamp("2010-01-01")
|
||||
TRAIN_END = pd.Timestamp("2020-01-01") # exclusive: train < this
|
||||
OOS_END = pd.Timestamp("2025-01-01") # exclusive: val < this (covers through 2024)
|
||||
|
||||
|
||||
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 symbol history in MT5")
|
||||
|
||||
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 forward_return_fingerprints(
|
||||
df: pd.DataFrame,
|
||||
feat_index: pd.DatetimeIndex,
|
||||
horizons: tuple[int, ...] = (1, 2, 4, 8, 16),
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Normalized forward returns at listed horizons; aligned to feat rows."""
|
||||
close = df["close"].to_numpy(dtype=np.float64)
|
||||
atr = atr_series(df, 14).to_numpy(dtype=np.float64)
|
||||
pos = df.index.get_indexer(feat_index)
|
||||
n = len(feat_index)
|
||||
d = len(horizons)
|
||||
M = np.zeros((n, d), dtype=np.float64)
|
||||
valid = np.ones(n, dtype=bool)
|
||||
max_h = max(horizons)
|
||||
for j, i in enumerate(pos):
|
||||
if i < 0 or i + max_h >= len(close):
|
||||
valid[j] = False
|
||||
continue
|
||||
a = float(atr[i]) if np.isfinite(atr[i]) and atr[i] > 0 else close[i] * 1e-4
|
||||
for k, h in enumerate(horizons):
|
||||
if i + h >= len(close):
|
||||
valid[j] = False
|
||||
break
|
||||
M[j, k] = (close[i + h] - close[i]) / a
|
||||
return M, valid
|
||||
|
||||
|
||||
def create_sequences(
|
||||
X: np.ndarray,
|
||||
y: np.ndarray,
|
||||
times: np.ndarray,
|
||||
lookback: int,
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
xs, ys, t_end = [], [], []
|
||||
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])
|
||||
t_end.append(times[i])
|
||||
return (
|
||||
np.asarray(xs, dtype=np.float32),
|
||||
np.asarray(ys, dtype=np.int64),
|
||||
np.asarray(t_end),
|
||||
)
|
||||
|
||||
|
||||
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("YT_SYMBOL", "US500")
|
||||
lookback = int(os.environ.get("YT_LOOKBACK", "48"))
|
||||
epochs = int(os.environ.get("YT_EPOCHS", "40"))
|
||||
batch_size = int(os.environ.get("YT_BATCH", "64"))
|
||||
n_clusters = int(os.environ.get("YT_CLUSTERS", "12"))
|
||||
|
||||
fetch_start = datetime(2009, 6, 1)
|
||||
fetch_end = datetime(2025, 1, 1)
|
||||
|
||||
out_dir = Path(__file__).resolve().parent / "models"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
onnx_path = out_dir / f"{symbol}_H1_article_split.onnx"
|
||||
meta_path = out_dir / f"{symbol}_H1_article_split_meta.json"
|
||||
|
||||
print(f"Symbol={symbol} H1 | train [{TRAIN_START.date()} , {TRAIN_END.date()}) | OOS [{TRAIN_END.date()} , {OOS_END.date()})")
|
||||
print("Fetching MT5 …")
|
||||
try:
|
||||
raw = fetch_mt5_range(symbol, mt5.TIMEFRAME_H1, fetch_start, fetch_end)
|
||||
finally:
|
||||
mt5.shutdown()
|
||||
|
||||
raw = raw.loc[raw.index >= TRAIN_START]
|
||||
if len(raw) < 500:
|
||||
print("ERROR: Not enough H1 bars after 2010 — check symbol and History Center.")
|
||||
return 1
|
||||
|
||||
print(f"Bars: {len(raw)} range: {raw.index[0]} → {raw.index[-1]}")
|
||||
|
||||
feat = prepare_features_full(raw)
|
||||
labels = compute_action_labels(raw).loc[feat.index]
|
||||
y = labels.values.astype(np.int64)
|
||||
X_raw = feat.values.astype(np.float32)
|
||||
times = feat.index.to_numpy()
|
||||
|
||||
valid = np.isfinite(X_raw).all(axis=1) & (y >= 0) & (y < NUM_CLASSES)
|
||||
X_raw = X_raw[valid]
|
||||
y = y[valid]
|
||||
times = times[valid]
|
||||
|
||||
train_row = times < np.datetime64(TRAIN_END)
|
||||
if int(train_row.sum()) < 800:
|
||||
print("ERROR: Too few train rows before 2020 — need deeper history.")
|
||||
return 1
|
||||
|
||||
scaler = MinMaxScaler()
|
||||
scaler.fit(X_raw[train_row])
|
||||
Xn = scaler.transform(X_raw).astype(np.float32)
|
||||
|
||||
X_seq, y_seq, t_end = create_sequences(Xn, y, times, lookback)
|
||||
if len(X_seq) < 500:
|
||||
print("ERROR: Too few sequences.")
|
||||
return 1
|
||||
|
||||
train_m = t_end < np.datetime64(TRAIN_END)
|
||||
val_m = (t_end >= np.datetime64(TRAIN_END)) & (t_end < np.datetime64(OOS_END))
|
||||
X_train, y_train = X_seq[train_m], y_seq[train_m]
|
||||
X_val, y_val = X_seq[val_m], y_seq[val_m]
|
||||
if len(X_val) < 200:
|
||||
print("ERROR: Too few OOS sequences in 2020–2024.")
|
||||
return 1
|
||||
|
||||
print(f"Sequences train={len(X_train)} val(OOS)={len(X_val)} lookback={lookback}")
|
||||
|
||||
cw = class_weights(y_train, NUM_CLASSES)
|
||||
tr_idx = np.flatnonzero(train_m)
|
||||
base_w = np.array([cw[int(y_seq[i])] for i in tr_idx], dtype=np.float32)
|
||||
sample_w = base_w.copy()
|
||||
|
||||
if n_clusters > 1:
|
||||
fp, fp_ok = forward_return_fingerprints(raw, pd.DatetimeIndex(times))
|
||||
fp_seq = fp[lookback - 1 :]
|
||||
ok_seq = fp_ok[lookback - 1 :]
|
||||
fp_tr = fp_seq[tr_idx]
|
||||
ok_tr = ok_seq[tr_idx]
|
||||
fit_mask = ok_tr & np.isfinite(fp_tr).all(axis=1)
|
||||
if int(fit_mask.sum()) >= n_clusters * 5:
|
||||
km = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
|
||||
km.fit(fp_tr[fit_mask])
|
||||
labels_tr = np.full(len(tr_idx), -1, dtype=np.int32)
|
||||
labels_tr[fit_mask] = km.predict(fp_tr[fit_mask])
|
||||
counts = np.zeros(n_clusters, dtype=np.float64)
|
||||
for c in labels_tr:
|
||||
if 0 <= c < n_clusters:
|
||||
counts[c] += 1.0
|
||||
counts = np.maximum(counts, 1.0)
|
||||
total_assigned = max(int((labels_tr >= 0).sum()), 1)
|
||||
w_cl = np.ones(len(tr_idx), dtype=np.float32)
|
||||
for j in range(len(tr_idx)):
|
||||
c = int(labels_tr[j])
|
||||
if c >= 0:
|
||||
w_cl[j] = float(total_assigned / (n_clusters * counts[c]))
|
||||
sample_w = base_w * w_cl
|
||||
sample_w *= len(sample_w) / float(np.sum(sample_w))
|
||||
print(
|
||||
f"KMeans trade-fingerprint clusters={n_clusters} "
|
||||
"(fit on in-sample train only; sample_weight × class balance)"
|
||||
)
|
||||
else:
|
||||
print("Skipping KMeans: not enough valid fingerprint rows in train.")
|
||||
|
||||
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=10, restore_best_weights=True
|
||||
),
|
||||
keras.callbacks.ReduceLROnPlateau(
|
||||
monitor="val_loss", factor=0.5, patience=4, min_lr=1e-6
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
loss, acc = model.evaluate(X_val, y_val, verbose=0)
|
||||
print(f"OOS val_loss={loss:.4f} val_accuracy={acc:.4f}")
|
||||
|
||||
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, str(onnx_path))
|
||||
|
||||
with open(str(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,
|
||||
"train_window": [str(TRAIN_START.date()), str(TRAIN_END.date())],
|
||||
"oos_window": [str(TRAIN_END.date()), str(OOS_END.date())],
|
||||
"scaler_fit_on": "train_only_rows_before_2020",
|
||||
"clustering": f"KMeans n={n_clusters} on forward returns (1,2,4,8,16) ATR-norm; sample reweight train" if n_clusters > 1 else "disabled",
|
||||
"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,
|
||||
"oos_val_accuracy": float(acc),
|
||||
"oos_val_loss": float(loss),
|
||||
"notes": "ONNX for repo EAs; convert to ENKS externally if required.",
|
||||
}
|
||||
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 (%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}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,607 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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", "MSFT", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,27 @@
|
||||
; saved on 2026.04.22
|
||||
; genetic optimization set for DarvasBoxXAUUSD/main.mq5
|
||||
; load in MT5 Strategy Tester -> Inputs -> Load
|
||||
;
|
||||
; === Core Darvas Box Parameters ===
|
||||
BoxPeriod=165||80||5||280||Y
|
||||
BoxDeviation=25140||8000||500||50000||Y
|
||||
VolumeThreshold=938||200||25||2500||Y
|
||||
StopLoss=1665||600||50||4000||Y
|
||||
TakeProfit=3685||1200||75||8000||Y
|
||||
EnableLogging=false||false||0||true||N
|
||||
BoxColor=255||0||1||16777215||N
|
||||
BoxWidth=1||1||1||3||N
|
||||
;
|
||||
; === Trend Confirmation Parameters ===
|
||||
TrendTimeframe=16386||16385||1||16388||Y
|
||||
MA_Period=125||20||5||260||Y
|
||||
MA_Method=1||0||1||3||Y
|
||||
MA_Price=6||0||1||6||Y
|
||||
TrendThreshold=4.94||1.0||0.2||20.0||Y
|
||||
;
|
||||
; === Volume Analysis Parameters ===
|
||||
VolumeMA_Period=110||20||5||220||Y
|
||||
VolumeThresholdMultiplier=1.5||1.0||0.1||3.5||Y
|
||||
;
|
||||
; === Execution / ID ===
|
||||
MagicNumber=135790||135790||1||1357900||N
|
||||
@@ -0,0 +1,443 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DarvasBox.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"
|
||||
#property strict
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
// Input parameters
|
||||
input int BoxPeriod = 165; // Period for Darvas Box calculation
|
||||
input double BoxDeviation = 25140; // Box deviation in points
|
||||
input int VolumeThreshold = 938; // Minimum volume for confirmation
|
||||
input double StopLoss = 1665; // Stop loss in points (increased for BTCUSD)
|
||||
input double TakeProfit = 3685; // Take profit in points (increased for BTCUSD)
|
||||
input bool EnableLogging = false; // Enable detailed logging
|
||||
input color BoxColor = clrBlue; // Color for Darvas Box
|
||||
input int BoxWidth = 1; // Width of box lines
|
||||
|
||||
// Trend confirmation parameters
|
||||
input ENUM_TIMEFRAMES TrendTimeframe = PERIOD_H2; // Timeframe for trend analysis
|
||||
input int MA_Period = 125; // Moving Average period for trend
|
||||
input ENUM_MA_METHOD MA_Method = MODE_EMA; // Moving Average method
|
||||
input ENUM_APPLIED_PRICE MA_Price = PRICE_WEIGHTED; // Price type for MA
|
||||
input double TrendThreshold = 4.94; // Trend strength threshold
|
||||
|
||||
// Volume analysis parameters
|
||||
input int VolumeMA_Period = 110; // Period for Volume MA
|
||||
input double VolumeThresholdMultiplier = 1.5; // Volume spike threshold
|
||||
|
||||
// Magic Number
|
||||
input int MagicNumber = 135790; // Magic Number for Trades
|
||||
|
||||
// Global variables
|
||||
double boxHigh = 0;
|
||||
double boxLow = 0;
|
||||
bool boxFormed = false;
|
||||
datetime lastBoxTime = 0;
|
||||
string boxName = "DarvasBox_";
|
||||
double minStopLevel = 0;
|
||||
double point = 0;
|
||||
CTrade trade;
|
||||
|
||||
// Indicator handles
|
||||
int maHandle;
|
||||
int volumeHandle;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize indicators and variables
|
||||
boxHigh = 0;
|
||||
boxLow = 0;
|
||||
boxFormed = false;
|
||||
lastBoxTime = 0;
|
||||
|
||||
// Get symbol properties
|
||||
point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
minStopLevel = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
||||
|
||||
// Initialize indicators
|
||||
maHandle = iMA(_Symbol, TrendTimeframe, MA_Period, 0, MA_Method, MA_Price);
|
||||
volumeHandle = iVolumes(_Symbol, PERIOD_CURRENT, VOLUME_TICK);
|
||||
|
||||
if(maHandle == INVALID_HANDLE || volumeHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Error creating indicators");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Configure trade object
|
||||
trade.SetDeviationInPoints(10);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
trade.SetAsyncMode(false);
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Darvas Box Expert Advisor initialized");
|
||||
Print("Symbol: ", _Symbol);
|
||||
Print("Point: ", point);
|
||||
Print("Minimum Stop Level: ", minStopLevel);
|
||||
}
|
||||
|
||||
// Delete any existing box objects
|
||||
ObjectsDeleteAll(0, boxName);
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Draw Darvas Box on chart |
|
||||
//+------------------------------------------------------------------+
|
||||
void DrawDarvasBox()
|
||||
{
|
||||
if(!boxFormed) return;
|
||||
|
||||
datetime time1 = iTime(_Symbol, PERIOD_H1, BoxPeriod);
|
||||
datetime time2 = iTime(_Symbol, PERIOD_H1, 0);
|
||||
|
||||
// Delete old box
|
||||
ObjectsDeleteAll(0, boxName);
|
||||
|
||||
// Draw box
|
||||
ObjectCreate(0, boxName + "Top", OBJ_TREND, 0, time1, boxHigh, time2, boxHigh);
|
||||
ObjectCreate(0, boxName + "Bottom", OBJ_TREND, 0, time1, boxLow, time2, boxLow);
|
||||
|
||||
// Set box properties
|
||||
ObjectSetInteger(0, boxName + "Top", OBJPROP_COLOR, BoxColor);
|
||||
ObjectSetInteger(0, boxName + "Bottom", OBJPROP_COLOR, BoxColor);
|
||||
ObjectSetInteger(0, boxName + "Top", OBJPROP_WIDTH, BoxWidth);
|
||||
ObjectSetInteger(0, boxName + "Bottom", OBJPROP_WIDTH, BoxWidth);
|
||||
ObjectSetInteger(0, boxName + "Top", OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, boxName + "Bottom", OBJPROP_RAY_RIGHT, true);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Calculate Darvas Box levels |
|
||||
//+------------------------------------------------------------------+
|
||||
void CalculateDarvasBox()
|
||||
{
|
||||
double high = 0;
|
||||
double low = DBL_MAX;
|
||||
|
||||
// Find highest high and lowest low in the period
|
||||
for(int i = 0; i < BoxPeriod; i++)
|
||||
{
|
||||
high = MathMax(high, iHigh(_Symbol, PERIOD_H1, i));
|
||||
low = MathMin(low, iLow(_Symbol, PERIOD_H1, i));
|
||||
}
|
||||
|
||||
double range = high - low;
|
||||
double allowedRange = BoxDeviation * _Point;
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Box Calculation - High: ", high, " Low: ", low, " Range: ", range, " Allowed Range: ", allowedRange);
|
||||
}
|
||||
|
||||
// Check if box is formed
|
||||
if(range <= allowedRange)
|
||||
{
|
||||
boxHigh = high;
|
||||
boxLow = low;
|
||||
boxFormed = true;
|
||||
lastBoxTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
||||
|
||||
// Draw the box
|
||||
DrawDarvasBox();
|
||||
|
||||
if(EnableLogging)
|
||||
Print("Box Formed - High: ", boxHigh, " Low: ", boxLow, " Time: ", lastBoxTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
boxFormed = false;
|
||||
// Delete box if it exists
|
||||
ObjectsDeleteAll(0, boxName);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Validate and adjust stop levels |
|
||||
//+------------------------------------------------------------------+
|
||||
bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
double minSlDistance = MathMax(minStopLevel, StopLoss * point);
|
||||
double minTpDistance = MathMax(minStopLevel, TakeProfit * point);
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Minimum SL Distance: ", minSlDistance);
|
||||
Print("Minimum TP Distance: ", minTpDistance);
|
||||
}
|
||||
|
||||
// Adjust stop loss
|
||||
if(orderType == ORDER_TYPE_BUY)
|
||||
{
|
||||
sl = price - minSlDistance;
|
||||
tp = price + minTpDistance;
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Buy Order Levels:");
|
||||
Print("Entry: ", price);
|
||||
Print("Stop Loss: ", sl);
|
||||
Print("Take Profit: ", tp);
|
||||
}
|
||||
}
|
||||
else // ORDER_TYPE_SELL
|
||||
{
|
||||
sl = price + minSlDistance;
|
||||
tp = price - minTpDistance;
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Sell Order Levels:");
|
||||
Print("Entry: ", price);
|
||||
Print("Stop Loss: ", sl);
|
||||
Print("Take Profit: ", tp);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check trend direction and strength |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
double ma[];
|
||||
ArraySetAsSeries(ma, true);
|
||||
|
||||
if(CopyBuffer(maHandle, 0, 0, 2, ma) <= 0)
|
||||
return false;
|
||||
|
||||
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double trendStrength = MathAbs(currentPrice - ma[0]) / point;
|
||||
|
||||
if(EnableLogging)
|
||||
Print("Trend Strength: ", trendStrength);
|
||||
|
||||
if(orderType == ORDER_TYPE_BUY)
|
||||
return (currentPrice > ma[0] && trendStrength > TrendThreshold);
|
||||
else
|
||||
return (currentPrice < ma[0] && trendStrength > TrendThreshold);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check volume conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckVolumeConditions()
|
||||
{
|
||||
double volumes[];
|
||||
ArraySetAsSeries(volumes, true);
|
||||
|
||||
if(CopyBuffer(volumeHandle, 0, 0, VolumeMA_Period + 1, volumes) <= 0)
|
||||
return false;
|
||||
|
||||
double volumeMA = 0;
|
||||
for(int i = 1; i <= VolumeMA_Period; i++)
|
||||
volumeMA += volumes[i];
|
||||
volumeMA /= VolumeMA_Period;
|
||||
|
||||
double currentVolume = volumes[0];
|
||||
double volumeRatio = currentVolume / volumeMA;
|
||||
|
||||
if(EnableLogging)
|
||||
Print("Volume Ratio: ", volumeRatio);
|
||||
|
||||
return (volumeRatio > VolumeThresholdMultiplier);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Place trade order |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
|
||||
{
|
||||
// Validate and adjust stop levels
|
||||
if(!ValidateStopLevels(price, sl, tp, orderType))
|
||||
{
|
||||
if(EnableLogging)
|
||||
Print("Invalid stop levels after adjustment");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check trend and volume conditions
|
||||
if(!IsTrendFavorable(orderType))
|
||||
{
|
||||
if(EnableLogging)
|
||||
Print("Trend not favorable for trade");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!CheckVolumeConditions())
|
||||
{
|
||||
if(EnableLogging)
|
||||
Print("Volume conditions not met");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Order Details:");
|
||||
Print("Type: ", EnumToString(orderType));
|
||||
Print("Price: ", price);
|
||||
Print("Stop Loss: ", sl);
|
||||
Print("Take Profit: ", tp);
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
|
||||
if(orderType == ORDER_TYPE_BUY)
|
||||
{
|
||||
result = trade.Buy(0.01, _Symbol, price, sl, tp, "Darvas Box Breakout");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = trade.Sell(0.01, _Symbol, price, sl, tp, "Darvas Box Breakdown");
|
||||
}
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
if(result)
|
||||
Print((orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), " Order Placed Successfully");
|
||||
else
|
||||
Print((orderType == ORDER_TYPE_BUY ? "Buy" : "Sell"), " Order Failed - Error: ", trade.ResultRetcode(), " Description: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Calculate new box levels
|
||||
CalculateDarvasBox();
|
||||
|
||||
// Check for trading signals
|
||||
if(boxFormed)
|
||||
{
|
||||
double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double currentVolume = iVolume(_Symbol, PERIOD_CURRENT, 0);
|
||||
|
||||
if(EnableLogging)
|
||||
{
|
||||
Print("Current Price: ", currentPrice, " Box High: ", boxHigh, " Box Low: ", boxLow);
|
||||
Print("Current Volume: ", currentVolume, " Volume Threshold: ", VolumeThreshold);
|
||||
}
|
||||
|
||||
// Check for breakout above box
|
||||
if(currentPrice > boxHigh && currentVolume > VolumeThreshold)
|
||||
{
|
||||
if(EnableLogging)
|
||||
Print("Breakout Signal Detected - Price above box high");
|
||||
|
||||
// Buy signal
|
||||
if(!PositionExistsByMagic(_Symbol, MagicNumber)) // No existing positions with our magic number
|
||||
{
|
||||
double sl = currentPrice - StopLoss * _Point;
|
||||
double tp = currentPrice + TakeProfit * _Point;
|
||||
|
||||
if(EnableLogging)
|
||||
Print("Preparing Buy Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp);
|
||||
|
||||
PlaceOrder(ORDER_TYPE_BUY, currentPrice, sl, tp);
|
||||
}
|
||||
else if(EnableLogging)
|
||||
Print("Skipping Buy Signal - Position already exists");
|
||||
}
|
||||
|
||||
// Check for breakdown below box
|
||||
if(currentPrice < boxLow && currentVolume > VolumeThreshold)
|
||||
{
|
||||
if(EnableLogging)
|
||||
Print("Breakdown Signal Detected - Price below box low");
|
||||
|
||||
// Sell signal
|
||||
if(!PositionExistsByMagic(_Symbol, MagicNumber)) // No existing positions with our magic number
|
||||
{
|
||||
double sl = currentPrice + StopLoss * _Point;
|
||||
double tp = currentPrice - TakeProfit * _Point;
|
||||
|
||||
if(EnableLogging)
|
||||
Print("Preparing Sell Order - Price: ", currentPrice, " SL: ", sl, " TP: ", tp);
|
||||
|
||||
PlaceOrder(ORDER_TYPE_SELL, currentPrice, sl, tp);
|
||||
}
|
||||
else if(EnableLogging)
|
||||
Print("Skipping Sell Signal - Position already exists");
|
||||
}
|
||||
}
|
||||
else if(EnableLogging)
|
||||
Print("No Box Formed - Waiting for consolidation");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get last error description |
|
||||
//+------------------------------------------------------------------+
|
||||
string GetLastErrorDescription()
|
||||
{
|
||||
string errorDescription;
|
||||
switch(GetLastError())
|
||||
{
|
||||
case 0: errorDescription = "No error"; break;
|
||||
case 1: errorDescription = "No error, but result unknown"; break;
|
||||
case 2: errorDescription = "Common error"; break;
|
||||
case 3: errorDescription = "Invalid trade parameters"; break;
|
||||
case 4: errorDescription = "Trade server is busy"; break;
|
||||
case 5: errorDescription = "Old version of the client terminal"; break;
|
||||
case 6: errorDescription = "No connection with trade server"; break;
|
||||
case 7: errorDescription = "Not enough rights"; break;
|
||||
case 8: errorDescription = "Too frequent requests"; break;
|
||||
case 9: errorDescription = "Malfunctional trade operation"; break;
|
||||
case 64: errorDescription = "Account disabled"; break;
|
||||
case 65: errorDescription = "Invalid account"; break;
|
||||
case 128: errorDescription = "Trade timeout"; break;
|
||||
case 129: errorDescription = "Invalid price"; break;
|
||||
case 130: errorDescription = "Invalid stops"; break;
|
||||
case 131: errorDescription = "Invalid trade volume"; break;
|
||||
case 132: errorDescription = "Market is closed"; break;
|
||||
case 133: errorDescription = "Trade is disabled"; break;
|
||||
case 134: errorDescription = "Not enough money"; break;
|
||||
case 135: errorDescription = "Price changed"; break;
|
||||
case 136: errorDescription = "Off quotes"; break;
|
||||
case 137: errorDescription = "Broker is busy"; break;
|
||||
case 138: errorDescription = "Requote"; break;
|
||||
case 139: errorDescription = "Order is locked"; break;
|
||||
case 140: errorDescription = "Long positions only allowed"; break;
|
||||
case 141: errorDescription = "Too many requests"; break;
|
||||
case 145: errorDescription = "Modification denied because order is too close to market"; break;
|
||||
case 146: errorDescription = "Trade context is busy"; break;
|
||||
case 147: errorDescription = "Expirations are denied by broker"; break;
|
||||
case 148: errorDescription = "Amount of open and pending orders has reached the limit"; break;
|
||||
case 149: errorDescription = "Hedging is prohibited"; break;
|
||||
case 150: errorDescription = "Prohibited by FIFO rules"; break;
|
||||
default: errorDescription = "Unknown error"; break;
|
||||
}
|
||||
return errorDescription;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Delete all box objects
|
||||
ObjectsDeleteAll(0, boxName);
|
||||
|
||||
if(EnableLogging)
|
||||
Print("Expert Advisor deinitialized - Reason: ", reason);
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMACrossOver.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.01"
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
//--- Eingabeparameter (Input Parameters) - Optimized Profitable Parameters
|
||||
input int EMA_Periode = 50; // EMA Periode
|
||||
input double PreisSchwelle = 700.0; // Preisbewegung Schwelle in Pips
|
||||
input double SteigungSchwelle = 25.0; // EMA Steigung Schwelle in Pips
|
||||
input int ÜberwachungTimeout = 340; // Überwachungszeit in Sekunden
|
||||
input double TrailingStop = 370.0; // Gleitender Stop in Pips
|
||||
input bool UseTrailingStop = true; // Gleitenden Stop anwenden
|
||||
input double TrailingActivationPips = 0.0; // Mindestgewinn in Pips bis Trail startet (0 = Konto-Profit>0)
|
||||
input bool UseStaleStopLossExit = false; // schließen wenn SL zu lange nicht angepasst wurde
|
||||
input int StaleStopLossSeconds = 33800; // Sekunden ohne SL-Änderung -> Close (0 = aus)
|
||||
input double LotGröße = 0.07; // Handelsvolumen
|
||||
input int MagicNumber = 135790; // Magic Number für Trades
|
||||
input bool UseSpreadAdjustment = true; // Spread-Anpassung verwenden
|
||||
input ENUM_TIMEFRAMES Timeframe = PERIOD_H1; // Zeitraum für Analyse
|
||||
input bool UseBarData = true; // Bar-Daten statt Tick-Daten verwenden
|
||||
input int MaxTradesPerCrossover = 3; // Maximale Trades pro Crossover-Ereignis
|
||||
input int ProfitCheckBars = 11; // Bars bis zur Profit-Prüfung
|
||||
input bool CloseUnprofitableTrades = true; // Unprofitable Trades nach X Bars schließen
|
||||
input bool UseWeeklyADXFilter = true; // W1 ADX Trendfilter aktivieren
|
||||
input int WeeklyADXPeriod = 15; // ADX-Periode auf W1
|
||||
input double WeeklyADXMin = 40.0; // Minimaler ADX fuer Trendfreigabe
|
||||
input int WeeklyADXBarShift = 2; // 1=letzte geschlossene W1-Kerze
|
||||
input bool WeeklyADXUseDirection = true; // +DI/-DI Richtung mitpruefen
|
||||
|
||||
//--- Globale Variablen (Global Variables)
|
||||
int ema_handle; // EMA Indicator Handle
|
||||
double ema_array[]; // Array für EMA
|
||||
datetime letzte_überwachung_zeit; // Zeit der letzten Überwachung
|
||||
bool überwachung_aktiv = false; // Überwachungsstatus
|
||||
bool preis_trigger_aktiv = false; // Preis-Trigger Status
|
||||
bool steigung_trigger_aktiv = false; // Steigungs-Trigger Status
|
||||
int ticket = 0; // Trade Ticket
|
||||
CTrade trade; // CTrade Objekt
|
||||
int trades_in_current_crossover = 0; // Anzahl Trades im aktuellen Crossover
|
||||
bool crossover_detected = false; // Crossover erkannt
|
||||
datetime trade_open_time = 0; // Zeitpunkt des Trade-Öffnens
|
||||
datetime g_last_sl_adjust_success_time = 0; // letzte erfolgreiche SL-Verschiebung (Stale-Exit)
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Weekly ADX trend filter |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsWeeklyADXTrendFavorable(ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
if(!UseWeeklyADXFilter)
|
||||
return true;
|
||||
|
||||
int adxShift = WeeklyADXBarShift;
|
||||
if(adxShift < 0)
|
||||
adxShift = 0;
|
||||
|
||||
int adx_handle = iADX(_Symbol, PERIOD_W1, WeeklyADXPeriod);
|
||||
if(adx_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("TRACE: Weekly ADX Handle ungültig - Filter blockiert Entry");
|
||||
return false;
|
||||
}
|
||||
|
||||
double adx_buf[], plus_di_buf[], minus_di_buf[];
|
||||
ArraySetAsSeries(adx_buf, true);
|
||||
ArraySetAsSeries(plus_di_buf, true);
|
||||
ArraySetAsSeries(minus_di_buf, true);
|
||||
|
||||
bool ok_adx = (CopyBuffer(adx_handle, 0, adxShift, 1, adx_buf) > 0);
|
||||
bool ok_plus = (CopyBuffer(adx_handle, 1, adxShift, 1, plus_di_buf) > 0);
|
||||
bool ok_minus = (CopyBuffer(adx_handle, 2, adxShift, 1, minus_di_buf) > 0);
|
||||
IndicatorRelease(adx_handle);
|
||||
|
||||
if(!ok_adx || !ok_plus || !ok_minus)
|
||||
{
|
||||
Print("TRACE: Weekly ADX Daten nicht verfügbar - Filter blockiert Entry");
|
||||
return false;
|
||||
}
|
||||
|
||||
double adx_value = adx_buf[0];
|
||||
double plus_di = plus_di_buf[0];
|
||||
double minus_di = minus_di_buf[0];
|
||||
|
||||
bool strength_ok = (adx_value >= WeeklyADXMin);
|
||||
bool direction_ok = true;
|
||||
if(WeeklyADXUseDirection)
|
||||
{
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
direction_ok = (plus_di > minus_di);
|
||||
else
|
||||
direction_ok = (minus_di > plus_di);
|
||||
}
|
||||
|
||||
Print("TRACE: Weekly ADX Filter | ADX=", DoubleToString(adx_value, 2),
|
||||
" +DI=", DoubleToString(plus_di, 2),
|
||||
" -DI=", DoubleToString(minus_di, 2),
|
||||
" strength_ok=", strength_ok,
|
||||
" direction_ok=", direction_ok);
|
||||
|
||||
return (strength_ok && direction_ok);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
//--- CTrade konfigurieren (Configure CTrade)
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(10);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
|
||||
//--- EMA Indicator Handle erstellen (Create EMA indicator handle)
|
||||
ema_handle = iMA(_Symbol, Timeframe, EMA_Periode, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(ema_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Fehler beim Erstellen des EMA Indicators");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
//--- Arrays initialisieren (Initialize arrays)
|
||||
ArraySetAsSeries(ema_array, true);
|
||||
|
||||
//--- Arrays mit aktuellen Werten füllen (Fill arrays with current values)
|
||||
BerechneEMA();
|
||||
|
||||
Print("EMA EA initialisiert - Periode: ", EMA_Periode, " Timeframe: ", EnumToString(Timeframe), " Handle: ", ema_handle);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
//--- Indicator Handle freigeben (Release indicator handle)
|
||||
if(ema_handle != INVALID_HANDLE)
|
||||
{
|
||||
IndicatorRelease(ema_handle);
|
||||
}
|
||||
|
||||
Print("EA beendet - Grund: ", reason);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
static datetime last_bar_time = 0;
|
||||
const datetime current_bar_time = iTime(_Symbol, Timeframe, 0);
|
||||
const bool new_bar = (current_bar_time != last_bar_time);
|
||||
const bool has_position = PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
|
||||
|
||||
// Offene Positionen: Management jeden Tick (Trailing / Stale-SL). Sonst bei Bar-Modus nur neuer Bar.
|
||||
if(UseBarData)
|
||||
{
|
||||
if(!new_bar && !has_position)
|
||||
return;
|
||||
if(new_bar)
|
||||
last_bar_time = current_bar_time;
|
||||
}
|
||||
|
||||
//--- EMA Werte berechnen (Calculate EMA values)
|
||||
BerechneEMA();
|
||||
|
||||
const bool run_signals = (!UseBarData || new_bar);
|
||||
|
||||
//--- Debug / Überwachung / Entry nur bei neuem Bar (Bar-Modus) oder jeden Tick (Tick-Modus)
|
||||
if(run_signals && ArraySize(ema_array) > 0)
|
||||
{
|
||||
double aktueller_close = iClose(_Symbol, Timeframe, 0);
|
||||
double ema_aktuell = ema_array[0];
|
||||
double ema_vorher = ema_array[1];
|
||||
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / _Point;
|
||||
double steigung = (ema_aktuell - ema_vorher) / _Point;
|
||||
|
||||
if(UseBarData)
|
||||
{
|
||||
Print("=== DEBUG INFO (Neuer Bar) ===");
|
||||
Print("Bar Zeit: ", TimeToString(iTime(_Symbol, Timeframe, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("=== DEBUG INFO (Tick) ===");
|
||||
}
|
||||
|
||||
Print("Aktueller Close: ", aktueller_close);
|
||||
Print("EMA: ", ema_aktuell);
|
||||
Print("Preis-Abstand: ", preis_abstand, " Pips");
|
||||
Print("EMA Steigung: ", steigung, " Pips");
|
||||
Print("Differenz Close-EMA: ", aktueller_close - ema_aktuell);
|
||||
Print("Preis-Trigger: ", preis_trigger_aktiv, " Steigungs-Trigger: ", steigung_trigger_aktiv);
|
||||
Print("Überwachung aktiv: ", überwachung_aktiv);
|
||||
Print("Position offen: ", PositionExistsByMagic(_Symbol, (ulong)MagicNumber));
|
||||
Print("Trades im aktuellen Crossover: ", trades_in_current_crossover, "/", MaxTradesPerCrossover);
|
||||
Print("==================");
|
||||
}
|
||||
|
||||
if(run_signals)
|
||||
{
|
||||
//--- Überwachung prüfen (Check monitoring)
|
||||
if(überwachung_aktiv)
|
||||
{
|
||||
if(UseBarData)
|
||||
{
|
||||
int bars_since_monitoring = iBarShift(_Symbol, Timeframe, letzte_überwachung_zeit);
|
||||
int timeout_bars = (int)(ÜberwachungTimeout / PeriodSeconds(Timeframe));
|
||||
|
||||
if(bars_since_monitoring > timeout_bars)
|
||||
{
|
||||
überwachung_aktiv = false;
|
||||
preis_trigger_aktiv = false;
|
||||
steigung_trigger_aktiv = false;
|
||||
Print("Überwachung beendet - Bar-basierte Zeitüberschreitung (", bars_since_monitoring, " Bars)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(TimeCurrent() - letzte_überwachung_zeit > ÜberwachungTimeout)
|
||||
{
|
||||
überwachung_aktiv = false;
|
||||
preis_trigger_aktiv = false;
|
||||
steigung_trigger_aktiv = false;
|
||||
Print("Überwachung beendet - Tick-basierte Zeitüberschreitung");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrüfeTrigger();
|
||||
}
|
||||
|
||||
VerwalteTrades();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMA Berechnung (EMA Calculation) |
|
||||
//+------------------------------------------------------------------+
|
||||
void BerechneEMA()
|
||||
{
|
||||
//--- EMA Werte vom Indicator kopieren (Copy EMA values from indicator)
|
||||
int copied = CopyBuffer(ema_handle, 0, 0, 3, ema_array);
|
||||
|
||||
if(copied <= 0)
|
||||
{
|
||||
Print("TRACE: Fehler beim Kopieren der EMA Werte - Copied: ", copied);
|
||||
return;
|
||||
}
|
||||
|
||||
Print("TRACE: EMA Werte kopiert: ", copied, " Bars");
|
||||
Print("TRACE: EMA [0]: ", ema_array[0], " [1]: ", ema_array[1], " [2]: ", ema_array[2]);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trigger-Bedingungen prüfen (Check trigger conditions) |
|
||||
//+------------------------------------------------------------------+
|
||||
void PrüfeTrigger()
|
||||
{
|
||||
if(ArraySize(ema_array) < 2)
|
||||
{
|
||||
Print("TRACE: Array zu klein - Größe: ", ArraySize(ema_array));
|
||||
return;
|
||||
}
|
||||
|
||||
//--- Aktuelle Werte (Current values)
|
||||
double aktueller_preis = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double aktueller_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double aktueller_close = iClose(_Symbol, Timeframe, 0);
|
||||
double pips_multiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
||||
|
||||
//--- EMA Werte in Variablen (EMA values in variables)
|
||||
double ema_aktuell = ema_array[0];
|
||||
double ema_vorher = ema_array[1];
|
||||
|
||||
//--- EMA Crossover Erkennung (EMA Crossover Detection)
|
||||
// Prüfe ob Preis die EMA kreuzt (Check if price crosses EMA)
|
||||
static double last_close = 0;
|
||||
static double last_ema = 0;
|
||||
|
||||
if(last_close != 0 && last_ema != 0)
|
||||
{
|
||||
bool crossover_bullish = (last_close <= last_ema) && (aktueller_close > ema_aktuell);
|
||||
bool crossover_bearish = (last_close >= last_ema) && (aktueller_close < ema_aktuell);
|
||||
|
||||
//--- Neues Crossover-Ereignis erkannt (New crossover event detected)
|
||||
if(crossover_bullish || crossover_bearish)
|
||||
{
|
||||
trades_in_current_crossover = 0; // Reset trade counter
|
||||
Print("TRACE: EMA Crossover erkannt - ", (crossover_bullish ? "BULLISH" : "BEARISH"), " - Trade-Counter zurückgesetzt");
|
||||
Print("TRACE: Vorher: Close=", last_close, " EMA=", last_ema, " Jetzt: Close=", aktueller_close, " EMA=", ema_aktuell);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Aktuelle Werte für nächsten Vergleich speichern (Save current values for next comparison)
|
||||
last_close = aktueller_close;
|
||||
last_ema = ema_aktuell;
|
||||
|
||||
//--- Preisbewegung zur EMA prüfen (Check price action to EMA)
|
||||
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / _Point / pips_multiplier;
|
||||
|
||||
Print("TRACE: Preis-Abstand: ", preis_abstand, " Pips (Schwelle: ", PreisSchwelle, ")");
|
||||
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
Print("TRACE: Trades im aktuellen Crossover: ", trades_in_current_crossover, "/", MaxTradesPerCrossover);
|
||||
|
||||
if(preis_abstand > PreisSchwelle && !preis_trigger_aktiv)
|
||||
{
|
||||
preis_trigger_aktiv = true;
|
||||
Print("TRACE: Preis-Trigger aktiviert: ", preis_abstand, " Pips");
|
||||
}
|
||||
|
||||
//--- EMA Steigung prüfen (Check EMA slope)
|
||||
double steigung = (ema_aktuell - ema_vorher) / _Point / pips_multiplier;
|
||||
|
||||
Print("TRACE: EMA Steigung: ", steigung, " Pips (Schwelle: ", SteigungSchwelle, ")");
|
||||
|
||||
if(MathAbs(steigung) > SteigungSchwelle && !steigung_trigger_aktiv)
|
||||
{
|
||||
steigung_trigger_aktiv = true;
|
||||
Print("TRACE: Steigungs-Trigger aktiviert: ", steigung, " Pips");
|
||||
}
|
||||
|
||||
//--- Überwachung starten wenn beide Trigger aktiv sind (Start monitoring when both triggers are active)
|
||||
if(preis_trigger_aktiv && steigung_trigger_aktiv && !überwachung_aktiv)
|
||||
{
|
||||
überwachung_aktiv = true;
|
||||
|
||||
if(UseBarData)
|
||||
{
|
||||
letzte_überwachung_zeit = iTime(_Symbol, Timeframe, 0); // Aktuelle Bar-Zeit
|
||||
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Bar: ", TimeToString(letzte_überwachung_zeit), ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
letzte_überwachung_zeit = TimeCurrent(); // Aktuelle Tick-Zeit
|
||||
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Tick)");
|
||||
}
|
||||
}
|
||||
|
||||
//--- Trade platzieren wenn Überwachung aktiv und Preis über/unter EMA (Place trade when monitoring active and price above/below EMA)
|
||||
if(überwachung_aktiv)
|
||||
{
|
||||
bool bullish_signal = aktueller_close > ema_aktuell;
|
||||
bool bearish_signal = aktueller_close < ema_aktuell;
|
||||
|
||||
Print("TRACE: Signal Check - Bullish: ", bullish_signal, " Bearish: ", bearish_signal);
|
||||
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
Print("TRACE: Differenz: ", aktueller_close - ema_aktuell);
|
||||
|
||||
//--- Trade-Limit prüfen (Check trade limit)
|
||||
if(trades_in_current_crossover >= MaxTradesPerCrossover)
|
||||
{
|
||||
Print("TRACE: Trade-Limit erreicht (", MaxTradesPerCrossover, ") - Kein neuer Trade");
|
||||
return;
|
||||
}
|
||||
|
||||
if(bullish_signal && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
if(!IsWeeklyADXTrendFavorable(ORDER_TYPE_BUY))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert BUY-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_BUY))
|
||||
{
|
||||
trades_in_current_crossover++;
|
||||
}
|
||||
}
|
||||
else if(bearish_signal && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
if(!IsWeeklyADXTrendFavorable(ORDER_TYPE_SELL))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert SELL-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_SELL))
|
||||
{
|
||||
trades_in_current_crossover++;
|
||||
}
|
||||
}
|
||||
else if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
Print("TRACE: Position bereits offen - kein neuer Trade");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trade platzieren (Place trade) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PlatziereTrade(ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
Print("TRACE: Versuche Trade zu platzieren - Typ: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF");
|
||||
Print("TRACE: Lot: ", LotGröße);
|
||||
|
||||
bool success = false;
|
||||
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
{
|
||||
success = trade.Buy(LotGröße, _Symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
else
|
||||
{
|
||||
success = trade.Sell(LotGröße, _Symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
|
||||
if(success)
|
||||
{
|
||||
ticket = (int)trade.ResultOrder();
|
||||
Print("TRACE: Trade erfolgreich platziert: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF", " Ticket: ", ticket);
|
||||
|
||||
//--- Trade-Öffnungszeit speichern (Save trade opening time)
|
||||
trade_open_time = iTime(_Symbol, Timeframe, 0);
|
||||
g_last_sl_adjust_success_time = 0;
|
||||
Print("TRACE: Trade-Öffnungszeit: ", TimeToString(trade_open_time));
|
||||
|
||||
//--- Überwachung zurücksetzen (Reset monitoring)
|
||||
überwachung_aktiv = false;
|
||||
preis_trigger_aktiv = false;
|
||||
steigung_trigger_aktiv = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Platzieren des Trades - Retcode: ", trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Mindestgewinn fuer Trailing erreicht? |
|
||||
//+------------------------------------------------------------------+
|
||||
bool TrailingActivationReached(const double position_profit, const ENUM_POSITION_TYPE position_type,
|
||||
const double pips_multiplier)
|
||||
{
|
||||
if(TrailingActivationPips <= 0.0)
|
||||
return (position_profit > 0.0);
|
||||
|
||||
const double open_px = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
return ((bid - open_px) / _Point / pips_multiplier >= TrailingActivationPips);
|
||||
}
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
return ((open_px - ask) / _Point / pips_multiplier >= TrailingActivationPips);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trades verwalten (Manage trades) |
|
||||
//+------------------------------------------------------------------+
|
||||
void VerwalteTrades()
|
||||
{
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
if(UseStaleStopLossExit && StaleStopLossSeconds > 0)
|
||||
{
|
||||
const datetime stale_ref = (g_last_sl_adjust_success_time > 0)
|
||||
? g_last_sl_adjust_success_time
|
||||
: (datetime)PositionGetInteger(POSITION_TIME);
|
||||
if(TimeCurrent() - stale_ref >= StaleStopLossSeconds)
|
||||
{
|
||||
SchließePosition("Stale stop loss - keine SL-Anpassung");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
double pips_multiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
||||
const double trail_dist = TrailingStop * _Point * pips_multiplier;
|
||||
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
const long stops_level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * _Point;
|
||||
|
||||
//--- Gleitender Stop (Trailing Stop)
|
||||
if(UseTrailingStop && TrailingStop > 0.0 && TrailingActivationReached(position_profit, position_type, pips_multiplier))
|
||||
{
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double new_stop_loss = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_stop_loss < min_dist)
|
||||
new_stop_loss = NormalizeDouble(bid - min_dist, digits);
|
||||
const double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
if(new_stop_loss < bid && new_stop_loss > 0.0 && new_stop_loss > current_stop_loss)
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double new_stop_loss = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_stop_loss - ask < min_dist)
|
||||
new_stop_loss = NormalizeDouble(ask + min_dist, digits);
|
||||
const double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
if(new_stop_loss > ask && new_stop_loss > 0.0 &&
|
||||
(new_stop_loss < current_stop_loss || current_stop_loss == 0.0))
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Ausstieg bei Preis unter/über EMA (Exit when price below/above EMA)
|
||||
if(ArraySize(ema_array) >= 1)
|
||||
{
|
||||
double aktueller_close = iClose(_Symbol, Timeframe, 0);
|
||||
double ema_aktuell = ema_array[0];
|
||||
bool exit_bullish = (position_type == POSITION_TYPE_SELL && aktueller_close > ema_aktuell);
|
||||
bool exit_bearish = (position_type == POSITION_TYPE_BUY && aktueller_close < ema_aktuell);
|
||||
|
||||
if(exit_bullish || exit_bearish)
|
||||
{
|
||||
Print("TRACE: Ausstiegssignal - Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
SchließePosition("EMA Crossover Exit");
|
||||
|
||||
Print("TRACE: Position geschlossen - Trade-Counter bleibt bei ", trades_in_current_crossover);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Profit-Prüfung nach X Bars (Profit check after X bars)
|
||||
if(CloseUnprofitableTrades && trade_open_time != 0 && PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
Print("TRACE: Profit-Prüfung aktiviert - CloseUnprofitableTrades: ", CloseUnprofitableTrades);
|
||||
PrüfeProfitNachBars();
|
||||
}
|
||||
else if(!CloseUnprofitableTrades)
|
||||
{
|
||||
Print("TRACE: Profit-Prüfung deaktiviert - CloseUnprofitableTrades: ", CloseUnprofitableTrades);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Profit-Prüfung nach X Bars (Profit check after X bars) |
|
||||
//+------------------------------------------------------------------+
|
||||
void PrüfeProfitNachBars()
|
||||
{
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Keine Position offen
|
||||
}
|
||||
|
||||
datetime current_bar_time = iTime(_Symbol, Timeframe, 0);
|
||||
int bars_since_trade_open = iBarShift(_Symbol, Timeframe, trade_open_time);
|
||||
|
||||
Print("TRACE: Bars seit Trade-Öffnung: ", bars_since_trade_open, "/", ProfitCheckBars);
|
||||
|
||||
//--- Prüfe ob genügend Bars vergangen sind (Check if enough bars have passed)
|
||||
if(bars_since_trade_open >= ProfitCheckBars)
|
||||
{
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
double position_volume = PositionGetDouble(POSITION_VOLUME);
|
||||
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
Print("TRACE: Profit-Prüfung nach ", ProfitCheckBars, " Bars");
|
||||
Print("TRACE: Position Profit: ", position_profit, " USD");
|
||||
|
||||
//--- Schließe Position wenn nicht im Profit (Close position if not in profit)
|
||||
if(position_profit <= 0)
|
||||
{
|
||||
Print("TRACE: Position nicht im Profit - Schließe Position");
|
||||
SchließePosition("Profit Check - Unprofitable");
|
||||
|
||||
//--- Trade-Öffnungszeit zurücksetzen (Reset trade opening time)
|
||||
trade_open_time = 0;
|
||||
Print("TRACE: Trade-Öffnungszeit zurückgesetzt");
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Position im Profit - Behalte Position");
|
||||
//--- Trade-Öffnungszeit zurücksetzen um weitere Prüfungen zu vermeiden (Reset to avoid further checks)
|
||||
trade_open_time = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Stop Loss ändern (Modify Stop Loss) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ÄndereStopLoss(double new_stop_loss)
|
||||
{
|
||||
Print("TRACE: Versuche Stop Loss zu ändern auf: ", new_stop_loss);
|
||||
|
||||
bool success = ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_stop_loss, PositionGetDouble(POSITION_TP));
|
||||
|
||||
if(success)
|
||||
{
|
||||
g_last_sl_adjust_success_time = TimeCurrent();
|
||||
Print("TRACE: Stop Loss erfolgreich geändert auf: ", new_stop_loss);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Ändern des Stop Loss - Retcode: ", trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Position schließen (Close position) |
|
||||
//+------------------------------------------------------------------+
|
||||
void SchließePosition(string reason = "Unbekannt")
|
||||
{
|
||||
Print("TRACE: Versuche Position zu schließen - Grund: ", reason);
|
||||
|
||||
bool success = ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber);
|
||||
|
||||
if(success)
|
||||
{
|
||||
g_last_sl_adjust_success_time = 0;
|
||||
Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Schließen der Position - Retcode: ", trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,347 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIConsolidation.mq5 |
|
||||
//| Mean-reversion RSI for ranging markets; trend filters block runs |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025"
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
//--- Symbol (empty = chart symbol)
|
||||
input group "=== Symbol & session ==="
|
||||
input string InpSymbol = "";
|
||||
|
||||
input group "=== Timeframe & bar logic ==="
|
||||
input ENUM_TIMEFRAMES SignalTF = PERIOD_M15;
|
||||
input bool EntryOnNewBarOnly = true;
|
||||
|
||||
//--- Core: no trend / consolidation regime
|
||||
input group "=== Regime: consolidation (anti-trend) ==="
|
||||
input int ADX_Period = 23;
|
||||
input double ADX_Max = 29.0;
|
||||
input bool UseATRRatioFilter = true;
|
||||
input int ATR_Period = 8;
|
||||
input int ATR_SMA_Period = 35;
|
||||
input double ATR_Ratio_Max = 1.36;
|
||||
input bool UseFlatEMAFilter = true;
|
||||
input int EMA_Fast = 13;
|
||||
input int EMA_Slow = 17;
|
||||
input double EMA_Separation_MaxPct = 0.26;
|
||||
|
||||
//--- RSI entries (fade extremes toward mean)
|
||||
input group "=== RSI entries ==="
|
||||
input int RSI_Period = 8;
|
||||
input ENUM_APPLIED_PRICE RSI_Price = PRICE_OPEN;
|
||||
input double RSI_Oversold = 22.0;
|
||||
input double RSI_Overbought = 63.0;
|
||||
|
||||
//--- Exits: mean target + hard ATR bracket
|
||||
input group "=== Exits ==="
|
||||
input bool UseRSI_MeanExit = true;
|
||||
input double RSI_Exit_Long = 48.0;
|
||||
input double RSI_Exit_Short = 52.0;
|
||||
input double SL_ATR_Mult = 2.15;
|
||||
input double TP_ATR_Mult = 2.40;
|
||||
input int MaxBarsInTrade = 54;
|
||||
|
||||
input group "=== Risk & execution ==="
|
||||
input double Lots = 0.10;
|
||||
input ulong MagicNumber = 20250420;
|
||||
input int Slippage = 10;
|
||||
input int MaxSpreadPoints = 28;
|
||||
|
||||
CTrade trade;
|
||||
string g_sym;
|
||||
|
||||
int h_rsi = INVALID_HANDLE;
|
||||
int h_adx = INVALID_HANDLE;
|
||||
int h_atr = INVALID_HANDLE;
|
||||
int h_ema_fast = INVALID_HANDLE;
|
||||
int h_ema_slow = INVALID_HANDLE;
|
||||
|
||||
datetime g_last_bar = 0;
|
||||
|
||||
bool PositionExistsByMagicSym(string sym, ulong magic)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == (long)magic)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong GetPositionTicketByMagicSym(string sym, ulong magic)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0) continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == (long)magic)
|
||||
return t;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SelectPositionTicketSymMagic(ulong ticket, string sym, ulong magic)
|
||||
{
|
||||
if(!PositionSelectByTicket(ticket)) return false;
|
||||
return PositionGetString(POSITION_SYMBOL) == sym && PositionGetInteger(POSITION_MAGIC) == (long)magic;
|
||||
}
|
||||
|
||||
double NormalizeVolume(string sym, double vol)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
||||
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
||||
if(step > 0.0)
|
||||
vol = MathFloor(vol / step) * step;
|
||||
if(vol < minLot) vol = minLot;
|
||||
if(vol > maxLot) vol = maxLot;
|
||||
return vol;
|
||||
}
|
||||
|
||||
int CurrentSpreadPoints(string sym)
|
||||
{
|
||||
long spread = 0;
|
||||
if(!SymbolInfoInteger(sym, SYMBOL_SPREAD, spread))
|
||||
return 999999;
|
||||
return (int)spread;
|
||||
}
|
||||
|
||||
double MinStopsDistancePrice(string sym)
|
||||
{
|
||||
long lvl = 0;
|
||||
if(!SymbolInfoInteger(sym, SYMBOL_TRADE_STOPS_LEVEL, lvl))
|
||||
return 0;
|
||||
double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
|
||||
if(pt <= 0)
|
||||
return 0;
|
||||
return (double)lvl * pt;
|
||||
}
|
||||
|
||||
bool Copy1(int handle, double &v)
|
||||
{
|
||||
double b[];
|
||||
ArraySetAsSeries(b, true);
|
||||
if(CopyBuffer(handle, 0, 0, 1, b) < 1) return false;
|
||||
v = b[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RSI_Buffers(double &cur, double &prev, double &twoAgo)
|
||||
{
|
||||
double b[];
|
||||
ArraySetAsSeries(b, true);
|
||||
if(CopyBuffer(h_rsi, 0, 0, 3, b) < 3) return false;
|
||||
cur = b[0];
|
||||
prev = b[1];
|
||||
twoAgo = b[2];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Regime_IsConsolidation()
|
||||
{
|
||||
double adx = 0;
|
||||
if(!Copy1(h_adx, adx))
|
||||
return false;
|
||||
if(adx >= ADX_Max)
|
||||
return false;
|
||||
|
||||
if(UseATRRatioFilter)
|
||||
{
|
||||
double atrArr[], atrSma[];
|
||||
ArraySetAsSeries(atrArr, true);
|
||||
if(CopyBuffer(h_atr, 0, 0, ATR_SMA_Period + 1, atrArr) < ATR_SMA_Period + 1)
|
||||
return false;
|
||||
double sum = 0;
|
||||
for(int i = 1; i <= ATR_SMA_Period; i++)
|
||||
sum += atrArr[i];
|
||||
double smaAtr = sum / (double)ATR_SMA_Period;
|
||||
if(smaAtr <= 0.0)
|
||||
return false;
|
||||
double ratio = atrArr[0] / smaAtr;
|
||||
if(ratio > ATR_Ratio_Max)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(UseFlatEMAFilter)
|
||||
{
|
||||
double ef[], es[];
|
||||
ArraySetAsSeries(ef, true);
|
||||
ArraySetAsSeries(es, true);
|
||||
if(CopyBuffer(h_ema_fast, 0, 0, 1, ef) < 1) return false;
|
||||
if(CopyBuffer(h_ema_slow, 0, 0, 1, es) < 1) return false;
|
||||
double c = SymbolInfoDouble(g_sym, SYMBOL_BID);
|
||||
if(c <= 0) return false;
|
||||
double sep = MathAbs(ef[0] - es[0]) / c * 100.0;
|
||||
if(sep > EMA_Separation_MaxPct)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Entry_BuyCross(double twoAgo, double prev)
|
||||
{
|
||||
return (twoAgo <= RSI_Oversold && prev > RSI_Oversold);
|
||||
}
|
||||
|
||||
bool Entry_SellCross(double twoAgo, double prev)
|
||||
{
|
||||
return (twoAgo >= RSI_Overbought && prev < RSI_Overbought);
|
||||
}
|
||||
|
||||
void TryCloseByRSI(ENUM_POSITION_TYPE typ, double rsi)
|
||||
{
|
||||
ulong tk = GetPositionTicketByMagicSym(g_sym, MagicNumber);
|
||||
if(tk == 0 || !SelectPositionTicketSymMagic(tk, g_sym, MagicNumber))
|
||||
return;
|
||||
if(!UseRSI_MeanExit)
|
||||
return;
|
||||
if(typ == POSITION_TYPE_BUY && rsi >= RSI_Exit_Long)
|
||||
trade.PositionClose(tk);
|
||||
else if(typ == POSITION_TYPE_SELL && rsi <= RSI_Exit_Short)
|
||||
trade.PositionClose(tk);
|
||||
}
|
||||
|
||||
void ManageOpenPosition(double rsi)
|
||||
{
|
||||
ulong tk = GetPositionTicketByMagicSym(g_sym, MagicNumber);
|
||||
if(tk == 0 || !SelectPositionTicketSymMagic(tk, g_sym, MagicNumber))
|
||||
return;
|
||||
ENUM_POSITION_TYPE typ = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
datetime openT = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
int barsAgo = iBarShift(g_sym, SignalTF, openT, false);
|
||||
if(barsAgo >= 0 && barsAgo >= MaxBarsInTrade)
|
||||
{
|
||||
trade.PositionClose(tk);
|
||||
return;
|
||||
}
|
||||
TryCloseByRSI(typ, rsi);
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_sym = InpSymbol;
|
||||
StringTrimLeft(g_sym);
|
||||
StringTrimRight(g_sym);
|
||||
if(StringLen(g_sym) == 0)
|
||||
g_sym = _Symbol;
|
||||
|
||||
if(!SymbolSelect(g_sym, true))
|
||||
{
|
||||
Print("RSIConsolidation: SymbolSelect failed: ", g_sym);
|
||||
return INIT_FAILED;
|
||||
}
|
||||
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||||
|
||||
h_rsi = iRSI(g_sym, SignalTF, RSI_Period, RSI_Price);
|
||||
h_adx = iADX(g_sym, SignalTF, ADX_Period);
|
||||
h_atr = iATR(g_sym, SignalTF, ATR_Period);
|
||||
h_ema_fast = iMA(g_sym, SignalTF, EMA_Fast, 0, MODE_EMA, PRICE_CLOSE);
|
||||
h_ema_slow = iMA(g_sym, SignalTF, EMA_Slow, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(h_rsi == INVALID_HANDLE || h_adx == INVALID_HANDLE || h_atr == INVALID_HANDLE
|
||||
|| h_ema_fast == INVALID_HANDLE || h_ema_slow == INVALID_HANDLE)
|
||||
{
|
||||
Print("RSIConsolidation: indicator init failed");
|
||||
return INIT_FAILED;
|
||||
}
|
||||
|
||||
Print("RSIConsolidation: symbol=", g_sym, " TF=", EnumToString(SignalTF));
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(h_rsi != INVALID_HANDLE) IndicatorRelease(h_rsi);
|
||||
if(h_adx != INVALID_HANDLE) IndicatorRelease(h_adx);
|
||||
if(h_atr != INVALID_HANDLE) IndicatorRelease(h_atr);
|
||||
if(h_ema_fast != INVALID_HANDLE) IndicatorRelease(h_ema_fast);
|
||||
if(h_ema_slow != INVALID_HANDLE) IndicatorRelease(h_ema_slow);
|
||||
}
|
||||
|
||||
bool EnoughHistory()
|
||||
{
|
||||
int need = MathMax(RSI_Period + 3, MathMax(ADX_Period + 2, ATR_SMA_Period + 3));
|
||||
if(Bars(g_sym, SignalTF) < need)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(!EnoughHistory())
|
||||
return;
|
||||
|
||||
if(MaxSpreadPoints > 0 && CurrentSpreadPoints(g_sym) > MaxSpreadPoints)
|
||||
return;
|
||||
|
||||
double rsi, rsiPrev, rsi2;
|
||||
if(!RSI_Buffers(rsi, rsiPrev, rsi2))
|
||||
return;
|
||||
|
||||
datetime barTime = iTime(g_sym, SignalTF, 0);
|
||||
bool isNew = (barTime != g_last_bar);
|
||||
|
||||
if(PositionExistsByMagicSym(g_sym, MagicNumber))
|
||||
{
|
||||
ManageOpenPosition(rsi);
|
||||
if(isNew)
|
||||
g_last_bar = barTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if(EntryOnNewBarOnly && !isNew)
|
||||
return;
|
||||
|
||||
g_last_bar = barTime;
|
||||
|
||||
if(!Regime_IsConsolidation())
|
||||
return;
|
||||
|
||||
double atrArr[];
|
||||
ArraySetAsSeries(atrArr, true);
|
||||
if(CopyBuffer(h_atr, 0, 0, 1, atrArr) < 1)
|
||||
return;
|
||||
double atr = atrArr[0];
|
||||
int dig = (int)SymbolInfoInteger(g_sym, SYMBOL_DIGITS);
|
||||
|
||||
double slDist = atr * SL_ATR_Mult;
|
||||
double tpDist = atr * TP_ATR_Mult;
|
||||
double minD = MinStopsDistancePrice(g_sym);
|
||||
if(slDist < minD)
|
||||
slDist = minD;
|
||||
if(tpDist < minD)
|
||||
tpDist = minD;
|
||||
|
||||
double vol = NormalizeVolume(g_sym, Lots);
|
||||
|
||||
if(Entry_BuyCross(rsi2, rsiPrev))
|
||||
{
|
||||
double ask = SymbolInfoDouble(g_sym, SYMBOL_ASK);
|
||||
double sl = ask - slDist;
|
||||
double tp = ask + tpDist;
|
||||
sl = NormalizeDouble(sl, dig);
|
||||
tp = NormalizeDouble(tp, dig);
|
||||
trade.Buy(vol, g_sym, ask, sl, tp, "RSIConsolidation BUY");
|
||||
}
|
||||
else if(Entry_SellCross(rsi2, rsiPrev))
|
||||
{
|
||||
double bid = SymbolInfoDouble(g_sym, SYMBOL_BID);
|
||||
double sl = bid + slDist;
|
||||
double tp = bid - tpDist;
|
||||
sl = NormalizeDouble(sl, dig);
|
||||
tp = NormalizeDouble(tp, dig);
|
||||
trade.Sell(vol, g_sym, bid, sl, tp, "RSIConsolidation SELL");
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,37 @@
|
||||
; RSIConsolidation.mq5 — optimization preset (Strategy Tester → Inputs → Load)
|
||||
; Format: Name=Current||Start||Step||Stop||Y|N (Y = include in optimization)
|
||||
;
|
||||
; === Symbol & session ===
|
||||
InpSymbol=
|
||||
; === Timeframe & bar logic ===
|
||||
; SignalTF: optimize per run (ENUM is non-sequential); M15=15, H1=16385, H4=16388
|
||||
SignalTF=15||15||0||15||N
|
||||
EntryOnNewBarOnly=true||false||0||true||N
|
||||
; === Regime: consolidation (anti-trend) ===
|
||||
ADX_Period=14||7||1||28||Y
|
||||
ADX_Max=22.0||16.0||1.0||32.0||Y
|
||||
UseATRRatioFilter=true||false||0||true||N
|
||||
ATR_Period=14||7||1||21||Y
|
||||
ATR_SMA_Period=50||20||5||100||Y
|
||||
ATR_Ratio_Max=1.18||1.0||0.02||1.35||Y
|
||||
UseFlatEMAFilter=true||false||0||true||N
|
||||
EMA_Fast=8||5||1||13||Y
|
||||
EMA_Slow=21||13||2||34||Y
|
||||
EMA_Separation_MaxPct=0.22||0.08||0.02||0.45||Y
|
||||
; === RSI entries ===
|
||||
RSI_Period=14||7||1||21||Y
|
||||
RSI_Price=1||1||1||7||Y
|
||||
RSI_Oversold=32.0||22.0||1.0||42.0||Y
|
||||
RSI_Overbought=68.0||58.0||1.0||78.0||Y
|
||||
; === Exits ===
|
||||
UseRSI_MeanExit=true||false||0||true||N
|
||||
RSI_Exit_Long=52.0||48.0||1.0||62.0||Y
|
||||
RSI_Exit_Short=48.0||38.0||1.0||52.0||Y
|
||||
SL_ATR_Mult=1.35||0.9||0.05||2.2||Y
|
||||
TP_ATR_Mult=1.85||1.0||0.05||3.0||Y
|
||||
MaxBarsInTrade=36||12||2||80||Y
|
||||
; === Risk & execution ===
|
||||
Lots=0.1||0.1||0.01||1.0||N
|
||||
MagicNumber=20250420||20250420||1||20250420||N
|
||||
Slippage=10||10||1||100||N
|
||||
MaxSpreadPoints=0||0||1||30||Y
|
||||
@@ -0,0 +1,295 @@
|
||||
// Input Parameters
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
input group "Trade Management"
|
||||
input int MagicNumber = 7;
|
||||
input int rsiPeriod = 19; // RSI period
|
||||
input int overboughtLevel = 93; // Overbought level (RSI > 70 for sell)
|
||||
input int oversoldLevel = 22; // Oversold level (RSI < 30 for buy)
|
||||
input double entryRSIBuySpread = 0;
|
||||
input double entryRSISellSpread = 0;
|
||||
input double lotSize = 0.1; // Trade lot size
|
||||
input int slippage = 3; // Slippage for orders
|
||||
input int cooldownSeconds = 209; // Cooldown period in seconds
|
||||
input ENUM_TIMEFRAMES TimeFrame1 = PERIOD_M1; // RSI Timeframe
|
||||
input ENUM_TIMEFRAMES TimeFrame2 = PERIOD_M1; // EMA Timeframe
|
||||
input ENUM_TIMEFRAMES BarTimeFrame = PERIOD_M12; // EMA Timeframe
|
||||
input int emaPeriod = 140; // EMA period
|
||||
input double emaSlopeThreshold = 105; // EMA slope threshold for trend strength
|
||||
input double exitBuyRSI = 86;
|
||||
input double exitSellRSI = 10;
|
||||
input double TrailingStop = 295;
|
||||
input double emaDistanceThreshold = 165;
|
||||
input int tradingHourOneBegin = 24;
|
||||
input int tradingHourOneEnd = 22;
|
||||
input int tradingHourTwoBegin = 6;
|
||||
input int tradingHourTwoEnd = 19;
|
||||
datetime bartime;
|
||||
// RSI Handle
|
||||
int rsiHandle;
|
||||
|
||||
input bool Sunday =false; // Sunday
|
||||
input bool Monday =false; // Monday
|
||||
input bool Tuesday =true; // Tuesday
|
||||
input bool Wednesday=true; // Wednesday
|
||||
input bool Thursday =true; // Thursday
|
||||
input bool Friday =false; // Friday
|
||||
input bool Saturday =false; // Saturday
|
||||
|
||||
bool WeekDays[7];
|
||||
|
||||
void WeekDays_Init()
|
||||
{
|
||||
WeekDays[0]=Sunday;
|
||||
WeekDays[1]=Monday;
|
||||
WeekDays[2]=Tuesday;
|
||||
WeekDays[3]=Wednesday;
|
||||
WeekDays[4]=Thursday;
|
||||
WeekDays[5]=Friday;
|
||||
WeekDays[6]=Saturday;
|
||||
}
|
||||
|
||||
bool WeekDays_Check(datetime aTime)
|
||||
{
|
||||
MqlDateTime stm;
|
||||
TimeToStruct(aTime,stm);
|
||||
return(WeekDays[stm.day_of_week]);
|
||||
}
|
||||
|
||||
|
||||
// EMA Handle
|
||||
int emaHandle;
|
||||
double previousRSIDef = 0;
|
||||
// Create CTrade object for executing trades
|
||||
CTrade trade;
|
||||
|
||||
// Track the last trade time
|
||||
datetime lastTradeTime = 0;
|
||||
|
||||
void OnInit() {
|
||||
WeekDays_Init();
|
||||
|
||||
// Create RSI handle
|
||||
rsiHandle = iRSI(_Symbol, TimeFrame1, rsiPeriod, PRICE_CLOSE);
|
||||
if (rsiHandle == INVALID_HANDLE) {
|
||||
Print("Error creating RSI handle: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Create EMA handle
|
||||
emaHandle = iMA(_Symbol, TimeFrame2, emaPeriod, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if (emaHandle == INVALID_HANDLE) {
|
||||
Print("Error creating EMA handle: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialization successful
|
||||
Print("RSI and EMA Reversal Strategy Initialized.");
|
||||
}
|
||||
|
||||
void OnTick() {
|
||||
if(bartime==iTime(_Symbol,BarTimeFrame,0))return;
|
||||
bartime=iTime(_Symbol,BarTimeFrame,0);
|
||||
|
||||
// Check if RSI data is available
|
||||
double rsi[];
|
||||
if (CopyBuffer(rsiHandle, 0, 0, 2, rsi) <= 0) {
|
||||
Print("Error copying RSI data: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if EMA data is available
|
||||
double ema[];
|
||||
if (CopyBuffer(emaHandle, 0, 0, 2, ema) <= 0) {
|
||||
Print("Error copying EMA data: ", GetLastError());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current time
|
||||
datetime currentTime = TimeCurrent();
|
||||
|
||||
|
||||
int currentHour = TimeHour(TimeCurrent());
|
||||
|
||||
if(!WeekDays_Check(TimeTradeServer())) {
|
||||
Close_Position_MN(MagicNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(currentHour < tradingHourOneEnd && currentHour > tradingHourOneBegin || currentHour < tradingHourTwoEnd && currentHour > tradingHourTwoBegin))
|
||||
{
|
||||
|
||||
Close_Position_MN(MagicNumber);
|
||||
return; // Prevent further trading during this time
|
||||
}
|
||||
|
||||
|
||||
// Ensure there is at least one position
|
||||
bool hasPosition = PositionExistsByMagic(_Symbol, MagicNumber);
|
||||
|
||||
|
||||
|
||||
// Get the current and previous RSI values
|
||||
double currentRSI = rsi[0];
|
||||
double previousRSI = rsi[1];
|
||||
|
||||
if(previousRSIDef == 0) {
|
||||
previousRSIDef = currentRSI;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current and previous EMA values
|
||||
double currentEMA = ema[0];
|
||||
double previousEMA = ema[1];
|
||||
|
||||
// Calculate the EMA slope (difference between current and previous EMA values)
|
||||
double emaSlope = (currentEMA - previousEMA) * 100;
|
||||
Print(emaSlope);
|
||||
|
||||
double closeCurr = iClose(Symbol(), Period(), 0); // Close of current bar
|
||||
// ** NEW CODE: Calculate distance to EMA and adjust score **
|
||||
double priceToEmaDistance = (closeCurr - currentEMA) * 10; // Distance between the current price and the EMA
|
||||
Print("priceToEmaDistance");
|
||||
Print(priceToEmaDistance);
|
||||
|
||||
|
||||
// Determine if there are existing buy or sell positions
|
||||
bool isBuyPosition = false;
|
||||
bool isSellPosition = false;
|
||||
if (hasPosition) {
|
||||
if (PositionSelectByMagic(_Symbol, MagicNumber)) {
|
||||
int positionType = PositionGetInteger(POSITION_TYPE);
|
||||
if (positionType == POSITION_TYPE_BUY) {
|
||||
isBuyPosition = true;
|
||||
} else if (positionType == POSITION_TYPE_SELL) {
|
||||
isSellPosition = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplyTrailingStop();
|
||||
|
||||
// Check if the cooldown period has elapsed since the last trade
|
||||
bool cooldownPassed = (currentTime - lastTradeTime) >= cooldownSeconds;
|
||||
|
||||
// Check if EMA slope is above the threshold (indicating strong trend)
|
||||
bool isTrendStrong = MathAbs(emaSlope) > emaSlopeThreshold || MathAbs(priceToEmaDistance) > emaDistanceThreshold;
|
||||
|
||||
// Close trade logic when RSI crosses 50
|
||||
if (isBuyPosition && currentRSI > exitBuyRSI) {
|
||||
// Close buy position
|
||||
Close_Position_MN(MagicNumber);
|
||||
lastTradeTime = currentTime; // Update last trade time
|
||||
}
|
||||
|
||||
if (isSellPosition && currentRSI < exitSellRSI) {
|
||||
Close_Position_MN(MagicNumber);
|
||||
lastTradeTime = currentTime; // Update last trade time
|
||||
|
||||
}
|
||||
|
||||
|
||||
// If the EMA slope is strong, do not place new trades
|
||||
if (isTrendStrong) {
|
||||
Close_Position_MN(MagicNumber);
|
||||
lastTradeTime = currentTime; // Update last trade time
|
||||
Print("Strong trend detected (EMA slope), skipping new trade.");
|
||||
return;
|
||||
}
|
||||
|
||||
// SELL logic (RSI crosses over the overbought level)
|
||||
if (currentRSI < overboughtLevel - entryRSISellSpread && previousRSIDef >= overboughtLevel && !isSellPosition && !hasPosition && cooldownPassed) {
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
if (trade.Sell(lotSize, _Symbol, 0, 0, "Sell Order")) {
|
||||
Print("Sell order placed.");
|
||||
lastTradeTime = currentTime; // Update last trade time
|
||||
} else {
|
||||
Print("Error placing sell order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// BUY logic (RSI crosses below the oversold level)
|
||||
if (currentRSI > oversoldLevel + entryRSIBuySpread && previousRSIDef <= oversoldLevel && !isBuyPosition && !hasPosition && cooldownPassed) {
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
if (trade.Buy(lotSize, _Symbol, 0, 0, "Buy Order")) {
|
||||
Print("Buy order placed.");
|
||||
lastTradeTime = currentTime; // Update last trade time
|
||||
} else {
|
||||
Print("Error placing buy order: ", GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
previousRSIDef = currentRSI;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason) {
|
||||
// Release RSI and EMA handles on deinitialization
|
||||
if (rsiHandle != INVALID_HANDLE) {
|
||||
IndicatorRelease(rsiHandle);
|
||||
Print("RSI handle released.");
|
||||
}
|
||||
if (emaHandle != INVALID_HANDLE) {
|
||||
IndicatorRelease(emaHandle);
|
||||
Print("EMA handle released.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Close_Position_MN(ulong magicNumber)
|
||||
{
|
||||
// Use helper function to close position by magic number
|
||||
ClosePositionByMagic(trade, _Symbol, (int)magicNumber);
|
||||
}
|
||||
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
Print("Scanning for trailing stop");
|
||||
|
||||
// Check if position exists with our magic number
|
||||
if(!PositionSelectByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
return; // No position with our magic number
|
||||
}
|
||||
|
||||
ulong PositionTicket = PositionGetInteger(POSITION_TICKET);
|
||||
long trade_type = PositionGetInteger(POSITION_TYPE);
|
||||
string symbol = _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 * TrailingStop, DIGIT))
|
||||
{
|
||||
if(PositionGetDouble(POSITION_SL) < NormalizeDouble(Bid - POINT * TrailingStop, DIGIT))
|
||||
{
|
||||
ModifyPositionByMagic(trade, symbol, MagicNumber,
|
||||
NormalizeDouble(Bid - POINT * 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 * TrailingStop, DIGIT))
|
||||
{
|
||||
if((PositionGetDouble(POSITION_SL) > NormalizeDouble(Ask + POINT * TrailingStop, DIGIT)) ||
|
||||
(PositionGetDouble(POSITION_SL) == 0))
|
||||
{
|
||||
ModifyPositionByMagic(trade, symbol, MagicNumber,
|
||||
NormalizeDouble(Ask + POINT * TrailingStop, DIGIT),
|
||||
PositionGetDouble(POSITION_TP));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int TimeHour(datetime when=0){ if(when == 0) when = TimeCurrent();
|
||||
return when / 3600 % 24;
|
||||
}
|
||||
@@ -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 <Trade\Trade.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
// Input Parameters
|
||||
input group "General Settings"
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; // Trading Timeframe
|
||||
input double InpLotSize = 0.1; // 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 = true; // Enable Strategy Lock
|
||||
input double InpLockProfitThreshold = 6.0; // Lock Profit Threshold (pips)
|
||||
input bool InpCloseOppositeTrades = true; // 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);
|
||||
}
|
||||
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,539 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SimpleRSIReversalAUDUSD.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"
|
||||
#property strict
|
||||
|
||||
// Include trade class
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
// Input parameters
|
||||
input int RSIPeriod = 28; // RSI period
|
||||
input double OverboughtLevel = 68; // Overbought level
|
||||
input double OversoldLevel = 30; // Oversold level
|
||||
input int TakeProfitPips = 175; // Take profit in pips
|
||||
input int StopLossPips = 5; // Stop loss in pips
|
||||
input double MaxLotSize = 0.2; // Maximum lot size
|
||||
input int MaxSpread = 1000; // Maximum allowed spread in pips
|
||||
input int MaxDuration = 340; // Maximum trade duration in hours
|
||||
input bool UseStopLoss = false; // Use stop loss
|
||||
input bool UseTakeProfit = false; // Use take profit
|
||||
input bool UseRSIExit = true; // Use RSI for exit
|
||||
input double RSIExitLevel = 48; // RSI level to exit (50 = neutral)
|
||||
input bool CloseOutsideSession = true; // Close trades outside Asian session
|
||||
input color PanelBackground = clrBlack; // Panel background color
|
||||
input color PanelText = clrWhite; // Panel text color
|
||||
input int PanelX = 10; // Panel X position
|
||||
input int PanelY = 20; // Panel Y position
|
||||
|
||||
// Global variables
|
||||
CTrade trade;
|
||||
int rsiHandle;
|
||||
bool isPositionOpen = false;
|
||||
double positionOpenPrice = 0;
|
||||
datetime positionOpenTime = 0;
|
||||
ENUM_POSITION_TYPE lastPositionType = POSITION_TYPE_BUY;
|
||||
bool sessionCloseAttempted = false; // Track if we've attempted to close positions for current session
|
||||
|
||||
// RSI crossover variables
|
||||
double rsiCurrent = 0;
|
||||
double rsiPrevious = 0;
|
||||
double rsiPrevious2 = 0;
|
||||
bool rsiCrossedOverbought = false;
|
||||
bool rsiCrossedOversold = false;
|
||||
bool rsiCrossedExitLevel = false;
|
||||
|
||||
// Panel objects
|
||||
string panelName = "RSIPanel";
|
||||
int panelWidth = 200;
|
||||
int panelHeight = 200;
|
||||
int labelHeight = 20;
|
||||
int labelSpacing = 5;
|
||||
|
||||
// Session times (UTC)
|
||||
const int AsianSessionStart = 0; // 00:00 UTC
|
||||
const int AsianSessionEnd = 8; // 08:00 UTC
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Create panel |
|
||||
//+------------------------------------------------------------------+
|
||||
void CreatePanel()
|
||||
{
|
||||
// Create panel background
|
||||
ObjectCreate(0, panelName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, PanelX);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, PanelY);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_XSIZE, panelWidth);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_YSIZE, panelHeight);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_BGCOLOR, PanelBackground);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_COLOR, PanelText);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_STYLE, STYLE_SOLID);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_BACK, false);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_SELECTABLE, false);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_SELECTED, false);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_HIDDEN, true);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_ZORDER, 0);
|
||||
|
||||
// Create title label
|
||||
ObjectCreate(0, panelName + "Title", OBJ_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_XDISTANCE, PanelX + 5);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_YDISTANCE, PanelY + 5);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||||
ObjectSetString(0, panelName + "Title", OBJPROP_TEXT, "RSI Reversal");
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_COLOR, PanelText);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_FONTSIZE, 10);
|
||||
|
||||
// Create score labels
|
||||
CreateScoreLabel("RSI", "RSI: ", 0);
|
||||
CreateScoreLabel("Position", "Position: ", 1);
|
||||
CreateScoreLabel("Spread", "Spread: ", 2);
|
||||
CreateScoreLabel("Session", "Session: ", 3);
|
||||
CreateScoreLabel("SL", "Stop Loss: ", 4);
|
||||
CreateScoreLabel("TP", "Take Profit: ", 5);
|
||||
CreateScoreLabel("Cross", "Cross: ", 6);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Create score label |
|
||||
//+------------------------------------------------------------------+
|
||||
void CreateScoreLabel(string name, string text, int index)
|
||||
{
|
||||
ObjectCreate(0, panelName + name, OBJ_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_XDISTANCE, PanelX + 5);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_YDISTANCE, PanelY + 30 + index * (labelHeight + labelSpacing));
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||||
ObjectSetString(0, panelName + name, OBJPROP_TEXT, text);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_COLOR, PanelText);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_FONTSIZE, 8);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update panel values |
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdatePanel(double rsi, string position, int spread, string session, double sl, double tp, string crossInfo)
|
||||
{
|
||||
ObjectSetString(0, panelName + "RSI", OBJPROP_TEXT, "RSI: " + DoubleToString(rsi, 2));
|
||||
ObjectSetString(0, panelName + "Position", OBJPROP_TEXT, "Position: " + position);
|
||||
ObjectSetString(0, panelName + "Spread", OBJPROP_TEXT, "Spread: " + IntegerToString(spread) + " pips");
|
||||
ObjectSetString(0, panelName + "Session", OBJPROP_TEXT, "Session: " + session);
|
||||
ObjectSetString(0, panelName + "SL", OBJPROP_TEXT, "Stop Loss: " + IntegerToString(StopLossPips) + " pips");
|
||||
ObjectSetString(0, panelName + "TP", OBJPROP_TEXT, "Take Profit: " + IntegerToString(TakeProfitPips) + " pips");
|
||||
ObjectSetString(0, panelName + "Cross", OBJPROP_TEXT, "Cross: " + crossInfo);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get current session name |
|
||||
//+------------------------------------------------------------------+
|
||||
string GetCurrentSession()
|
||||
{
|
||||
datetime currentTime = TimeCurrent();
|
||||
MqlDateTime timeStruct;
|
||||
TimeToStruct(currentTime, timeStruct);
|
||||
|
||||
if(timeStruct.hour >= AsianSessionStart && timeStruct.hour < AsianSessionEnd)
|
||||
return "Asian";
|
||||
else if(timeStruct.hour >= 8 && timeStruct.hour < 16)
|
||||
return "London";
|
||||
else if(timeStruct.hour >= 13 && timeStruct.hour < 21)
|
||||
return "New York";
|
||||
else
|
||||
return "Other";
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if trading is allowed |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTradingAllowed()
|
||||
{
|
||||
// Check if market is open
|
||||
if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE) == 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()
|
||||
{
|
||||
// Reset crossover flags
|
||||
rsiCrossedOverbought = false;
|
||||
rsiCrossedOversold = false;
|
||||
rsiCrossedExitLevel = false;
|
||||
|
||||
// Check for overbought crossover (RSI crosses above overbought level)
|
||||
if(rsiPrevious < OverboughtLevel && rsiCurrent >= OverboughtLevel)
|
||||
{
|
||||
rsiCrossedOverbought = true;
|
||||
}
|
||||
|
||||
// Check for oversold crossover (RSI crosses below oversold level)
|
||||
if(rsiPrevious > OversoldLevel && rsiCurrent <= OversoldLevel)
|
||||
{
|
||||
rsiCrossedOversold = true;
|
||||
}
|
||||
|
||||
// Check for exit level crossover
|
||||
if(rsiPrevious < RSIExitLevel && rsiCurrent >= RSIExitLevel)
|
||||
{
|
||||
rsiCrossedExitLevel = true;
|
||||
}
|
||||
else if(rsiPrevious > RSIExitLevel && rsiCurrent <= RSIExitLevel)
|
||||
{
|
||||
rsiCrossedExitLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize RSI indicator
|
||||
rsiHandle = iRSI(_Symbol, PERIOD_M15, RSIPeriod, PRICE_CLOSE);
|
||||
|
||||
if(rsiHandle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Wait a bit for the indicator to be ready
|
||||
Sleep(100);
|
||||
|
||||
// Initialize RSI values with retry logic
|
||||
double rsi[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
|
||||
int retryCount = 0;
|
||||
bool rsiInitialized = false;
|
||||
|
||||
while(retryCount < 10 && !rsiInitialized)
|
||||
{
|
||||
int copied = CopyBuffer(rsiHandle, 0, 0, 3, rsi);
|
||||
if(copied >= 3)
|
||||
{
|
||||
rsiCurrent = rsi[0];
|
||||
rsiPrevious = rsi[1];
|
||||
rsiPrevious2 = rsi[2];
|
||||
rsiInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
retryCount++;
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
if(!rsiInitialized)
|
||||
{
|
||||
// Don't fail initialization, just set default values
|
||||
rsiCurrent = 50.0;
|
||||
rsiPrevious = 50.0;
|
||||
rsiPrevious2 = 50.0;
|
||||
}
|
||||
|
||||
// Create panel
|
||||
CreatePanel();
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release indicator handles
|
||||
IndicatorRelease(rsiHandle);
|
||||
|
||||
// Remove panel objects
|
||||
ObjectsDeleteAll(0, panelName);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close all trades for the current symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CloseAllTrades(string reason = "")
|
||||
{
|
||||
bool allClosed = true;
|
||||
int totalPositions = PositionsTotal();
|
||||
|
||||
if(totalPositions == 0)
|
||||
return true;
|
||||
|
||||
// Check if there are any positions with our magic number
|
||||
bool hasOurPositions = false;
|
||||
for(int i = 0; i < totalPositions; i++)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == 123456)
|
||||
{
|
||||
hasOurPositions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = totalPositions - 1; i >= 0; i--)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol)
|
||||
{
|
||||
// Try to close position with retry logic
|
||||
int retryCount = 0;
|
||||
bool positionClosed = false;
|
||||
|
||||
while(retryCount < 3 && !positionClosed)
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if trading is allowed
|
||||
if(!IsTradingAllowed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're in Asian session
|
||||
if(!IsAsianSession())
|
||||
{
|
||||
// Close all positions if outside Asian session and CloseOutsideSession is true
|
||||
if(CloseOutsideSession && !sessionCloseAttempted)
|
||||
{
|
||||
CloseAllTrades("Outside Asian session");
|
||||
sessionCloseAttempted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset the session close attempt flag when we enter Asian session
|
||||
sessionCloseAttempted = false;
|
||||
}
|
||||
|
||||
// Get current spread
|
||||
double spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
int spreadInPips = (int)(spread / _Point);
|
||||
|
||||
// Check if spread is too high
|
||||
if(spreadInPips > MaxSpread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get RSI values from bar data
|
||||
double rsi[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
|
||||
int copied = CopyBuffer(rsiHandle, 0, 0, 3, rsi);
|
||||
if(copied < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update RSI values
|
||||
rsiPrevious2 = rsiPrevious;
|
||||
rsiPrevious = rsiCurrent;
|
||||
rsiCurrent = rsi[0];
|
||||
|
||||
// Validate RSI values
|
||||
if(rsiCurrent == 0 || rsiPrevious == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for RSI crossovers
|
||||
CheckRSICrossover();
|
||||
|
||||
// Get current prices
|
||||
double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
// Get position status
|
||||
string positionStatus = "None";
|
||||
for(int i = 0; i < PositionsTotal(); i++)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
positionStatus = (posType == POSITION_TYPE_BUY) ? "Long" : "Short";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate stop loss and take profit levels
|
||||
double sl = 0;
|
||||
double tp = 0;
|
||||
|
||||
// Prepare crossover info for panel
|
||||
string crossInfo = "None";
|
||||
if(rsiCrossedOverbought) crossInfo = "Overbought";
|
||||
else if(rsiCrossedOversold) crossInfo = "Oversold";
|
||||
else if(rsiCrossedExitLevel) crossInfo = "Exit";
|
||||
|
||||
// Update panel
|
||||
UpdatePanel(rsiCurrent, positionStatus, spreadInPips, GetCurrentSession(), sl, tp, crossInfo);
|
||||
|
||||
// Check for open position
|
||||
bool hasOpenPosition = false;
|
||||
for(int i = 0; i < PositionsTotal(); i++)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol)
|
||||
{
|
||||
hasOpenPosition = true;
|
||||
|
||||
// Get position details
|
||||
double positionProfit = PositionGetDouble(POSITION_PROFIT);
|
||||
double positionVolume = PositionGetDouble(POSITION_VOLUME);
|
||||
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
// Check for RSI exit if enabled
|
||||
if(UseRSIExit && rsiCrossedExitLevel)
|
||||
{
|
||||
bool shouldExit = false;
|
||||
|
||||
// For long positions, exit when RSI crosses above exit level
|
||||
if(posType == POSITION_TYPE_BUY && rsiCurrent >= RSIExitLevel && rsiPrevious < RSIExitLevel)
|
||||
{
|
||||
shouldExit = true;
|
||||
}
|
||||
// For short positions, exit when RSI crosses below exit level
|
||||
else if(posType == POSITION_TYPE_SELL && rsiCurrent <= RSIExitLevel && rsiPrevious > RSIExitLevel)
|
||||
{
|
||||
shouldExit = true;
|
||||
}
|
||||
|
||||
if(shouldExit)
|
||||
{
|
||||
CloseAllTrades("RSI Exit Crossover");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for timeout
|
||||
if(TimeCurrent() - positionOpenTime > MaxDuration * 3600)
|
||||
{
|
||||
CloseAllTrades("Timeout");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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(rsiCrossedOversold)
|
||||
{
|
||||
double sl = UseStopLoss ? currentBid - StopLossPips * _Point : 0;
|
||||
double tp = UseTakeProfit ? currentBid + TakeProfitPips * _Point : 0;
|
||||
|
||||
if(UseStopLoss && sl >= currentBid)
|
||||
return;
|
||||
if(UseTakeProfit && tp <= currentBid)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
trade.SetDeviationInPoints(3);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
trade.SetExpertMagicNumber(123456);
|
||||
|
||||
// Place buy order using CTrade
|
||||
if(trade.Buy(MaxLotSize, _Symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy"))
|
||||
{
|
||||
isPositionOpen = true;
|
||||
positionOpenPrice = currentAsk;
|
||||
positionOpenTime = TimeCurrent();
|
||||
lastPositionType = POSITION_TYPE_BUY;
|
||||
}
|
||||
}
|
||||
// Place sell order if RSI crosses above overbought level (overbought crossover)
|
||||
else if(rsiCrossedOverbought)
|
||||
{
|
||||
double sl = UseStopLoss ? currentAsk + StopLossPips * _Point : 0;
|
||||
double tp = UseTakeProfit ? currentAsk - TakeProfitPips * _Point : 0;
|
||||
|
||||
if(UseStopLoss && sl <= currentAsk)
|
||||
return;
|
||||
if(UseTakeProfit && tp >= currentAsk)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
trade.SetDeviationInPoints(3);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
trade.SetExpertMagicNumber(123456);
|
||||
|
||||
// Place sell order using CTrade
|
||||
if(trade.Sell(MaxLotSize, _Symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell"))
|
||||
{
|
||||
isPositionOpen = true;
|
||||
positionOpenPrice = currentBid;
|
||||
positionOpenTime = TimeCurrent();
|
||||
lastPositionType = POSITION_TYPE_SELL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 241 KiB |
@@ -0,0 +1,539 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SimpleRSIReversalAUDUSD.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"
|
||||
#property strict
|
||||
|
||||
// Include trade class
|
||||
#include <Trade\Trade.mqh>
|
||||
|
||||
// Input parameters
|
||||
input int RSIPeriod = 28; // RSI period
|
||||
input double OverboughtLevel = 60; // Overbought level
|
||||
input double OversoldLevel = 8; // Oversold level
|
||||
input int TakeProfitPips = 175; // Take profit in pips
|
||||
input int StopLossPips = 5; // Stop loss in pips
|
||||
input double MaxLotSize = 0.1; // Maximum lot size
|
||||
input int MaxSpread = 1000; // Maximum allowed spread in pips
|
||||
input int MaxDuration = 270; // Maximum trade duration in hours
|
||||
input bool UseStopLoss = false; // Use stop loss
|
||||
input bool UseTakeProfit = false; // Use take profit
|
||||
input bool UseRSIExit = true; // Use RSI for exit
|
||||
input double RSIExitLevel = 55; // RSI level to exit (50 = neutral)
|
||||
input bool CloseOutsideSession = false; // Close trades outside Asian session
|
||||
input color PanelBackground = clrBlack; // Panel background color
|
||||
input color PanelText = clrWhite; // Panel text color
|
||||
input int PanelX = 10; // Panel X position
|
||||
input int PanelY = 20; // Panel Y position
|
||||
|
||||
// Global variables
|
||||
CTrade trade;
|
||||
int rsiHandle;
|
||||
bool isPositionOpen = false;
|
||||
double positionOpenPrice = 0;
|
||||
datetime positionOpenTime = 0;
|
||||
ENUM_POSITION_TYPE lastPositionType = POSITION_TYPE_BUY;
|
||||
bool sessionCloseAttempted = false; // Track if we've attempted to close positions for current session
|
||||
|
||||
// RSI crossover variables
|
||||
double rsiCurrent = 0;
|
||||
double rsiPrevious = 0;
|
||||
double rsiPrevious2 = 0;
|
||||
bool rsiCrossedOverbought = false;
|
||||
bool rsiCrossedOversold = false;
|
||||
bool rsiCrossedExitLevel = false;
|
||||
|
||||
// Panel objects
|
||||
string panelName = "RSIPanel";
|
||||
int panelWidth = 200;
|
||||
int panelHeight = 200;
|
||||
int labelHeight = 20;
|
||||
int labelSpacing = 5;
|
||||
|
||||
// Session times (UTC)
|
||||
const int AsianSessionStart = 0; // 00:00 UTC
|
||||
const int AsianSessionEnd = 8; // 08:00 UTC
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Create panel |
|
||||
//+------------------------------------------------------------------+
|
||||
void CreatePanel()
|
||||
{
|
||||
// Create panel background
|
||||
ObjectCreate(0, panelName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, PanelX);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, PanelY);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_XSIZE, panelWidth);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_YSIZE, panelHeight);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_BGCOLOR, PanelBackground);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_COLOR, PanelText);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_STYLE, STYLE_SOLID);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_WIDTH, 1);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_BACK, false);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_SELECTABLE, false);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_SELECTED, false);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_HIDDEN, true);
|
||||
ObjectSetInteger(0, panelName, OBJPROP_ZORDER, 0);
|
||||
|
||||
// Create title label
|
||||
ObjectCreate(0, panelName + "Title", OBJ_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_XDISTANCE, PanelX + 5);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_YDISTANCE, PanelY + 5);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||||
ObjectSetString(0, panelName + "Title", OBJPROP_TEXT, "RSI Reversal");
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_COLOR, PanelText);
|
||||
ObjectSetInteger(0, panelName + "Title", OBJPROP_FONTSIZE, 10);
|
||||
|
||||
// Create score labels
|
||||
CreateScoreLabel("RSI", "RSI: ", 0);
|
||||
CreateScoreLabel("Position", "Position: ", 1);
|
||||
CreateScoreLabel("Spread", "Spread: ", 2);
|
||||
CreateScoreLabel("Session", "Session: ", 3);
|
||||
CreateScoreLabel("SL", "Stop Loss: ", 4);
|
||||
CreateScoreLabel("TP", "Take Profit: ", 5);
|
||||
CreateScoreLabel("Cross", "Cross: ", 6);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Create score label |
|
||||
//+------------------------------------------------------------------+
|
||||
void CreateScoreLabel(string name, string text, int index)
|
||||
{
|
||||
ObjectCreate(0, panelName + name, OBJ_LABEL, 0, 0, 0);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_XDISTANCE, PanelX + 5);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_YDISTANCE, PanelY + 30 + index * (labelHeight + labelSpacing));
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||||
ObjectSetString(0, panelName + name, OBJPROP_TEXT, text);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_COLOR, PanelText);
|
||||
ObjectSetInteger(0, panelName + name, OBJPROP_FONTSIZE, 8);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update panel values |
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdatePanel(double rsi, string position, int spread, string session, double sl, double tp, string crossInfo)
|
||||
{
|
||||
ObjectSetString(0, panelName + "RSI", OBJPROP_TEXT, "RSI: " + DoubleToString(rsi, 2));
|
||||
ObjectSetString(0, panelName + "Position", OBJPROP_TEXT, "Position: " + position);
|
||||
ObjectSetString(0, panelName + "Spread", OBJPROP_TEXT, "Spread: " + IntegerToString(spread) + " pips");
|
||||
ObjectSetString(0, panelName + "Session", OBJPROP_TEXT, "Session: " + session);
|
||||
ObjectSetString(0, panelName + "SL", OBJPROP_TEXT, "Stop Loss: " + IntegerToString(StopLossPips) + " pips");
|
||||
ObjectSetString(0, panelName + "TP", OBJPROP_TEXT, "Take Profit: " + IntegerToString(TakeProfitPips) + " pips");
|
||||
ObjectSetString(0, panelName + "Cross", OBJPROP_TEXT, "Cross: " + crossInfo);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Get current session name |
|
||||
//+------------------------------------------------------------------+
|
||||
string GetCurrentSession()
|
||||
{
|
||||
datetime currentTime = TimeCurrent();
|
||||
MqlDateTime timeStruct;
|
||||
TimeToStruct(currentTime, timeStruct);
|
||||
|
||||
if(timeStruct.hour >= AsianSessionStart && timeStruct.hour < AsianSessionEnd)
|
||||
return "Asian";
|
||||
else if(timeStruct.hour >= 8 && timeStruct.hour < 16)
|
||||
return "London";
|
||||
else if(timeStruct.hour >= 13 && timeStruct.hour < 21)
|
||||
return "New York";
|
||||
else
|
||||
return "Other";
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check if trading is allowed |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTradingAllowed()
|
||||
{
|
||||
// Check if market is open
|
||||
if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE) == 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()
|
||||
{
|
||||
// Reset crossover flags
|
||||
rsiCrossedOverbought = false;
|
||||
rsiCrossedOversold = false;
|
||||
rsiCrossedExitLevel = false;
|
||||
|
||||
// Check for overbought crossover (RSI crosses above overbought level)
|
||||
if(rsiPrevious < OverboughtLevel && rsiCurrent >= OverboughtLevel)
|
||||
{
|
||||
rsiCrossedOverbought = true;
|
||||
}
|
||||
|
||||
// Check for oversold crossover (RSI crosses below oversold level)
|
||||
if(rsiPrevious > OversoldLevel && rsiCurrent <= OversoldLevel)
|
||||
{
|
||||
rsiCrossedOversold = true;
|
||||
}
|
||||
|
||||
// Check for exit level crossover
|
||||
if(rsiPrevious < RSIExitLevel && rsiCurrent >= RSIExitLevel)
|
||||
{
|
||||
rsiCrossedExitLevel = true;
|
||||
}
|
||||
else if(rsiPrevious > RSIExitLevel && rsiCurrent <= RSIExitLevel)
|
||||
{
|
||||
rsiCrossedExitLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize RSI indicator
|
||||
rsiHandle = iRSI(_Symbol, PERIOD_M15, RSIPeriod, PRICE_CLOSE);
|
||||
|
||||
if(rsiHandle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Wait a bit for the indicator to be ready
|
||||
Sleep(100);
|
||||
|
||||
// Initialize RSI values with retry logic
|
||||
double rsi[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
|
||||
int retryCount = 0;
|
||||
bool rsiInitialized = false;
|
||||
|
||||
while(retryCount < 10 && !rsiInitialized)
|
||||
{
|
||||
int copied = CopyBuffer(rsiHandle, 0, 0, 3, rsi);
|
||||
if(copied >= 3)
|
||||
{
|
||||
rsiCurrent = rsi[0];
|
||||
rsiPrevious = rsi[1];
|
||||
rsiPrevious2 = rsi[2];
|
||||
rsiInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
retryCount++;
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
if(!rsiInitialized)
|
||||
{
|
||||
// Don't fail initialization, just set default values
|
||||
rsiCurrent = 50.0;
|
||||
rsiPrevious = 50.0;
|
||||
rsiPrevious2 = 50.0;
|
||||
}
|
||||
|
||||
// Create panel
|
||||
CreatePanel();
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Release indicator handles
|
||||
IndicatorRelease(rsiHandle);
|
||||
|
||||
// Remove panel objects
|
||||
ObjectsDeleteAll(0, panelName);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close all trades for the current symbol |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CloseAllTrades(string reason = "")
|
||||
{
|
||||
bool allClosed = true;
|
||||
int totalPositions = PositionsTotal();
|
||||
|
||||
if(totalPositions == 0)
|
||||
return true;
|
||||
|
||||
// Check if there are any positions with our magic number
|
||||
bool hasOurPositions = false;
|
||||
for(int i = 0; i < totalPositions; i++)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol && PositionGetInteger(POSITION_MAGIC) == 123456)
|
||||
{
|
||||
hasOurPositions = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = totalPositions - 1; i >= 0; i--)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol)
|
||||
{
|
||||
// Try to close position with retry logic
|
||||
int retryCount = 0;
|
||||
bool positionClosed = false;
|
||||
|
||||
while(retryCount < 3 && !positionClosed)
|
||||
{
|
||||
if(trade.PositionClose(_Symbol))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if trading is allowed
|
||||
if(!IsTradingAllowed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're in Asian session
|
||||
if(!IsAsianSession())
|
||||
{
|
||||
// Close all positions if outside Asian session and CloseOutsideSession is true
|
||||
if(CloseOutsideSession && !sessionCloseAttempted)
|
||||
{
|
||||
CloseAllTrades("Outside Asian session");
|
||||
sessionCloseAttempted = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset the session close attempt flag when we enter Asian session
|
||||
sessionCloseAttempted = false;
|
||||
}
|
||||
|
||||
// Get current spread
|
||||
double spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
int spreadInPips = (int)(spread / _Point);
|
||||
|
||||
// Check if spread is too high
|
||||
if(spreadInPips > MaxSpread)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get RSI values from bar data
|
||||
double rsi[];
|
||||
ArraySetAsSeries(rsi, true);
|
||||
|
||||
int copied = CopyBuffer(rsiHandle, 0, 0, 3, rsi);
|
||||
if(copied < 3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update RSI values
|
||||
rsiPrevious2 = rsiPrevious;
|
||||
rsiPrevious = rsiCurrent;
|
||||
rsiCurrent = rsi[0];
|
||||
|
||||
// Validate RSI values
|
||||
if(rsiCurrent == 0 || rsiPrevious == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for RSI crossovers
|
||||
CheckRSICrossover();
|
||||
|
||||
// Get current prices
|
||||
double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
// Get position status
|
||||
string positionStatus = "None";
|
||||
for(int i = 0; i < PositionsTotal(); i++)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
positionStatus = (posType == POSITION_TYPE_BUY) ? "Long" : "Short";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate stop loss and take profit levels
|
||||
double sl = 0;
|
||||
double tp = 0;
|
||||
|
||||
// Prepare crossover info for panel
|
||||
string crossInfo = "None";
|
||||
if(rsiCrossedOverbought) crossInfo = "Overbought";
|
||||
else if(rsiCrossedOversold) crossInfo = "Oversold";
|
||||
else if(rsiCrossedExitLevel) crossInfo = "Exit";
|
||||
|
||||
// Update panel
|
||||
UpdatePanel(rsiCurrent, positionStatus, spreadInPips, GetCurrentSession(), sl, tp, crossInfo);
|
||||
|
||||
// Check for open position
|
||||
bool hasOpenPosition = false;
|
||||
for(int i = 0; i < PositionsTotal(); i++)
|
||||
{
|
||||
if(PositionGetSymbol(i) == _Symbol)
|
||||
{
|
||||
hasOpenPosition = true;
|
||||
|
||||
// Get position details
|
||||
double positionProfit = PositionGetDouble(POSITION_PROFIT);
|
||||
double positionVolume = PositionGetDouble(POSITION_VOLUME);
|
||||
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
// Check for RSI exit if enabled
|
||||
if(UseRSIExit && rsiCrossedExitLevel)
|
||||
{
|
||||
bool shouldExit = false;
|
||||
|
||||
// For long positions, exit when RSI crosses above exit level
|
||||
if(posType == POSITION_TYPE_BUY && rsiCurrent >= RSIExitLevel && rsiPrevious < RSIExitLevel)
|
||||
{
|
||||
shouldExit = true;
|
||||
}
|
||||
// For short positions, exit when RSI crosses below exit level
|
||||
else if(posType == POSITION_TYPE_SELL && rsiCurrent <= RSIExitLevel && rsiPrevious > RSIExitLevel)
|
||||
{
|
||||
shouldExit = true;
|
||||
}
|
||||
|
||||
if(shouldExit)
|
||||
{
|
||||
CloseAllTrades("RSI Exit Crossover");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for timeout
|
||||
if(TimeCurrent() - positionOpenTime > MaxDuration * 3600)
|
||||
{
|
||||
CloseAllTrades("Timeout");
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 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(rsiCrossedOversold)
|
||||
{
|
||||
double sl = UseStopLoss ? currentBid - StopLossPips * _Point : 0;
|
||||
double tp = UseTakeProfit ? currentBid + TakeProfitPips * _Point : 0;
|
||||
|
||||
if(UseStopLoss && sl >= currentBid)
|
||||
return;
|
||||
if(UseTakeProfit && tp <= currentBid)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
trade.SetDeviationInPoints(3);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
trade.SetExpertMagicNumber(123456);
|
||||
|
||||
// Place buy order using CTrade
|
||||
if(trade.Buy(MaxLotSize, _Symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy"))
|
||||
{
|
||||
isPositionOpen = true;
|
||||
positionOpenPrice = currentAsk;
|
||||
positionOpenTime = TimeCurrent();
|
||||
lastPositionType = POSITION_TYPE_BUY;
|
||||
}
|
||||
}
|
||||
// Place sell order if RSI crosses above overbought level (overbought crossover)
|
||||
else if(rsiCrossedOverbought)
|
||||
{
|
||||
double sl = UseStopLoss ? currentAsk + StopLossPips * _Point : 0;
|
||||
double tp = UseTakeProfit ? currentAsk - TakeProfitPips * _Point : 0;
|
||||
|
||||
if(UseStopLoss && sl <= currentAsk)
|
||||
return;
|
||||
if(UseTakeProfit && tp >= currentAsk)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
trade.SetDeviationInPoints(3);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
trade.SetExpertMagicNumber(123456);
|
||||
|
||||
// Place sell order using CTrade
|
||||
if(trade.Sell(MaxLotSize, _Symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell"))
|
||||
{
|
||||
isPositionOpen = true;
|
||||
positionOpenPrice = currentBid;
|
||||
positionOpenTime = TimeCurrent();
|
||||
lastPositionType = POSITION_TYPE_SELL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 250 KiB |
@@ -0,0 +1,581 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIScalping.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.01"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
//--- Input parameters
|
||||
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis
|
||||
input int RSI_Period = 14; // RSI Period
|
||||
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
|
||||
input double RSI_Overbought = 90; // RSI Overbought Level
|
||||
input double RSI_Oversold = 73; // RSI Oversold Level
|
||||
input double RSI_Target_Buy = 88; // RSI Target for Buy Exit
|
||||
input double RSI_Target_Sell = 48; // RSI Target for Sell Exit
|
||||
input int BarsToWait = 6; // Bars to wait when RSI goes against position
|
||||
input double LotSize = 0.1; // Lot Size
|
||||
input int MagicNumber = 123459123; // Magic Number
|
||||
input int Slippage = 3; // Slippage in points
|
||||
|
||||
input group "=== Reversal escape (intrabar, multi-signal) ==="
|
||||
input bool UseReversalEscape = true; // run while in position every tick
|
||||
input int ReversalATRPeriod = 14; // ATR lookback on signal timeframe
|
||||
input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR
|
||||
input int ReversalSignsRequired = 2; // how many independent signs must align
|
||||
input double ReversalRsiVelocity = 16.0; // RSI points drop (long) / rise (short) vs prior buffer
|
||||
input double ReversalBodyAtrMult = 5.1; // last closed bar body >= this * ATR counts as one sign
|
||||
|
||||
input group "=== Trailing stop ==="
|
||||
input bool UseTrailingStop = true; // move SL behind bid/ask while in profit
|
||||
input double TrailingStopDistancePoints = 120.0; // SL distance from bid/ask (points)
|
||||
input double TrailingActivationPoints = 0.0; // min profit before trailing (0 = same as distance)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int rsi_handle;
|
||||
double rsi_buffer[];
|
||||
double rsi_prev, rsi_current, rsi_two_bars_ago;
|
||||
bool position_open = false;
|
||||
int position_ticket = 0;
|
||||
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
|
||||
datetime last_bar_time = 0;
|
||||
bool rsi_against_position = false;
|
||||
int bars_against_count = 0;
|
||||
|
||||
void ResetPositionTracking();
|
||||
void SyncTrackedPosition();
|
||||
double ATRPriceOnTF(const int period);
|
||||
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr);
|
||||
void TryReversalEscape();
|
||||
void ApplyTrailingStop();
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize RSI indicator
|
||||
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
|
||||
if(rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Initialize trade object
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Allocate arrays
|
||||
ArraySetAsSeries(rsi_buffer, true);
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(rsi_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(rsi_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Check if we have enough bars
|
||||
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a new bar
|
||||
datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
|
||||
bool is_new_bar = (current_bar_time != last_bar_time);
|
||||
bool in_position = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
|
||||
|
||||
// While flat, process only on new bars. While in position, allow intrabar reversal escape checks.
|
||||
if(!in_position && !is_new_bar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update RSI values
|
||||
if(!UpdateRSI())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(in_position && UseReversalEscape)
|
||||
{
|
||||
TryReversalEscape();
|
||||
}
|
||||
|
||||
if(in_position && UseTrailingStop)
|
||||
ApplyTrailingStop();
|
||||
|
||||
if(!is_new_bar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
// Keep local tracking aligned with actual terminal positions for this symbol/magic.
|
||||
SyncTrackedPosition();
|
||||
|
||||
// Check for existing position
|
||||
CheckExistingPosition();
|
||||
|
||||
// Check for new entry signals - only if no position exists for THIS EA (magic number) on THIS symbol
|
||||
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
CheckEntrySignals();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update RSI values |
|
||||
//+------------------------------------------------------------------+
|
||||
bool UpdateRSI()
|
||||
{
|
||||
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rsi_current = rsi_buffer[0]; // Current bar
|
||||
rsi_prev = rsi_buffer[1]; // Previous bar
|
||||
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Wilder ATR in price units (signal timeframe) |
|
||||
//+------------------------------------------------------------------+
|
||||
double ATRPriceOnTF(const int period)
|
||||
{
|
||||
if(period < 1)
|
||||
return 0.0;
|
||||
|
||||
MqlRates rates[];
|
||||
const int need = period + 2;
|
||||
if(CopyRates(_Symbol, TimeFrame, 0, need, rates) < need)
|
||||
return 0.0;
|
||||
|
||||
ArraySetAsSeries(rates, true);
|
||||
double sum = 0.0;
|
||||
for(int i = 1; i <= period; i++)
|
||||
{
|
||||
const double hl = rates[i].high - rates[i].low;
|
||||
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
|
||||
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
|
||||
sum += MathMax(hl, MathMax(hc, lc));
|
||||
}
|
||||
|
||||
return sum / (double)period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Independent adverse signs (need ReversalSignsRequired to exit) |
|
||||
//+------------------------------------------------------------------+
|
||||
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr)
|
||||
{
|
||||
if(atr <= 0.0)
|
||||
return 0;
|
||||
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
int signs = 0;
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(entry - bid >= ReversalAdverseAtrMult * atr)
|
||||
signs++;
|
||||
if(rsi_prev - rsi_current >= ReversalRsiVelocity)
|
||||
signs++;
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
if(ask - entry >= ReversalAdverseAtrMult * atr)
|
||||
signs++;
|
||||
if(rsi_current - rsi_prev >= ReversalRsiVelocity)
|
||||
signs++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
MqlRates rates[];
|
||||
if(CopyRates(_Symbol, TimeFrame, 0, 4, rates) >= 4)
|
||||
{
|
||||
ArraySetAsSeries(rates, true);
|
||||
const double body = MathAbs(rates[1].close - rates[1].open);
|
||||
if(body >= ReversalBodyAtrMult * atr)
|
||||
{
|
||||
if(ptype == POSITION_TYPE_BUY && rates[1].close < rates[1].open)
|
||||
signs++;
|
||||
else if(ptype == POSITION_TYPE_SELL && rates[1].close > rates[1].open)
|
||||
signs++;
|
||||
}
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(rates[1].close < rates[2].close && rates[2].close < rates[3].close)
|
||||
signs++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(rates[1].close > rates[2].close && rates[2].close > rates[3].close)
|
||||
signs++;
|
||||
}
|
||||
}
|
||||
|
||||
return signs;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Cut losers fast on violent reversals (evaluated every tick) |
|
||||
//+------------------------------------------------------------------+
|
||||
void TryReversalEscape()
|
||||
{
|
||||
ulong live_ticket = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(live_ticket == 0)
|
||||
return;
|
||||
if(!PositionSelectByTicketSymbolAndMagic(live_ticket, _Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double atr = ATRPriceOnTF(ReversalATRPeriod);
|
||||
if(atr <= 0.0)
|
||||
return;
|
||||
|
||||
const int signs = CountReversalEscapeSigns(ptype, atr);
|
||||
if(signs < ReversalSignsRequired)
|
||||
return;
|
||||
|
||||
ClosePosition();
|
||||
Print("RSIScalpingBTCUSD: reversal escape signs=", signs, " need=", ReversalSignsRequired,
|
||||
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trail SL behind favorable price (every tick when enabled) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
if(TrailingStopDistancePoints <= 0.0)
|
||||
return;
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
if(point <= 0.0)
|
||||
return;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
const double trail_dist = TrailingStopDistancePoints * point;
|
||||
const double activation_pts = (TrailingActivationPoints > 0.0)
|
||||
? TrailingActivationPoints
|
||||
: TrailingStopDistancePoints;
|
||||
const double activation = activation_pts * point;
|
||||
const long stops_level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double cur_sl = PositionGetDouble(POSITION_SL);
|
||||
const double cur_tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
if(bid - entry <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_sl < min_dist)
|
||||
new_sl = NormalizeDouble(bid - min_dist, digits);
|
||||
|
||||
if(new_sl >= bid || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl <= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
if(entry - ask <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_sl - ask < min_dist)
|
||||
new_sl = NormalizeDouble(ask + min_dist, digits);
|
||||
|
||||
if(new_sl <= ask || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl >= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Reset local position tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
void ResetPositionTracking()
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Sync local state with real position in terminal |
|
||||
//+------------------------------------------------------------------+
|
||||
void SyncTrackedPosition()
|
||||
{
|
||||
ulong live_ticket = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(live_ticket == 0)
|
||||
{
|
||||
ResetPositionTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we were not tracking (or ticket changed), start tracking the live position.
|
||||
if(!position_open || position_ticket != (int)live_ticket)
|
||||
{
|
||||
if(PositionSelectByTicketSymbolAndMagic(live_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = true;
|
||||
position_ticket = (int)live_ticket;
|
||||
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check existing position for exit conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckExistingPosition()
|
||||
{
|
||||
if(!position_open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if position still exists with correct magic number AND symbol for THIS EA
|
||||
if(!PositionSelectByTicketSymbolAndMagic(position_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
ResetPositionTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit conditions based on RSI target
|
||||
if(current_position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
// Check if RSI is against the position (below oversold)
|
||||
if(rsi_current < RSI_Oversold)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit long position when RSI reaches buy target
|
||||
if(rsi_current >= RSI_Target_Buy)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(current_position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
// Check if RSI is against the position (above overbought)
|
||||
if(rsi_current > RSI_Overbought)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit short position when RSI reaches sell target
|
||||
if(rsi_current <= RSI_Target_Sell)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check for entry signals |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckEntrySignals()
|
||||
{
|
||||
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
|
||||
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
}
|
||||
|
||||
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
|
||||
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought)
|
||||
{
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
// Verify no position exists for THIS EA (magic number) on THIS symbol before opening
|
||||
if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Position already exists for this EA
|
||||
}
|
||||
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
|
||||
{
|
||||
ulong new_ticket = trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
// Verify position was opened for THIS EA (magic number) on THIS symbol
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = new_ticket;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_BUY;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error: Position opened but doesn't match EA magic number or symbol");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
// Verify no position exists for THIS EA (magic number) on THIS symbol before opening
|
||||
if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Position already exists for this EA
|
||||
}
|
||||
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
|
||||
{
|
||||
ulong new_ticket = trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
// Verify position was opened for THIS EA (magic number) on THIS symbol
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = new_ticket;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_SELL;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error: Position opened but doesn't match EA magic number or symbol");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close current position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ClosePosition()
|
||||
{
|
||||
bool position_exists_before_close = PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(!position_exists_before_close)
|
||||
{
|
||||
ResetPositionTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close position using helper that verifies symbol AND magic number for THIS EA
|
||||
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
ResetPositionTracking();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep tracking when close fails (e.g. market closed); retry on next bar.
|
||||
if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
ResetPositionTracking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,28 @@
|
||||
; saved on 2026.02.07
|
||||
; Genetic Algorithm Optimization Parameters for RSIScalpingNVDA
|
||||
; Recommended ranges for profitable parameter discovery
|
||||
;
|
||||
; Format: Parameter=Start||Step||Min||Max||Optimize(Y/N)
|
||||
;
|
||||
; NOTE: Current values show RSI_Overbought=19 and RSI_Oversold=50 which are unusual.
|
||||
; This config uses STANDARD RSI ranges (60-85 overbought, 15-40 oversold).
|
||||
; If your current values are intentional, use the alternative ranges in OPTIMIZATION_GUIDE.md
|
||||
;
|
||||
; === PHASE 1: CORE RSI PARAMETERS (Primary Optimization) ===
|
||||
RSI_Period=14||1||7||21||Y
|
||||
RSI_Overbought=70.0||2.0||60.0||85.0||Y
|
||||
RSI_Oversold=30.0||2.0||15.0||40.0||Y
|
||||
RSI_Target_Buy=75.0||2.0||65.0||90.0||Y
|
||||
RSI_Target_Sell=25.0||2.0||10.0||35.0||Y
|
||||
|
||||
; === PHASE 2: RISK MANAGEMENT (Secondary Optimization) ===
|
||||
BarsToWait=2||1||1||8||Y
|
||||
TimeFrame=16387||0||16385||16390||Y
|
||||
|
||||
; === PHASE 3: POSITION SIZING (Optimize with caution) ===
|
||||
LotSize=50.0||5.0||10.0||100.0||Y
|
||||
|
||||
; === FIXED PARAMETERS (Do Not Optimize) ===
|
||||
RSI_Applied_Price=1||0||1||1||N
|
||||
MagicNumber=12345||0||12345||12345||N
|
||||
Slippage=3||0||3||3||N
|
||||
@@ -0,0 +1,24 @@
|
||||
; saved on 2026.02.07
|
||||
; Alternative Genetic Algorithm Optimization - Respects Current Unusual RSI Values
|
||||
; Use this if RSI_Overbought=19 and RSI_Oversold=50 are intentional
|
||||
;
|
||||
; Format: Parameter=Start||Step||Min||Max||Optimize(Y/N)
|
||||
;
|
||||
; === PHASE 1: CORE RSI PARAMETERS ===
|
||||
RSI_Period=14||1||7||21||Y
|
||||
RSI_Overbought=19.0||1.0||15.0||30.0||Y
|
||||
RSI_Oversold=50.0||2.0||40.0||60.0||Y
|
||||
RSI_Target_Buy=71.0||2.0||65.0||80.0||Y
|
||||
RSI_Target_Sell=70.0||2.0||60.0||75.0||Y
|
||||
|
||||
; === PHASE 2: RISK MANAGEMENT ===
|
||||
BarsToWait=1||1||1||8||Y
|
||||
TimeFrame=16387||0||16385||16390||Y
|
||||
|
||||
; === PHASE 3: POSITION SIZING ===
|
||||
LotSize=50.0||5.0||10.0||100.0||Y
|
||||
|
||||
; === FIXED PARAMETERS ===
|
||||
RSI_Applied_Price=1||0||1||1||N
|
||||
MagicNumber=12345||0||12345||12345||N
|
||||
Slippage=3||0||3||3||N
|
||||
@@ -0,0 +1,134 @@
|
||||
# Genetic Algorithm Optimization Guide for RSIScalpingNVDA
|
||||
|
||||
## Recommended Optimization Strategy
|
||||
|
||||
### Phase 1: Core RSI Parameters (Primary Focus)
|
||||
These parameters directly control entry/exit signals and should be optimized first.
|
||||
|
||||
#### **RSI_Period** (Y - Optimize)
|
||||
- **Current**: 14
|
||||
- **Recommended Range**: 7-21
|
||||
- **Step**: 1
|
||||
- **Rationale**: Standard RSI periods. Shorter = more sensitive, longer = smoother signals
|
||||
|
||||
#### **RSI_Overbought** (Y - Optimize)
|
||||
- **Current**: 19.0 (unusually low - verify if this is correct)
|
||||
- **Standard Range**: 60.0-85.0
|
||||
- **Step**: 2.0
|
||||
- **Alternative Range** (if current is intentional): 15.0-30.0
|
||||
- **Rationale**: Level where RSI indicates overbought condition for sell entries
|
||||
|
||||
#### **RSI_Oversold** (Y - Optimize)
|
||||
- **Current**: 50.0 (unusually high - verify if this is correct)
|
||||
- **Standard Range**: 15.0-40.0
|
||||
- **Step**: 2.0
|
||||
- **Alternative Range** (if current is intentional): 40.0-60.0
|
||||
- **Rationale**: Level where RSI indicates oversold condition for buy entries
|
||||
|
||||
#### **RSI_Target_Buy** (Y - Optimize)
|
||||
- **Current**: 71.0
|
||||
- **Recommended Range**: 65.0-90.0
|
||||
- **Step**: 2.0
|
||||
- **Rationale**: Exit target for long positions. Must be > RSI_Oversold
|
||||
|
||||
#### **RSI_Target_Sell** (Y - Optimize)
|
||||
- **Current**: 70.0
|
||||
- **Recommended Range**: 10.0-35.0
|
||||
- **Step**: 2.0
|
||||
- **Rationale**: Exit target for short positions. Must be < RSI_Overbought
|
||||
|
||||
### Phase 2: Risk Management Parameters
|
||||
|
||||
#### **BarsToWait** (Y - Optimize)
|
||||
- **Current**: 1
|
||||
- **Recommended Range**: 1-8
|
||||
- **Step**: 1
|
||||
- **Rationale**: Bars to wait before closing when RSI goes against position. Higher = more patience
|
||||
|
||||
#### **TimeFrame** (Y - Optimize)
|
||||
- **Current**: 16387 (M5)
|
||||
- **Recommended**: Test M1, M5, M15, H1
|
||||
- **Values**:
|
||||
- M1 = 16385
|
||||
- M5 = 16387
|
||||
- M15 = 16388
|
||||
- H1 = 16390
|
||||
- **Rationale**: Different timeframes can significantly affect scalping performance
|
||||
|
||||
### Phase 3: Position Sizing (Optimize with Caution)
|
||||
|
||||
#### **LotSize** (Y - Optimize with Fixed Risk)
|
||||
- **Current**: 50.0
|
||||
- **Recommended Range**: 10.0-100.0
|
||||
- **Step**: 5.0
|
||||
- **Note**: Consider using fixed risk % instead of fixed lot size
|
||||
- **Rationale**: Position sizing affects profitability but also risk
|
||||
|
||||
### Fixed Parameters (Do NOT Optimize)
|
||||
|
||||
#### **RSI_Applied_Price** (N)
|
||||
- **Value**: 1 (PRICE_CLOSE)
|
||||
- **Rationale**: Standard choice, changing may not improve results significantly
|
||||
|
||||
#### **MagicNumber** (N)
|
||||
- **Value**: 12345
|
||||
- **Rationale**: Identifier only, no impact on performance
|
||||
|
||||
#### **Slippage** (N)
|
||||
- **Value**: 3
|
||||
- **Rationale**: Broker-specific, should match your actual slippage
|
||||
|
||||
## Genetic Algorithm Settings
|
||||
|
||||
### Recommended GA Settings:
|
||||
- **Optimization Criterion**: Balance (or Custom: Profit Factor * Total Net Profit)
|
||||
- **Population Size**: 50-100
|
||||
- **Mutation Probability**: 0.1-0.2
|
||||
- **Crossover Probability**: 0.7-0.9
|
||||
- **Optimization Passes**: 3-5
|
||||
- **Forward Testing**: Always use out-of-sample data
|
||||
|
||||
### Optimization Phases:
|
||||
|
||||
1. **Broad Search** (First Pass):
|
||||
- Optimize: RSI_Period, RSI_Overbought, RSI_Oversold, RSI_Target_Buy, RSI_Target_Sell
|
||||
- Fix: BarsToWait=1, TimeFrame=M5, LotSize=50
|
||||
|
||||
2. **Refinement** (Second Pass):
|
||||
- Use best results from Phase 1
|
||||
- Optimize: BarsToWait, TimeFrame
|
||||
- Narrow ranges around Phase 1 winners
|
||||
|
||||
3. **Fine-Tuning** (Third Pass):
|
||||
- Optimize: LotSize (if needed)
|
||||
- Very narrow ranges around Phase 2 winners
|
||||
|
||||
## Important Notes
|
||||
|
||||
⚠️ **Current Parameter Anomaly**:
|
||||
- RSI_Overbought=19 and RSI_Oversold=50 are unusual
|
||||
- Standard RSI ranges: Overbought 70-80, Oversold 20-30
|
||||
- **Verify** if these are intentional or if there's a scaling issue
|
||||
|
||||
✅ **Validation Checklist**:
|
||||
- Ensure RSI_Target_Buy > RSI_Oversold
|
||||
- Ensure RSI_Target_Sell < RSI_Overbought
|
||||
- Test on sufficient historical data (at least 6-12 months)
|
||||
- Use forward testing on unseen data
|
||||
- Check for overfitting (too many parameters optimized)
|
||||
|
||||
## Example .set File Structure
|
||||
|
||||
```
|
||||
RSI_Period=14||1||7||21||Y
|
||||
RSI_Overbought=70.0||2.0||60.0||85.0||Y
|
||||
RSI_Oversold=30.0||2.0||15.0||40.0||Y
|
||||
RSI_Target_Buy=75.0||2.0||65.0||90.0||Y
|
||||
RSI_Target_Sell=25.0||2.0||10.0||35.0||Y
|
||||
BarsToWait=2||1||1||8||Y
|
||||
TimeFrame=16387||0||16385||16390||Y
|
||||
LotSize=50.0||5.0||10.0||100.0||Y
|
||||
RSI_Applied_Price=1||0||1||1||N
|
||||
MagicNumber=12345||0||12345||12345||N
|
||||
Slippage=3||0||3||3||N
|
||||
```
|
||||
@@ -0,0 +1,408 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIScalping.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.01"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
//--- Input parameters
|
||||
input ENUM_TIMEFRAMES TimeFrame = PERIOD_M15; // Timeframe for Analysis
|
||||
input int RSI_Period = 8; // RSI Period
|
||||
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
|
||||
input double RSI_Overbought = 36; // RSI Overbought Level
|
||||
input double RSI_Oversold = 38; // RSI Oversold Level
|
||||
input double RSI_Target_Buy = 90; // RSI Target for Buy Exit
|
||||
input double RSI_Target_Sell = 70; // RSI Target for Sell Exit
|
||||
input int BarsToWait = 5; // Bars to wait when RSI goes against position
|
||||
input double LotSize = 50; // Lot Size
|
||||
input int MagicNumber = 12345; // Magic Number
|
||||
input int Slippage = 3; // Slippage in points
|
||||
|
||||
input group "=== Trailing stop ==="
|
||||
input bool UseTrailingStop = true; // move SL behind bid/ask while in profit
|
||||
input double TrailingStopDistancePoints = 375.0; // SL distance from bid/ask (points)
|
||||
input double TrailingActivationPoints = 75.0; // min profit before trailing (0 = same as distance)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int rsi_handle;
|
||||
double rsi_buffer[];
|
||||
double rsi_prev, rsi_current, rsi_two_bars_ago;
|
||||
bool position_open = false;
|
||||
int position_ticket = 0;
|
||||
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
|
||||
datetime last_bar_time = 0;
|
||||
bool rsi_against_position = false;
|
||||
int bars_against_count = 0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize RSI indicator
|
||||
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
|
||||
if(rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Initialize trade object
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Allocate arrays
|
||||
ArraySetAsSeries(rsi_buffer, true);
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(rsi_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(rsi_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
|
||||
return;
|
||||
|
||||
const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
|
||||
const bool new_bar = (current_bar_time != last_bar_time);
|
||||
const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
|
||||
|
||||
if(!in_pos && !new_bar)
|
||||
return;
|
||||
|
||||
if(!UpdateRSI())
|
||||
return;
|
||||
|
||||
if(in_pos && UseTrailingStop)
|
||||
ApplyTrailingStop();
|
||||
|
||||
if(!new_bar)
|
||||
return;
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
ResyncPositionFromMarket();
|
||||
CheckExistingPosition();
|
||||
|
||||
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
CheckEntrySignals();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update RSI values |
|
||||
//+------------------------------------------------------------------+
|
||||
bool UpdateRSI()
|
||||
{
|
||||
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rsi_current = rsi_buffer[0]; // Current bar
|
||||
rsi_prev = rsi_buffer[1]; // Previous bar
|
||||
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trail SL behind favorable price (every tick when enabled) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
if(TrailingStopDistancePoints <= 0.0)
|
||||
return;
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
if(point <= 0.0)
|
||||
return;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
const double trail_dist = TrailingStopDistancePoints * point;
|
||||
const double activation_pts = (TrailingActivationPoints > 0.0)
|
||||
? TrailingActivationPoints
|
||||
: TrailingStopDistancePoints;
|
||||
const double activation = activation_pts * point;
|
||||
const long stops_level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double cur_sl = PositionGetDouble(POSITION_SL);
|
||||
const double cur_tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
if(bid - entry <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_sl < min_dist)
|
||||
new_sl = NormalizeDouble(bid - min_dist, digits);
|
||||
|
||||
if(new_sl >= bid || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl <= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
if(entry - ask <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_sl - ask < min_dist)
|
||||
new_sl = NormalizeDouble(ask + min_dist, digits);
|
||||
|
||||
if(new_sl <= ask || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl >= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Sync ticket/state if a position exists after restart |
|
||||
//+------------------------------------------------------------------+
|
||||
void ResyncPositionFromMarket()
|
||||
{
|
||||
if(position_open)
|
||||
return;
|
||||
ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(t == 0 || !PositionSelectByTicket(t))
|
||||
return;
|
||||
position_ticket = (int)t;
|
||||
position_open = true;
|
||||
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check existing position for exit conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckExistingPosition()
|
||||
{
|
||||
if(!position_open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if position still exists with correct magic number AND symbol for THIS EA
|
||||
if(!PositionSelectByTicketSymbolAndMagic(position_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit conditions based on RSI target
|
||||
if(current_position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
// Check if RSI is against the position (below oversold)
|
||||
if(rsi_current < RSI_Oversold)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit long position when RSI reaches buy target
|
||||
if(rsi_current >= RSI_Target_Buy)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(current_position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
// Check if RSI is against the position (above overbought)
|
||||
if(rsi_current > RSI_Overbought)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit short position when RSI reaches sell target
|
||||
if(rsi_current <= RSI_Target_Sell)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check for entry signals |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckEntrySignals()
|
||||
{
|
||||
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
|
||||
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
}
|
||||
|
||||
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
|
||||
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought)
|
||||
{
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
// Verify no position exists for THIS EA (magic number) on THIS symbol before opening
|
||||
if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Position already exists for this EA
|
||||
}
|
||||
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
|
||||
{
|
||||
ulong new_ticket = trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
// Verify position was opened for THIS EA (magic number) on THIS symbol
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = new_ticket;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_BUY;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error: Position opened but doesn't match EA magic number or symbol");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
// Verify no position exists for THIS EA (magic number) on THIS symbol before opening
|
||||
if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Position already exists for this EA
|
||||
}
|
||||
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
|
||||
{
|
||||
ulong new_ticket = trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
// Verify position was opened for THIS EA (magic number) on THIS symbol
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = new_ticket;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_SELL;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error: Position opened but doesn't match EA magic number or symbol");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close current position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ClosePosition()
|
||||
{
|
||||
// Close position using helper that verifies symbol AND magic number for THIS EA
|
||||
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position doesn't exist or wrong magic number - reset tracking
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIScalping.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.01"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
//--- Input parameters
|
||||
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis
|
||||
input int RSI_Period = 14; // RSI Period
|
||||
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
|
||||
input double RSI_Overbought = 54; // RSI Overbought Level
|
||||
input double RSI_Oversold = 73; // RSI Oversold Level
|
||||
input double RSI_Target_Buy = 87; // RSI Target for Buy Exit
|
||||
input double RSI_Target_Sell = 33; // RSI Target for Sell Exit
|
||||
input int BarsToWait = 1; // Bars to wait when RSI goes against position
|
||||
input double LotSize = 5; // Lot Size
|
||||
input int MagicNumber = 125421321; // Magic Number
|
||||
input int Slippage = 3; // Slippage in points
|
||||
|
||||
input group "=== Trailing stop ==="
|
||||
input bool UseTrailingStop = true; // move SL behind bid/ask while in profit
|
||||
input double TrailingStopDistancePoints = 900.0; // SL distance from bid/ask (points)
|
||||
input double TrailingActivationPoints = 950.0; // min profit before trailing (0 = same as distance)
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int rsi_handle;
|
||||
double rsi_buffer[];
|
||||
double rsi_prev, rsi_current, rsi_two_bars_ago;
|
||||
bool position_open = false;
|
||||
int position_ticket = 0;
|
||||
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
|
||||
datetime last_bar_time = 0;
|
||||
bool rsi_against_position = false;
|
||||
int bars_against_count = 0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize RSI indicator
|
||||
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
|
||||
if(rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
// Initialize trade object
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Allocate arrays
|
||||
ArraySetAsSeries(rsi_buffer, true);
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(rsi_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(rsi_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
|
||||
return;
|
||||
|
||||
const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
|
||||
const bool new_bar = (current_bar_time != last_bar_time);
|
||||
const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
|
||||
|
||||
if(!in_pos && !new_bar)
|
||||
return;
|
||||
|
||||
if(!UpdateRSI())
|
||||
return;
|
||||
|
||||
if(in_pos && UseTrailingStop)
|
||||
ApplyTrailingStop();
|
||||
|
||||
if(!new_bar)
|
||||
return;
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
ResyncPositionFromMarket();
|
||||
CheckExistingPosition();
|
||||
|
||||
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
CheckEntrySignals();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update RSI values |
|
||||
//+------------------------------------------------------------------+
|
||||
bool UpdateRSI()
|
||||
{
|
||||
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rsi_current = rsi_buffer[0]; // Current bar
|
||||
rsi_prev = rsi_buffer[1]; // Previous bar
|
||||
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trail SL behind favorable price (every tick when enabled) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
if(TrailingStopDistancePoints <= 0.0)
|
||||
return;
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
if(point <= 0.0)
|
||||
return;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
const double trail_dist = TrailingStopDistancePoints * point;
|
||||
const double activation_pts = (TrailingActivationPoints > 0.0)
|
||||
? TrailingActivationPoints
|
||||
: TrailingStopDistancePoints;
|
||||
const double activation = activation_pts * point;
|
||||
const long stops_level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double cur_sl = PositionGetDouble(POSITION_SL);
|
||||
const double cur_tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
if(bid - entry <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_sl < min_dist)
|
||||
new_sl = NormalizeDouble(bid - min_dist, digits);
|
||||
|
||||
if(new_sl >= bid || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl <= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
if(entry - ask <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_sl - ask < min_dist)
|
||||
new_sl = NormalizeDouble(ask + min_dist, digits);
|
||||
|
||||
if(new_sl <= ask || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl >= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Sync ticket/state if a position exists after restart |
|
||||
//+------------------------------------------------------------------+
|
||||
void ResyncPositionFromMarket()
|
||||
{
|
||||
if(position_open)
|
||||
return;
|
||||
ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(t == 0 || !PositionSelectByTicket(t))
|
||||
return;
|
||||
position_ticket = (int)t;
|
||||
position_open = true;
|
||||
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check existing position for exit conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckExistingPosition()
|
||||
{
|
||||
if(!position_open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if position still exists with correct magic number AND symbol for THIS EA
|
||||
if(!PositionSelectByTicketSymbolAndMagic(position_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit conditions based on RSI target
|
||||
if(current_position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
// Check if RSI is against the position (below oversold)
|
||||
if(rsi_current < RSI_Oversold)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit long position when RSI reaches buy target
|
||||
if(rsi_current >= RSI_Target_Buy)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(current_position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
// Check if RSI is against the position (above overbought)
|
||||
if(rsi_current > RSI_Overbought)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit short position when RSI reaches sell target
|
||||
if(rsi_current <= RSI_Target_Sell)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check for entry signals |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckEntrySignals()
|
||||
{
|
||||
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
|
||||
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
}
|
||||
|
||||
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
|
||||
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought)
|
||||
{
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
// Verify no position exists for THIS EA (magic number) on THIS symbol before opening
|
||||
if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Position already exists for this EA
|
||||
}
|
||||
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
|
||||
{
|
||||
ulong new_ticket = trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
// Verify position was opened for THIS EA (magic number) on THIS symbol
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = new_ticket;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_BUY;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error: Position opened but doesn't match EA magic number or symbol");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
// Verify no position exists for THIS EA (magic number) on THIS symbol before opening
|
||||
if(PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
return; // Position already exists for this EA
|
||||
}
|
||||
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
|
||||
{
|
||||
ulong new_ticket = trade.ResultOrder();
|
||||
if(new_ticket > 0)
|
||||
{
|
||||
// Verify position was opened for THIS EA (magic number) on THIS symbol
|
||||
if(PositionSelectByTicketSymbolAndMagic(new_ticket, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = new_ticket;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_SELL;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("Error: Position opened but doesn't match EA magic number or symbol");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close current position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ClosePosition()
|
||||
{
|
||||
// Close position using helper that verifies symbol AND magic number for THIS EA
|
||||
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Position doesn't exist or wrong magic number - reset tracking
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
@@ -0,0 +1,34 @@
|
||||
; RSIScalpingXAUUSD-trailing — matches main.mq5 v1.06 default inputs
|
||||
; saved on 2026.05.01 22:32:43
|
||||
; MT5 Strategy Tester: Inputs → Load
|
||||
;
|
||||
TimeFrame=16385||15||0||16385||N
|
||||
RSI_Period=14||14||1||140||N
|
||||
RSI_Applied_Price=1||1||0||7||N
|
||||
RSI_Overbought=71.0||0||2||100||N
|
||||
RSI_Oversold=57.0||0||2||100||N
|
||||
UseEntrySlopeFilter=false||false||0||true||N
|
||||
EntryMinSlopePerBar=1.0||1.0||0.100000||10.000000||N
|
||||
RSI_Target_Buy=80.0||0||2||100||N
|
||||
RSI_Target_Sell=57.0||0||2||100||N
|
||||
BarsToWait=1||0||1||50||N
|
||||
LotSize=0.1||0.1||0.010000||1.000000||N
|
||||
MagicNumber=129102315||129102315||1||1291023150||N
|
||||
Slippage=3||3||1||30||N
|
||||
; === Reversal escape (intrabar, multi-signal) ===
|
||||
UseReversalEscape=true||false||0||true||N
|
||||
ReversalEscapeTimeFrame=5||0||0||49153||N
|
||||
ReversalATRPeriod=14||14||1||140||N
|
||||
ReversalAdverseAtrMult=5.25||5.25||0.525000||52.500000||N
|
||||
ReversalSignsRequired=1||2||1||20||N
|
||||
ReversalRsiVelocity=16.0||16.0||1.600000||160.000000||N
|
||||
ReversalBodyAtrMult=5.1||5.1||0.510000||51.000000||N
|
||||
; === Trailing stop ===
|
||||
UseTrailingStop=true||false||0||true||N
|
||||
TrailingStopDistancePoints=71.0||100||100||5000||Y
|
||||
TrailingActivationPoints=41.0||100||100||5000||Y
|
||||
; === Intrabar give-back (same bar reversals) ===
|
||||
UseGiveBackExit=true||false||0||true||N
|
||||
GiveBackATRPeriod=14||14||1||140||N
|
||||
GiveBackAtrMult=0.1||1.85||0.185000||18.500000||N
|
||||
GiveBackRequireMfe=true||false||0||true||N
|
||||
@@ -0,0 +1,645 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSIScalping.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.06"
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
|
||||
//--- Input parameters
|
||||
input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis
|
||||
input int RSI_Period = 14; // RSI Period
|
||||
input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price
|
||||
input double RSI_Overbought = 71; // RSI Overbought Level
|
||||
input double RSI_Oversold = 57; // RSI Oversold Level
|
||||
input bool UseEntrySlopeFilter = false; // require RSI momentum on entry bars
|
||||
input double EntryMinSlopePerBar = 1.0; // minimum RSI delta per bar for entry
|
||||
input double RSI_Target_Buy = 80; // RSI Target for Buy Exit
|
||||
input double RSI_Target_Sell = 57; // RSI Target for Sell Exit
|
||||
input int BarsToWait = 1; // Bars to wait when RSI goes against position
|
||||
input double LotSize = 0.1; // Lot Size
|
||||
input int MagicNumber = 129102315; // Magic Number
|
||||
input int Slippage = 3; // Slippage in points
|
||||
|
||||
input group "=== Reversal escape (intrabar, multi-signal) ==="
|
||||
input bool UseReversalEscape = true; // run while in position every tick (now uses ReversalEscapeTimeFrame)
|
||||
input ENUM_TIMEFRAMES ReversalEscapeTimeFrame = PERIOD_M5; // ATR / RSI velocity / bar signs on this TF (not signal TF)
|
||||
input int ReversalATRPeriod = 14; // ATR lookback on ReversalEscapeTimeFrame
|
||||
input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR
|
||||
input int ReversalSignsRequired = 1; // how many independent signs must align
|
||||
input double ReversalRsiVelocity = 16.0; // RSI points drop (long) / rise (short) vs prior buffer
|
||||
input double ReversalBodyAtrMult = 5.1; // last closed bar body >= this * ATR counts as one sign
|
||||
|
||||
input group "=== Trailing stop ==="
|
||||
input bool UseTrailingStop = true; // move SL behind price while in profit
|
||||
input double TrailingStopDistancePoints = 71.0; // SL distance from current bid/ask (points)
|
||||
input double TrailingActivationPoints = 41.0; // min profit before trailing (0 = same as distance)
|
||||
|
||||
input group "=== Intrabar give-back (same bar reversals) ==="
|
||||
input bool UseGiveBackExit = true; // exit if price gives back vs best tick since entry
|
||||
input int GiveBackATRPeriod = 14; // ATR period on signal timeframe (Wilder)
|
||||
input double GiveBackAtrMult = 0.1; // close when retrace from peak/trough >= this * ATR
|
||||
input bool GiveBackRequireMfe = true; // long: only after bid was above entry; short: ask below entry
|
||||
|
||||
//--- Global variables
|
||||
CTrade trade;
|
||||
int rsi_handle;
|
||||
int rsi_escape_handle = INVALID_HANDLE; // RSI on ReversalEscapeTimeFrame (may alias rsi_handle)
|
||||
double rsi_buffer[];
|
||||
double rsi_prev, rsi_current, rsi_two_bars_ago;
|
||||
bool position_open = false;
|
||||
int position_ticket = 0;
|
||||
ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY;
|
||||
datetime last_bar_time = 0;
|
||||
bool rsi_against_position = false;
|
||||
int bars_against_count = 0;
|
||||
|
||||
ulong g_giveback_track_ticket = 0;
|
||||
double g_peak_bid_since_entry = 0.0;
|
||||
double g_trough_ask_since_entry = 0.0;
|
||||
|
||||
void ResetIntrabarGiveBackState();
|
||||
void TryGiveBackExit();
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize RSI indicator
|
||||
rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price);
|
||||
if(rsi_handle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
if(ReversalEscapeTimeFrame == TimeFrame)
|
||||
rsi_escape_handle = rsi_handle;
|
||||
else
|
||||
{
|
||||
rsi_escape_handle = iRSI(_Symbol, ReversalEscapeTimeFrame, RSI_Period, RSI_Applied_Price);
|
||||
if(rsi_escape_handle == INVALID_HANDLE)
|
||||
{
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize trade object
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(Slippage);
|
||||
trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
// Allocate arrays
|
||||
ArraySetAsSeries(rsi_buffer, true);
|
||||
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(rsi_escape_handle != INVALID_HANDLE && rsi_escape_handle != rsi_handle)
|
||||
IndicatorRelease(rsi_escape_handle);
|
||||
if(rsi_handle != INVALID_HANDLE)
|
||||
IndicatorRelease(rsi_handle);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
if(Bars(_Symbol, TimeFrame) < RSI_Period + 2)
|
||||
return;
|
||||
|
||||
const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0);
|
||||
const bool new_bar = (current_bar_time != last_bar_time);
|
||||
const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber);
|
||||
|
||||
if(!in_pos && !new_bar)
|
||||
return;
|
||||
|
||||
if(!UpdateRSI())
|
||||
return;
|
||||
|
||||
if(in_pos && UseReversalEscape)
|
||||
TryReversalEscape();
|
||||
|
||||
if(in_pos && UseGiveBackExit)
|
||||
TryGiveBackExit();
|
||||
|
||||
if(in_pos && UseTrailingStop)
|
||||
ApplyTrailingStop();
|
||||
|
||||
if(!new_bar)
|
||||
return;
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
|
||||
ResyncPositionFromMarket();
|
||||
CheckExistingPosition();
|
||||
|
||||
if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
CheckEntrySignals();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Update RSI values |
|
||||
//+------------------------------------------------------------------+
|
||||
bool UpdateRSI()
|
||||
{
|
||||
if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
rsi_current = rsi_buffer[0]; // Current bar
|
||||
rsi_prev = rsi_buffer[1]; // Previous bar
|
||||
rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Wilder ATR in price units (signal timeframe) |
|
||||
//+------------------------------------------------------------------+
|
||||
double ATRPriceOnTF(const int period)
|
||||
{
|
||||
if(period < 1)
|
||||
return 0.0;
|
||||
MqlRates rates[];
|
||||
const int need = period + 2;
|
||||
if(CopyRates(_Symbol, TimeFrame, 0, need, rates) < need)
|
||||
return 0.0;
|
||||
ArraySetAsSeries(rates, true);
|
||||
double sum = 0.0;
|
||||
for(int i = 1; i <= period; i++)
|
||||
{
|
||||
const double hl = rates[i].high - rates[i].low;
|
||||
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
|
||||
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
|
||||
sum += MathMax(hl, MathMax(hc, lc));
|
||||
}
|
||||
return sum / (double)period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Wilder ATR on arbitrary timeframe |
|
||||
//+------------------------------------------------------------------+
|
||||
double WilderATRForTF(const ENUM_TIMEFRAMES tf, const int period)
|
||||
{
|
||||
if(period < 1)
|
||||
return 0.0;
|
||||
MqlRates rates[];
|
||||
const int need = period + 2;
|
||||
if(CopyRates(_Symbol, tf, 0, need, rates) < need)
|
||||
return 0.0;
|
||||
ArraySetAsSeries(rates, true);
|
||||
double sum = 0.0;
|
||||
for(int i = 1; i <= period; i++)
|
||||
{
|
||||
const double hl = rates[i].high - rates[i].low;
|
||||
const double hc = MathAbs(rates[i].high - rates[i + 1].close);
|
||||
const double lc = MathAbs(rates[i].low - rates[i + 1].close);
|
||||
sum += MathMax(hl, MathMax(hc, lc));
|
||||
}
|
||||
return sum / (double)period;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Independent adverse signs (need ReversalSignsRequired to exit) |
|
||||
//+------------------------------------------------------------------+
|
||||
int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr)
|
||||
{
|
||||
if(atr <= 0.0)
|
||||
return 0;
|
||||
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
int signs = 0;
|
||||
|
||||
double rsi_esc[];
|
||||
ArraySetAsSeries(rsi_esc, true);
|
||||
const bool ok_esc_rsi = (rsi_escape_handle != INVALID_HANDLE &&
|
||||
CopyBuffer(rsi_escape_handle, 0, 0, 2, rsi_esc) >= 2);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(entry - bid >= ReversalAdverseAtrMult * atr)
|
||||
signs++;
|
||||
if(ok_esc_rsi && rsi_esc[1] - rsi_esc[0] >= ReversalRsiVelocity)
|
||||
signs++;
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
if(ask - entry >= ReversalAdverseAtrMult * atr)
|
||||
signs++;
|
||||
if(ok_esc_rsi && rsi_esc[0] - rsi_esc[1] >= ReversalRsiVelocity)
|
||||
signs++;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
|
||||
MqlRates r[];
|
||||
if(CopyRates(_Symbol, ReversalEscapeTimeFrame, 0, 4, r) >= 4)
|
||||
{
|
||||
ArraySetAsSeries(r, true);
|
||||
const double body = MathAbs(r[1].close - r[1].open);
|
||||
if(body >= ReversalBodyAtrMult * atr)
|
||||
{
|
||||
if(ptype == POSITION_TYPE_BUY && r[1].close < r[1].open)
|
||||
signs++;
|
||||
else if(ptype == POSITION_TYPE_SELL && r[1].close > r[1].open)
|
||||
signs++;
|
||||
}
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(r[1].close < r[2].close && r[2].close < r[3].close)
|
||||
signs++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(r[1].close > r[2].close && r[2].close > r[3].close)
|
||||
signs++;
|
||||
}
|
||||
}
|
||||
|
||||
return signs;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Cut losers fast on violent reversals (evaluated every tick) |
|
||||
//+------------------------------------------------------------------+
|
||||
void TryReversalEscape()
|
||||
{
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double atr = WilderATRForTF(ReversalEscapeTimeFrame, ReversalATRPeriod);
|
||||
if(atr <= 0.0)
|
||||
return;
|
||||
|
||||
const int n = CountReversalEscapeSigns(ptype, atr);
|
||||
if(n < ReversalSignsRequired)
|
||||
return;
|
||||
|
||||
ClosePosition();
|
||||
Print("RSIScalpingXAUUSD: reversal escape TF=", EnumToString(ReversalEscapeTimeFrame),
|
||||
" signs=", n, " need=", ReversalSignsRequired,
|
||||
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)));
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Reset give-back peak/trough tracking |
|
||||
//+------------------------------------------------------------------+
|
||||
void ResetIntrabarGiveBackState()
|
||||
{
|
||||
g_giveback_track_ticket = 0;
|
||||
g_peak_bid_since_entry = 0.0;
|
||||
g_trough_ask_since_entry = 0.0;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Exit when intrabar price gives back sharply vs best since entry |
|
||||
//+------------------------------------------------------------------+
|
||||
void TryGiveBackExit()
|
||||
{
|
||||
if(!UseGiveBackExit || GiveBackAtrMult <= 0.0)
|
||||
return;
|
||||
|
||||
const ulong ticket = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(ticket == 0 || !PositionSelectByTicket(ticket))
|
||||
{
|
||||
ResetIntrabarGiveBackState();
|
||||
return;
|
||||
}
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
|
||||
if(g_giveback_track_ticket != ticket)
|
||||
{
|
||||
g_giveback_track_ticket = ticket;
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
g_peak_bid_since_entry = bid;
|
||||
g_trough_ask_since_entry = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_trough_ask_since_entry = ask;
|
||||
g_peak_bid_since_entry = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
const double atr = ATRPriceOnTF(GiveBackATRPeriod);
|
||||
if(atr <= 0.0)
|
||||
return;
|
||||
|
||||
const double threshold = GiveBackAtrMult * atr;
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(bid > g_peak_bid_since_entry)
|
||||
g_peak_bid_since_entry = bid;
|
||||
if(GiveBackRequireMfe && g_peak_bid_since_entry <= entry)
|
||||
return;
|
||||
if(g_peak_bid_since_entry - bid >= threshold)
|
||||
{
|
||||
ClosePosition();
|
||||
Print("RSIScalpingXAUUSD: give-back exit BUY retrace=",
|
||||
DoubleToString(g_peak_bid_since_entry - bid, digits),
|
||||
" thr=", DoubleToString(threshold, digits));
|
||||
}
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
if(ask < g_trough_ask_since_entry)
|
||||
g_trough_ask_since_entry = ask;
|
||||
if(GiveBackRequireMfe && g_trough_ask_since_entry >= entry)
|
||||
return;
|
||||
if(ask - g_trough_ask_since_entry >= threshold)
|
||||
{
|
||||
ClosePosition();
|
||||
Print("RSIScalpingXAUUSD: give-back exit SELL retrace=",
|
||||
DoubleToString(ask - g_trough_ask_since_entry, digits),
|
||||
" thr=", DoubleToString(threshold, digits));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trail SL behind favorable price (every tick when enabled) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ApplyTrailingStop()
|
||||
{
|
||||
if(TrailingStopDistancePoints <= 0.0)
|
||||
return;
|
||||
if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||||
if(point <= 0.0)
|
||||
return;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||||
const double trail_dist = TrailingStopDistancePoints * point;
|
||||
const double activation_pts = (TrailingActivationPoints > 0.0)
|
||||
? TrailingActivationPoints
|
||||
: TrailingStopDistancePoints;
|
||||
const double activation = activation_pts * point;
|
||||
const long stops_level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double cur_sl = PositionGetDouble(POSITION_SL);
|
||||
const double cur_tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
if(bid - entry <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_sl < min_dist)
|
||||
new_sl = NormalizeDouble(bid - min_dist, digits);
|
||||
|
||||
if(new_sl >= bid || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl <= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
if(entry - ask <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_sl - ask < min_dist)
|
||||
new_sl = NormalizeDouble(ask + min_dist, digits);
|
||||
|
||||
if(new_sl <= ask || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl >= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(trade, _Symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
}
|
||||
|
||||
void ResyncPositionFromMarket()
|
||||
{
|
||||
if(position_open)
|
||||
return;
|
||||
ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(t == 0 || !PositionSelectByTicket(t))
|
||||
return;
|
||||
position_ticket = (int)t;
|
||||
position_open = true;
|
||||
current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check existing position for exit conditions |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckExistingPosition()
|
||||
{
|
||||
if(!position_open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if position still exists with correct magic number
|
||||
if(!PositionSelectByTicketAndMagic(position_ticket, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit conditions based on RSI target
|
||||
if(current_position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
// Check if RSI is against the position (below oversold)
|
||||
if(rsi_current < RSI_Oversold)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit long position when RSI reaches buy target
|
||||
if(rsi_current >= RSI_Target_Buy)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(current_position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
// Check if RSI is against the position (above overbought)
|
||||
if(rsi_current > RSI_Overbought)
|
||||
{
|
||||
if(!rsi_against_position)
|
||||
{
|
||||
rsi_against_position = true;
|
||||
bars_against_count = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
bars_against_count++;
|
||||
}
|
||||
|
||||
// Close position if RSI has been against for Y bars
|
||||
if(bars_against_count >= BarsToWait)
|
||||
{
|
||||
ClosePosition();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// RSI is no longer against the position, reset counter
|
||||
if(rsi_against_position)
|
||||
{
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
}
|
||||
|
||||
// Exit short position when RSI reaches sell target
|
||||
if(rsi_current <= RSI_Target_Sell)
|
||||
{
|
||||
ClosePosition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Check for entry signals |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckEntrySignals()
|
||||
{
|
||||
const double upSlope1 = rsi_prev - rsi_two_bars_ago; // older->prev
|
||||
const double upSlope2 = rsi_current - rsi_prev; // prev->current
|
||||
const double dnSlope1 = rsi_two_bars_ago - rsi_prev; // older->prev
|
||||
const double dnSlope2 = rsi_prev - rsi_current; // prev->current
|
||||
const bool buySlopeOk = (!UseEntrySlopeFilter) || (upSlope1 >= EntryMinSlopePerBar && upSlope2 >= EntryMinSlopePerBar);
|
||||
const bool sellSlopeOk = (!UseEntrySlopeFilter) || (dnSlope1 >= EntryMinSlopePerBar && dnSlope2 >= EntryMinSlopePerBar);
|
||||
|
||||
// Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover)
|
||||
if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold && buySlopeOk)
|
||||
{
|
||||
OpenBuyPosition();
|
||||
}
|
||||
|
||||
// Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover)
|
||||
if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought && sellSlopeOk)
|
||||
{
|
||||
OpenSellPosition();
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open buy position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenBuyPosition()
|
||||
{
|
||||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
|
||||
if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy"))
|
||||
{
|
||||
const ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = (int)t;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_BUY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Open sell position |
|
||||
//+------------------------------------------------------------------+
|
||||
void OpenSellPosition()
|
||||
{
|
||||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
|
||||
if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell"))
|
||||
{
|
||||
const ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber);
|
||||
if(t > 0 && PositionSelectByTicketSymbolAndMagic(t, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_ticket = (int)t;
|
||||
position_open = true;
|
||||
current_position_type = POSITION_TYPE_SELL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Close current position |
|
||||
//+------------------------------------------------------------------+
|
||||
void ClosePosition()
|
||||
{
|
||||
if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
ResetIntrabarGiveBackState();
|
||||
return;
|
||||
}
|
||||
if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber))
|
||||
{
|
||||
position_open = false;
|
||||
position_ticket = 0;
|
||||
rsi_against_position = false;
|
||||
bars_against_count = 0;
|
||||
ResetIntrabarGiveBackState();
|
||||
return;
|
||||
}
|
||||
Print("RSIScalpingXAUUSD: close failed (will retry on next bar). retcode=",
|
||||
trade.ResultRetcode(), " lastError=", GetLastError());
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.4 KiB |
@@ -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 <Trade\Trade.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
|
||||
//--- Input Parameters
|
||||
input group "=== Trading Settings ==="
|
||||
input string InpSymbol = "XAUUSD"; // Default gold; same numbers as secret_sauce.set (that file uses BTCUSD as symbol)
|
||||
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
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -0,0 +1,284 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H4; // Higher timeframe for MA/cross points
|
||||
input int InpMAPeriod = 150; // MA period
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_SMMA; // MA method
|
||||
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
|
||||
input int InpHTFBarsToScan = 1200; // HTF bars to scan for crossings
|
||||
input double InpLineTouchTolerance = 170; // Pullback touch tolerance (points)
|
||||
input double InpBreakBuffer = 90; // Break confirmation buffer (points)
|
||||
input double InpLots = 0.10; // Position size
|
||||
input long InpMagic = 26042501; // Magic number
|
||||
input bool InpDrawTrendline = true; // Draw detected trendline
|
||||
|
||||
CTrade trade;
|
||||
|
||||
int g_maHandle = INVALID_HANDLE;
|
||||
datetime g_lastBarTime = 0;
|
||||
string g_lineName = "SimpleTrendline_Basis";
|
||||
|
||||
struct TrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double p1;
|
||||
double p2;
|
||||
double p3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
bool IsNewBar()
|
||||
{
|
||||
datetime t = iTime(_Symbol, _Period, 0);
|
||||
if(t == 0)
|
||||
return false;
|
||||
if(t != g_lastBarTime)
|
||||
{
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int FindRecentCrossPoints(datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(InpHTFBarsToScan, InpMAPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
|
||||
int copiedRates = CopyRates(_Symbol, InpHigherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(g_maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
for(int i = 2; i < bars - 1; i++)
|
||||
{
|
||||
double d0 = rates[i].close - maBuf[i];
|
||||
double d1 = rates[i + 1].close - maBuf[i + 1];
|
||||
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
|
||||
{
|
||||
int n = ArraySize(times);
|
||||
ArrayResize(times, n + 1);
|
||||
ArrayResize(prices, n + 1);
|
||||
times[n] = rates[i].time;
|
||||
prices[n] = rates[i].close;
|
||||
if(ArraySize(times) >= 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ArraySize(times);
|
||||
}
|
||||
|
||||
bool BuildTrendlineFrom3Points(TrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
int n = FindRecentCrossPoints(ts, ps);
|
||||
if(n < 3)
|
||||
return false;
|
||||
|
||||
// We collected from recent to older in series order.
|
||||
// Re-map as oldest -> newest to stabilize slope direction.
|
||||
datetime tOld[3];
|
||||
double pOld[3];
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
tOld[i] = ts[2 - i];
|
||||
pOld[i] = ps[2 - i];
|
||||
}
|
||||
|
||||
long t0 = (long)tOld[0];
|
||||
double x1 = 0.0;
|
||||
double x2 = (double)((long)tOld[1] - t0);
|
||||
double x3 = (double)((long)tOld[2] - t0);
|
||||
double y1 = pOld[0];
|
||||
double y2 = pOld[1];
|
||||
double y3 = pOld[2];
|
||||
|
||||
double sx = x1 + x2 + x3;
|
||||
double sy = y1 + y2 + y3;
|
||||
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
|
||||
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
|
||||
|
||||
double den = 3.0 * sxx - sx * sx;
|
||||
if(MathAbs(den) < 1e-10)
|
||||
return false;
|
||||
|
||||
m.a = (3.0 * sxy - sx * sy) / den;
|
||||
m.b = (sy - m.a * sx) / 3.0;
|
||||
|
||||
m.t1 = tOld[0];
|
||||
m.t2 = tOld[1];
|
||||
m.t3 = tOld[2];
|
||||
m.p1 = pOld[0];
|
||||
m.p2 = pOld[1];
|
||||
m.p3 = pOld[2];
|
||||
m.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double TrendlinePriceAtTime(const TrendlineModel &m, datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void DrawTrendline(const TrendlineModel &m)
|
||||
{
|
||||
if(!InpDrawTrendline || !m.valid)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(_Symbol, _Period, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(_Period) * 20;
|
||||
|
||||
double pStart = TrendlinePriceAtTime(m, tStart);
|
||||
double pEnd = TrendlinePriceAtTime(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, g_lineName) < 0)
|
||||
ObjectCreate(0, g_lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, g_lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, g_lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
bool GetCurrentPosition(long &type, double &volume)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return false;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
return false;
|
||||
type = PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TryExitOnBreak(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(!GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(_Symbol, _Period, 1);
|
||||
datetime t1 = iTime(_Symbol, _Period, 1);
|
||||
double line1 = TrendlinePriceAtTime(m, t1);
|
||||
double buf = InpBreakBuffer * _Point;
|
||||
|
||||
bool closePos = false;
|
||||
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
|
||||
closePos = true;
|
||||
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
|
||||
closePos = true;
|
||||
|
||||
if(closePos)
|
||||
trade.PositionClose(_Symbol);
|
||||
}
|
||||
|
||||
void TryPullbackEntry(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
MqlRates bars1[], bars2[];
|
||||
if(CopyRates(_Symbol, _Period, 1, 1, bars1) != 1)
|
||||
return;
|
||||
if(CopyRates(_Symbol, _Period, 2, 1, bars2) != 1)
|
||||
return;
|
||||
if(ArraySize(bars1) < 1 || ArraySize(bars2) < 1)
|
||||
return;
|
||||
|
||||
MqlRates b1 = bars1[0];
|
||||
MqlRates b2 = bars2[0];
|
||||
|
||||
double line1 = TrendlinePriceAtTime(m, b1.time);
|
||||
double tol = InpLineTouchTolerance * _Point;
|
||||
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1.low <= (line1 + tol));
|
||||
bool reclaim = (b1.close > line1);
|
||||
bool bullish = (b1.close > b1.open);
|
||||
bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy");
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1.high >= (line1 - tol));
|
||||
bool reject = (b1.close < line1);
|
||||
bool bearish = (b1.close < b1.open);
|
||||
bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_maHandle = iMA(_Symbol, InpHigherTF, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice);
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
g_lastBarTime = 0;
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(g_maHandle);
|
||||
if(ObjectFind(0, g_lineName) >= 0)
|
||||
ObjectDelete(0, g_lineName);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(!IsNewBar())
|
||||
return;
|
||||
|
||||
TrendlineModel m;
|
||||
if(!BuildTrendlineFrom3Points(m))
|
||||
return;
|
||||
|
||||
DrawTrendline(m);
|
||||
TryExitOnBreak(m);
|
||||
TryPullbackEntry(m);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
; SimpleTrendline.mq5 optimization preset
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback entries + break exits (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||0||16388||Y
|
||||
InpMAPeriod=50||20||5||200||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=400||200||100||1200||Y
|
||||
InpLineTouchTolerance=100.0||30.0||10.0||300.0||Y
|
||||
InpBreakBuffer=30.0||5.0||5.0||120.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
@@ -0,0 +1,284 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_M15; // Higher timeframe for MA/cross points
|
||||
input int InpMAPeriod = 65; // MA period
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_LWMA; // MA method
|
||||
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
|
||||
input int InpHTFBarsToScan = 1200; // HTF bars to scan for crossings
|
||||
input double InpLineTouchTolerance = 100; // Pullback touch tolerance (points)
|
||||
input double InpBreakBuffer = 80; // Break confirmation buffer (points)
|
||||
input double InpLots = 0.10; // Position size
|
||||
input long InpMagic = 26042501; // Magic number
|
||||
input bool InpDrawTrendline = true; // Draw detected trendline
|
||||
|
||||
CTrade trade;
|
||||
|
||||
int g_maHandle = INVALID_HANDLE;
|
||||
datetime g_lastBarTime = 0;
|
||||
string g_lineName = "SimpleTrendline_Basis";
|
||||
|
||||
struct TrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double p1;
|
||||
double p2;
|
||||
double p3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
bool IsNewBar()
|
||||
{
|
||||
datetime t = iTime(_Symbol, _Period, 0);
|
||||
if(t == 0)
|
||||
return false;
|
||||
if(t != g_lastBarTime)
|
||||
{
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int FindRecentCrossPoints(datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(InpHTFBarsToScan, InpMAPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
|
||||
int copiedRates = CopyRates(_Symbol, InpHigherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(g_maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
for(int i = 2; i < bars - 1; i++)
|
||||
{
|
||||
double d0 = rates[i].close - maBuf[i];
|
||||
double d1 = rates[i + 1].close - maBuf[i + 1];
|
||||
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
|
||||
{
|
||||
int n = ArraySize(times);
|
||||
ArrayResize(times, n + 1);
|
||||
ArrayResize(prices, n + 1);
|
||||
times[n] = rates[i].time;
|
||||
prices[n] = rates[i].close;
|
||||
if(ArraySize(times) >= 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ArraySize(times);
|
||||
}
|
||||
|
||||
bool BuildTrendlineFrom3Points(TrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
int n = FindRecentCrossPoints(ts, ps);
|
||||
if(n < 3)
|
||||
return false;
|
||||
|
||||
// We collected from recent to older in series order.
|
||||
// Re-map as oldest -> newest to stabilize slope direction.
|
||||
datetime tOld[3];
|
||||
double pOld[3];
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
tOld[i] = ts[2 - i];
|
||||
pOld[i] = ps[2 - i];
|
||||
}
|
||||
|
||||
long t0 = (long)tOld[0];
|
||||
double x1 = 0.0;
|
||||
double x2 = (double)((long)tOld[1] - t0);
|
||||
double x3 = (double)((long)tOld[2] - t0);
|
||||
double y1 = pOld[0];
|
||||
double y2 = pOld[1];
|
||||
double y3 = pOld[2];
|
||||
|
||||
double sx = x1 + x2 + x3;
|
||||
double sy = y1 + y2 + y3;
|
||||
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
|
||||
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
|
||||
|
||||
double den = 3.0 * sxx - sx * sx;
|
||||
if(MathAbs(den) < 1e-10)
|
||||
return false;
|
||||
|
||||
m.a = (3.0 * sxy - sx * sy) / den;
|
||||
m.b = (sy - m.a * sx) / 3.0;
|
||||
|
||||
m.t1 = tOld[0];
|
||||
m.t2 = tOld[1];
|
||||
m.t3 = tOld[2];
|
||||
m.p1 = pOld[0];
|
||||
m.p2 = pOld[1];
|
||||
m.p3 = pOld[2];
|
||||
m.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double TrendlinePriceAtTime(const TrendlineModel &m, datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void DrawTrendline(const TrendlineModel &m)
|
||||
{
|
||||
if(!InpDrawTrendline || !m.valid)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(_Symbol, _Period, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(_Period) * 20;
|
||||
|
||||
double pStart = TrendlinePriceAtTime(m, tStart);
|
||||
double pEnd = TrendlinePriceAtTime(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, g_lineName) < 0)
|
||||
ObjectCreate(0, g_lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, g_lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, g_lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
bool GetCurrentPosition(long &type, double &volume)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return false;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
return false;
|
||||
type = PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
void TryExitOnBreak(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(!GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(_Symbol, _Period, 1);
|
||||
datetime t1 = iTime(_Symbol, _Period, 1);
|
||||
double line1 = TrendlinePriceAtTime(m, t1);
|
||||
double buf = InpBreakBuffer * _Point;
|
||||
|
||||
bool closePos = false;
|
||||
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
|
||||
closePos = true;
|
||||
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
|
||||
closePos = true;
|
||||
|
||||
if(closePos)
|
||||
trade.PositionClose(_Symbol);
|
||||
}
|
||||
|
||||
void TryPullbackEntry(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
MqlRates bars1[], bars2[];
|
||||
if(CopyRates(_Symbol, _Period, 1, 1, bars1) != 1)
|
||||
return;
|
||||
if(CopyRates(_Symbol, _Period, 2, 1, bars2) != 1)
|
||||
return;
|
||||
if(ArraySize(bars1) < 1 || ArraySize(bars2) < 1)
|
||||
return;
|
||||
|
||||
MqlRates b1 = bars1[0];
|
||||
MqlRates b2 = bars2[0];
|
||||
|
||||
double line1 = TrendlinePriceAtTime(m, b1.time);
|
||||
double tol = InpLineTouchTolerance * _Point;
|
||||
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1.low <= (line1 + tol));
|
||||
bool reclaim = (b1.close > line1);
|
||||
bool bullish = (b1.close > b1.open);
|
||||
bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy");
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1.high >= (line1 - tol));
|
||||
bool reject = (b1.close < line1);
|
||||
bool bearish = (b1.close < b1.open);
|
||||
bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_maHandle = iMA(_Symbol, InpHigherTF, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice);
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
g_lastBarTime = 0;
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(g_maHandle);
|
||||
if(ObjectFind(0, g_lineName) >= 0)
|
||||
ObjectDelete(0, g_lineName);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(!IsNewBar())
|
||||
return;
|
||||
|
||||
TrendlineModel m;
|
||||
if(!BuildTrendlineFrom3Points(m))
|
||||
return;
|
||||
|
||||
DrawTrendline(m);
|
||||
TryExitOnBreak(m);
|
||||
TryPullbackEntry(m);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
; SimpleTrendline.mq5 optimization preset
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback entries + break exits (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||0||16388||Y
|
||||
InpMAPeriod=50||20||5||200||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=400||200||100||1200||Y
|
||||
InpLineTouchTolerance=100.0||30.0||10.0||300.0||Y
|
||||
InpBreakBuffer=30.0||5.0||5.0||120.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
@@ -0,0 +1,376 @@
|
||||
#property strict
|
||||
#property version "1.00"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_M10; // Higher timeframe for MA/cross points
|
||||
input int InpMAPeriod = 65; // MA period
|
||||
input ENUM_MA_METHOD InpMAMethod = MODE_EMA; // MA method
|
||||
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_OPEN; // MA applied price
|
||||
input int InpHTFBarsToScan = 500; // HTF bars to scan for crossings
|
||||
input double InpLineTouchTolerance = 220; // Pullback touch tolerance (points)
|
||||
input double InpBreakBuffer = 110; // Break confirmation buffer (points)
|
||||
input double InpLots = 0.10; // Position size
|
||||
input long InpMagic = 26042501; // Magic number
|
||||
input bool InpDrawTrendline = true; // Draw detected trendline
|
||||
input bool InpUseSessionModeGate = true; // Block entries when symbol/session disallow opens
|
||||
input bool InpBypassGateInTester = true; // Ignore gate in Strategy Tester for optimization
|
||||
|
||||
CTrade trade;
|
||||
|
||||
int g_maHandle = INVALID_HANDLE;
|
||||
datetime g_lastBarTime = 0;
|
||||
string g_lineName = "SimpleTrendline_Basis";
|
||||
datetime g_lastEntryBlockLog = 0;
|
||||
|
||||
struct TrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double p1;
|
||||
double p2;
|
||||
double p3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
bool IsNewBar()
|
||||
{
|
||||
datetime t = iTime(_Symbol, _Period, 0);
|
||||
if(t == 0)
|
||||
return false;
|
||||
if(t != g_lastBarTime)
|
||||
{
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int FindRecentCrossPoints(datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(InpHTFBarsToScan, InpMAPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
|
||||
int copiedRates = CopyRates(_Symbol, InpHigherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(g_maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
for(int i = 2; i < bars - 1; i++)
|
||||
{
|
||||
double d0 = rates[i].close - maBuf[i];
|
||||
double d1 = rates[i + 1].close - maBuf[i + 1];
|
||||
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
|
||||
{
|
||||
int n = ArraySize(times);
|
||||
ArrayResize(times, n + 1);
|
||||
ArrayResize(prices, n + 1);
|
||||
times[n] = rates[i].time;
|
||||
prices[n] = rates[i].close;
|
||||
if(ArraySize(times) >= 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ArraySize(times);
|
||||
}
|
||||
|
||||
bool BuildTrendlineFrom3Points(TrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
int n = FindRecentCrossPoints(ts, ps);
|
||||
if(n < 3)
|
||||
return false;
|
||||
|
||||
// We collected from recent to older in series order.
|
||||
// Re-map as oldest -> newest to stabilize slope direction.
|
||||
datetime tOld[3];
|
||||
double pOld[3];
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
tOld[i] = ts[2 - i];
|
||||
pOld[i] = ps[2 - i];
|
||||
}
|
||||
|
||||
long t0 = (long)tOld[0];
|
||||
double x1 = 0.0;
|
||||
double x2 = (double)((long)tOld[1] - t0);
|
||||
double x3 = (double)((long)tOld[2] - t0);
|
||||
double y1 = pOld[0];
|
||||
double y2 = pOld[1];
|
||||
double y3 = pOld[2];
|
||||
|
||||
double sx = x1 + x2 + x3;
|
||||
double sy = y1 + y2 + y3;
|
||||
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
|
||||
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
|
||||
|
||||
double den = 3.0 * sxx - sx * sx;
|
||||
if(MathAbs(den) < 1e-10)
|
||||
return false;
|
||||
|
||||
m.a = (3.0 * sxy - sx * sy) / den;
|
||||
m.b = (sy - m.a * sx) / 3.0;
|
||||
|
||||
m.t1 = tOld[0];
|
||||
m.t2 = tOld[1];
|
||||
m.t3 = tOld[2];
|
||||
m.p1 = pOld[0];
|
||||
m.p2 = pOld[1];
|
||||
m.p3 = pOld[2];
|
||||
m.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double TrendlinePriceAtTime(const TrendlineModel &m, datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void DrawTrendline(const TrendlineModel &m)
|
||||
{
|
||||
if(!InpDrawTrendline || !m.valid)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(_Symbol, _Period, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(_Period) * 20;
|
||||
|
||||
double pStart = TrendlinePriceAtTime(m, tStart);
|
||||
double pEnd = TrendlinePriceAtTime(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, g_lineName) < 0)
|
||||
ObjectCreate(0, g_lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, g_lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, g_lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, g_lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
bool GetCurrentPosition(long &type, double &volume)
|
||||
{
|
||||
if(!PositionSelect(_Symbol))
|
||||
return false;
|
||||
if((long)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
return false;
|
||||
type = PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsWithinAnyTradeSession(const datetime nowServer)
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(nowServer, dt);
|
||||
ENUM_DAY_OF_WEEK day = (ENUM_DAY_OF_WEEK)dt.day_of_week;
|
||||
int nowSec = dt.hour * 3600 + dt.min * 60 + dt.sec;
|
||||
|
||||
datetime from = 0;
|
||||
datetime to = 0;
|
||||
bool hasAny = false;
|
||||
for(uint idx = 0; idx < 16; idx++)
|
||||
{
|
||||
if(!SymbolInfoSessionTrade(_Symbol, day, idx, from, to))
|
||||
break;
|
||||
hasAny = true;
|
||||
// SymbolInfoSessionTrade returns session boundaries as time-of-day values.
|
||||
MqlDateTime fdt, tdt;
|
||||
TimeToStruct(from, fdt);
|
||||
TimeToStruct(to, tdt);
|
||||
int fromSec = fdt.hour * 3600 + fdt.min * 60 + fdt.sec;
|
||||
int toSec = tdt.hour * 3600 + tdt.min * 60 + tdt.sec;
|
||||
|
||||
// from==to on some brokers means full-day session.
|
||||
if(fromSec == toSec)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(fromSec < toSec)
|
||||
{
|
||||
if(nowSec >= fromSec && nowSec <= toSec)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Session passes midnight.
|
||||
if(nowSec >= fromSec || nowSec <= toSec)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// If broker does not expose sessions for this symbol, do not block by session.
|
||||
if(!hasAny)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CanOpenNewPositionNow(const ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
if(!InpUseSessionModeGate)
|
||||
return true;
|
||||
if(InpBypassGateInTester && (bool)MQLInfoInteger(MQL_TESTER))
|
||||
return true;
|
||||
|
||||
long tradeMode = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode == SYMBOL_TRADE_MODE_DISABLED ||
|
||||
tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY)
|
||||
return false;
|
||||
if(orderType == ORDER_TYPE_BUY &&
|
||||
tradeMode == SYMBOL_TRADE_MODE_SHORTONLY)
|
||||
return false;
|
||||
if(orderType == ORDER_TYPE_SELL &&
|
||||
tradeMode == SYMBOL_TRADE_MODE_LONGONLY)
|
||||
return false;
|
||||
|
||||
if(!IsWithinAnyTradeSession(TimeCurrent()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TryExitOnBreak(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(!GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(_Symbol, _Period, 1);
|
||||
datetime t1 = iTime(_Symbol, _Period, 1);
|
||||
double line1 = TrendlinePriceAtTime(m, t1);
|
||||
double buf = InpBreakBuffer * _Point;
|
||||
|
||||
bool closePos = false;
|
||||
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
|
||||
closePos = true;
|
||||
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
|
||||
closePos = true;
|
||||
|
||||
if(closePos)
|
||||
trade.PositionClose(_Symbol);
|
||||
}
|
||||
|
||||
void TryPullbackEntry(const TrendlineModel &m)
|
||||
{
|
||||
long posType;
|
||||
double vol;
|
||||
if(GetCurrentPosition(posType, vol))
|
||||
return;
|
||||
|
||||
MqlRates bars1[], bars2[];
|
||||
if(CopyRates(_Symbol, _Period, 1, 1, bars1) != 1)
|
||||
return;
|
||||
if(CopyRates(_Symbol, _Period, 2, 1, bars2) != 1)
|
||||
return;
|
||||
if(ArraySize(bars1) < 1 || ArraySize(bars2) < 1)
|
||||
return;
|
||||
|
||||
MqlRates b1 = bars1[0];
|
||||
MqlRates b2 = bars2[0];
|
||||
|
||||
double line1 = TrendlinePriceAtTime(m, b1.time);
|
||||
double tol = InpLineTouchTolerance * _Point;
|
||||
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1.low <= (line1 + tol));
|
||||
bool reclaim = (b1.close > line1);
|
||||
bool bullish = (b1.close > b1.open);
|
||||
bool stillHealthy = (b2.close >= TrendlinePriceAtTime(m, b2.time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
if(!CanOpenNewPositionNow(ORDER_TYPE_BUY))
|
||||
{
|
||||
datetime nowBar = iTime(_Symbol, _Period, 0);
|
||||
if(nowBar != g_lastEntryBlockLog)
|
||||
{
|
||||
g_lastEntryBlockLog = nowBar;
|
||||
Print("Buy entry skipped: symbol mode/session does not allow opening now");
|
||||
}
|
||||
return;
|
||||
}
|
||||
trade.Buy(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback buy");
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1.high >= (line1 - tol));
|
||||
bool reject = (b1.close < line1);
|
||||
bool bearish = (b1.close < b1.open);
|
||||
bool stillWeak = (b2.close <= TrendlinePriceAtTime(m, b2.time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
if(!CanOpenNewPositionNow(ORDER_TYPE_SELL))
|
||||
{
|
||||
datetime nowBar = iTime(_Symbol, _Period, 0);
|
||||
if(nowBar != g_lastEntryBlockLog)
|
||||
{
|
||||
g_lastEntryBlockLog = nowBar;
|
||||
Print("Sell entry skipped: symbol mode/session does not allow opening now");
|
||||
}
|
||||
return;
|
||||
}
|
||||
trade.Sell(InpLots, _Symbol, 0.0, 0.0, 0.0, "Pullback sell");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
g_maHandle = iMA(_Symbol, InpHigherTF, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice);
|
||||
if(g_maHandle == INVALID_HANDLE)
|
||||
return INIT_FAILED;
|
||||
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
g_lastBarTime = 0;
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
if(g_maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(g_maHandle);
|
||||
if(ObjectFind(0, g_lineName) >= 0)
|
||||
ObjectDelete(0, g_lineName);
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
if(!IsNewBar())
|
||||
return;
|
||||
|
||||
TrendlineModel m;
|
||||
if(!BuildTrendlineFrom3Points(m))
|
||||
return;
|
||||
|
||||
DrawTrendline(m);
|
||||
TryExitOnBreak(m);
|
||||
TryPullbackEntry(m);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
; SimpleTrendline.mq5 optimization preset
|
||||
; Strategy Tester -> Inputs -> Load
|
||||
; Focus: trendline pullback entries + break exits (no broker SL/TP)
|
||||
;
|
||||
InpHigherTF=16385||16385||0||16388||Y
|
||||
InpMAPeriod=50||20||5||200||Y
|
||||
InpMAMethod=1||0||1||3||Y
|
||||
InpAppliedPrice=0||0||1||6||Y
|
||||
InpHTFBarsToScan=400||200||100||1200||Y
|
||||
InpLineTouchTolerance=100.0||30.0||10.0||300.0||Y
|
||||
InpBreakBuffer=30.0||5.0||5.0||120.0||Y
|
||||
InpLots=0.10||0.10||0.01||0.10||N
|
||||
InpMagic=26042501||26042501||1||26042501||N
|
||||
InpDrawTrendline=false||false||0||true||N
|
||||
@@ -0,0 +1,36 @@
|
||||
; SuperEMA — defaults aligned with lab/EAs/SuperEMA.mq5 (v1.01)
|
||||
; Load from Strategy Tester → Inputs → context menu → Load
|
||||
;
|
||||
; === Market ===
|
||||
InpSymbol=
|
||||
InpTimeframe=15||15||0||49153||N
|
||||
InpLots=0.01||0.01||0.01||0.10||N
|
||||
InpSlippagePoints=55||20||5||120||Y
|
||||
InpMagic=940001||940001||1||9400010||N
|
||||
; === EMA (trend & structure) ===
|
||||
InpEmaFast=40||20||10||120||Y
|
||||
InpEmaMid=180||60||15||200||Y
|
||||
InpEmaSlow=125||100||25||400||Y
|
||||
InpEmaTrendBars=3||1||1||3||Y
|
||||
; === CCI ===
|
||||
InpCciPeriod=17||7||1||28||Y
|
||||
InpCciOverbought=80.0||80.0||10.0||140.0||Y
|
||||
InpCciOversold=-140.0||-140.0||10.0||-80.0||Y
|
||||
InpPullbackCciLookback=20||4||2||24||Y
|
||||
; === MACD (histogram = main - signal) ===
|
||||
InpMacdFast=14||8||2||20||Y
|
||||
InpMacdSlow=38||20||2||40||Y
|
||||
InpMacdSignal=9||5||1||15||Y
|
||||
; === Strategy ===
|
||||
InpEntryStyle=1||0||1||2||Y
|
||||
InpOneTradeOnly=true||false||0||true||N
|
||||
InpUseStructuralSL=false||false||0||true||Y
|
||||
InpSlBufferPoints=110.0||20.0||10.0||200.0||Y
|
||||
; === Exits (so trades do not run forever) ===
|
||||
InpExitOnTrendFlip=false||false||0||true||Y
|
||||
InpExitOnMacdFlip=false||false||0||true||Y
|
||||
InpExitOnCciZeroCross=true||false||0||true||Y
|
||||
InpMaxHoldingBars=168||48||24||480||Y
|
||||
InpExitBelowMidEma=false||false||0||true||Y
|
||||
; === Debug ===
|
||||
InpDebugLogs=false||false||0||true||N
|
||||
@@ -0,0 +1,448 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SuperEMA.mq5 |
|
||||
//| EMA + CCI + MACD histogram — trend filter, momentum confirmation |
|
||||
//+------------------------------------------------------------------+
|
||||
#property strict
|
||||
#property version "1.01"
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
|
||||
enum ENUM_ENTRY_STYLE
|
||||
{
|
||||
ENTRY_CCIZERO_MACD = 0, // EMA trend + CCI crosses zero + MACD histogram agrees
|
||||
ENTRY_LAMBERT = 1, // EMA trend + CCI crosses ±100 + MACD histogram agrees
|
||||
ENTRY_PULLBACK = 2 // Uptrend: pullback to fast EMA + CCI was oversold + CCI crosses up through 0 + MACD > 0 (mirror for sells)
|
||||
};
|
||||
|
||||
input group "=== Market ==="
|
||||
input string InpSymbol = "";
|
||||
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_M15;
|
||||
input double InpLots = 0.01;
|
||||
input int InpSlippagePoints = 55;
|
||||
input int InpMagic = 940001;
|
||||
|
||||
input group "=== EMA (trend & structure) ==="
|
||||
input int InpEmaFast = 40;
|
||||
input int InpEmaMid = 180;
|
||||
input int InpEmaSlow = 125;
|
||||
input int InpEmaTrendBars = 3; // closed bar shift for EMA reads
|
||||
|
||||
input group "=== CCI ==="
|
||||
input int InpCciPeriod = 17;
|
||||
input double InpCciOverbought = 80.0;
|
||||
input double InpCciOversold = -140.0;
|
||||
input int InpPullbackCciLookback = 20; // bars to check prior CCI oversold/overbought
|
||||
|
||||
input group "=== MACD (histogram = main - signal) ==="
|
||||
input int InpMacdFast = 14;
|
||||
input int InpMacdSlow = 38;
|
||||
input int InpMacdSignal = 9;
|
||||
|
||||
input group "=== Strategy ==="
|
||||
input ENUM_ENTRY_STYLE InpEntryStyle = ENTRY_LAMBERT;
|
||||
input bool InpOneTradeOnly = true;
|
||||
input bool InpUseStructuralSL = false;
|
||||
input double InpSlBufferPoints = 110;
|
||||
|
||||
input group "=== Exits (so trades do not run forever) ==="
|
||||
input bool InpExitOnTrendFlip = false; // close when price vs slow EMA flips against position
|
||||
input bool InpExitOnMacdFlip = false; // close when MACD histogram flips against position
|
||||
input bool InpExitOnCciZeroCross = true; // long: CCI crosses below 0; short: CCI crosses above 0
|
||||
input int InpMaxHoldingBars = 168; // 0 = disabled (e.g. ~8 days M15)
|
||||
input bool InpExitBelowMidEma = false; // long: close if close < mid EMA (invalidation)
|
||||
|
||||
input group "=== Debug ==="
|
||||
input bool InpDebugLogs = false;
|
||||
|
||||
CTrade trade;
|
||||
datetime g_lastBarTime = 0;
|
||||
|
||||
string WorkSymbol()
|
||||
{
|
||||
return (InpSymbol == "" || InpSymbol == NULL) ? _Symbol : InpSymbol;
|
||||
}
|
||||
|
||||
void Log(const string s)
|
||||
{
|
||||
if(InpDebugLogs)
|
||||
Print("[SuperEMA] ", s);
|
||||
}
|
||||
|
||||
bool IsNewBar(const string sym, const ENUM_TIMEFRAMES tf)
|
||||
{
|
||||
datetime t = iTime(sym, tf, 0);
|
||||
if(t <= 0 || t == g_lastBarTime)
|
||||
return false;
|
||||
g_lastBarTime = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
double EmaAt(const string sym, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int h = iMA(sym, tf, period, 0, MODE_EMA, PRICE_CLOSE);
|
||||
if(h == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
double b[1];
|
||||
if(CopyBuffer(h, 0, shift, 1, b) <= 0)
|
||||
{
|
||||
IndicatorRelease(h);
|
||||
return 0.0;
|
||||
}
|
||||
IndicatorRelease(h);
|
||||
return b[0];
|
||||
}
|
||||
|
||||
double CciAt(const string sym, const ENUM_TIMEFRAMES tf, const int period, const int shift)
|
||||
{
|
||||
int h = iCCI(sym, tf, period, PRICE_TYPICAL);
|
||||
if(h == INVALID_HANDLE)
|
||||
return 0.0;
|
||||
double b[1];
|
||||
if(CopyBuffer(h, 0, shift, 1, b) <= 0)
|
||||
{
|
||||
IndicatorRelease(h);
|
||||
return 0.0;
|
||||
}
|
||||
IndicatorRelease(h);
|
||||
return b[0];
|
||||
}
|
||||
|
||||
bool MacdHistAt(const string sym, const ENUM_TIMEFRAMES tf, const int fast, const int slow, const int signal, const int shift, double &hist)
|
||||
{
|
||||
int h = iMACD(sym, tf, fast, slow, signal, PRICE_CLOSE);
|
||||
if(h == INVALID_HANDLE)
|
||||
return false;
|
||||
double mainLine[1], sigLine[1];
|
||||
if(CopyBuffer(h, 0, shift, 1, mainLine) <= 0 || CopyBuffer(h, 1, shift, 1, sigLine) <= 0)
|
||||
{
|
||||
IndicatorRelease(h);
|
||||
return false;
|
||||
}
|
||||
IndicatorRelease(h);
|
||||
hist = mainLine[0] - sigLine[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TrendUp(const string sym, const int sh)
|
||||
{
|
||||
double c = iClose(sym, InpTimeframe, sh);
|
||||
double emaS = EmaAt(sym, InpTimeframe, InpEmaSlow, sh);
|
||||
return (emaS > 0.0 && c > emaS);
|
||||
}
|
||||
|
||||
bool TrendDown(const string sym, const int sh)
|
||||
{
|
||||
double c = iClose(sym, InpTimeframe, sh);
|
||||
double emaS = EmaAt(sym, InpTimeframe, InpEmaSlow, sh);
|
||||
return (emaS > 0.0 && c < emaS);
|
||||
}
|
||||
|
||||
bool CciCrossAboveZero(const string sym)
|
||||
{
|
||||
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
||||
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
||||
return (c2 <= 0.0 && c1 > 0.0);
|
||||
}
|
||||
|
||||
bool CciCrossBelowZero(const string sym)
|
||||
{
|
||||
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
||||
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
||||
return (c2 >= 0.0 && c1 < 0.0);
|
||||
}
|
||||
|
||||
bool CciCrossAbove100(const string sym)
|
||||
{
|
||||
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
||||
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
||||
return (c2 < InpCciOverbought && c1 > InpCciOverbought);
|
||||
}
|
||||
|
||||
bool CciCrossBelowMinus100(const string sym)
|
||||
{
|
||||
double c1 = CciAt(sym, InpTimeframe, InpCciPeriod, 1);
|
||||
double c2 = CciAt(sym, InpTimeframe, InpCciPeriod, 2);
|
||||
return (c2 > InpCciOversold && c1 < InpCciOversold);
|
||||
}
|
||||
|
||||
bool HadCciOversoldRecently(const string sym)
|
||||
{
|
||||
for(int i = 2; i <= InpPullbackCciLookback + 1; i++)
|
||||
{
|
||||
double v = CciAt(sym, InpTimeframe, InpCciPeriod, i);
|
||||
if(v <= InpCciOversold)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HadCciOverboughtRecently(const string sym)
|
||||
{
|
||||
for(int i = 2; i <= InpPullbackCciLookback + 1; i++)
|
||||
{
|
||||
double v = CciAt(sym, InpTimeframe, InpCciPeriod, i);
|
||||
if(v >= InpCciOverbought)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PullbackNearFastEmaLong(const string sym)
|
||||
{
|
||||
double emaF = EmaAt(sym, InpTimeframe, InpEmaFast, 1);
|
||||
double lo = iLow(sym, InpTimeframe, 1);
|
||||
if(emaF <= 0.0)
|
||||
return false;
|
||||
return (lo <= emaF + InpSlBufferPoints * _Point * 3.0);
|
||||
}
|
||||
|
||||
bool PullbackNearFastEmaShort(const string sym)
|
||||
{
|
||||
double emaF = EmaAt(sym, InpTimeframe, InpEmaFast, 1);
|
||||
double hi = iHigh(sym, InpTimeframe, 1);
|
||||
if(emaF <= 0.0)
|
||||
return false;
|
||||
return (hi >= emaF - InpSlBufferPoints * _Point * 3.0);
|
||||
}
|
||||
|
||||
int PositionsByMagic(const string sym, const int magic)
|
||||
{
|
||||
int n = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == sym && (int)PositionGetInteger(POSITION_MAGIC) == magic)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void ComputeSLTP(const bool isBuy, const double entry, double &sl, double &tp)
|
||||
{
|
||||
const string sym = WorkSymbol();
|
||||
sl = 0.0;
|
||||
tp = 0.0;
|
||||
if(!InpUseStructuralSL)
|
||||
return;
|
||||
double emaM = EmaAt(sym, InpTimeframe, InpEmaMid, InpEmaTrendBars);
|
||||
double buf = InpSlBufferPoints * _Point;
|
||||
if(isBuy)
|
||||
sl = emaM - buf;
|
||||
else
|
||||
sl = emaM + buf;
|
||||
}
|
||||
|
||||
int BarsSinceOpen(const string sym, const datetime openTime)
|
||||
{
|
||||
if(openTime <= 0)
|
||||
return 0;
|
||||
int sh = iBarShift(sym, InpTimeframe, openTime, false);
|
||||
if(sh < 0)
|
||||
return 999999;
|
||||
return sh;
|
||||
}
|
||||
|
||||
void ClosePositionTicket(const ulong ticket, const string reason)
|
||||
{
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
if(trade.PositionClose(ticket))
|
||||
Log("Close: " + reason);
|
||||
}
|
||||
|
||||
void ManageSuperEMAExits(const string sym)
|
||||
{
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) != sym)
|
||||
continue;
|
||||
if((int)PositionGetInteger(POSITION_MAGIC) != InpMagic)
|
||||
continue;
|
||||
|
||||
ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
||||
|
||||
double h1 = 0.0;
|
||||
if(!MacdHistAt(sym, InpTimeframe, InpMacdFast, InpMacdSlow, InpMacdSignal, 1, h1))
|
||||
continue;
|
||||
|
||||
bool closeLong = false;
|
||||
bool closeShort = false;
|
||||
string reason = "";
|
||||
|
||||
if(InpMaxHoldingBars > 0)
|
||||
{
|
||||
int held = BarsSinceOpen(sym, openTime);
|
||||
if(held >= InpMaxHoldingBars)
|
||||
{
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
closeLong = true;
|
||||
else
|
||||
closeShort = true;
|
||||
reason = "time stop (max bars)";
|
||||
}
|
||||
}
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
if(InpExitOnTrendFlip && TrendDown(sym, InpEmaTrendBars))
|
||||
{
|
||||
closeLong = true;
|
||||
reason = "trend flip (below slow EMA)";
|
||||
}
|
||||
if(InpExitOnMacdFlip && h1 < 0.0)
|
||||
{
|
||||
closeLong = true;
|
||||
reason = "MACD histogram < 0";
|
||||
}
|
||||
if(InpExitOnCciZeroCross && CciCrossBelowZero(sym))
|
||||
{
|
||||
closeLong = true;
|
||||
reason = "CCI crossed below zero";
|
||||
}
|
||||
if(InpExitBelowMidEma)
|
||||
{
|
||||
double c = iClose(sym, InpTimeframe, 1);
|
||||
double emaM = EmaAt(sym, InpTimeframe, InpEmaMid, 1);
|
||||
if(emaM > 0.0 && c < emaM)
|
||||
{
|
||||
closeLong = true;
|
||||
reason = "close below mid EMA";
|
||||
}
|
||||
}
|
||||
if(closeLong)
|
||||
ClosePositionTicket(ticket, reason);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
if(InpExitOnTrendFlip && TrendUp(sym, InpEmaTrendBars))
|
||||
{
|
||||
closeShort = true;
|
||||
reason = "trend flip (above slow EMA)";
|
||||
}
|
||||
if(InpExitOnMacdFlip && h1 > 0.0)
|
||||
{
|
||||
closeShort = true;
|
||||
reason = "MACD histogram > 0";
|
||||
}
|
||||
if(InpExitOnCciZeroCross && CciCrossAboveZero(sym))
|
||||
{
|
||||
closeShort = true;
|
||||
reason = "CCI crossed above zero";
|
||||
}
|
||||
if(InpExitBelowMidEma)
|
||||
{
|
||||
double c = iClose(sym, InpTimeframe, 1);
|
||||
double emaM = EmaAt(sym, InpTimeframe, InpEmaMid, 1);
|
||||
if(emaM > 0.0 && c > emaM)
|
||||
{
|
||||
closeShort = true;
|
||||
reason = "close above mid EMA";
|
||||
}
|
||||
}
|
||||
if(closeShort)
|
||||
ClosePositionTicket(ticket, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int OnInit()
|
||||
{
|
||||
string sym = WorkSymbol();
|
||||
if(!SymbolSelect(sym, true))
|
||||
{
|
||||
Print("SuperEMA: cannot select symbol ", sym);
|
||||
return INIT_FAILED;
|
||||
}
|
||||
trade.SetExpertMagicNumber(InpMagic);
|
||||
trade.SetDeviationInPoints(InpSlippagePoints);
|
||||
return INIT_SUCCEEDED;
|
||||
}
|
||||
|
||||
void OnTick()
|
||||
{
|
||||
string sym = WorkSymbol();
|
||||
if(_Symbol != sym)
|
||||
{
|
||||
static datetime lastLog = 0;
|
||||
datetime tb = iTime(_Symbol, PERIOD_M1, 0);
|
||||
if(tb != lastLog && InpDebugLogs)
|
||||
{
|
||||
lastLog = tb;
|
||||
Log("Chart symbol differs from WorkSymbol; attach to " + sym + " or set InpSymbol empty.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!IsNewBar(sym, InpTimeframe))
|
||||
return;
|
||||
|
||||
// Exits must run every bar; do not skip when a position exists (otherwise trades never close with SL=0/TP=0).
|
||||
ManageSuperEMAExits(sym);
|
||||
|
||||
if(InpOneTradeOnly && PositionsByMagic(sym, InpMagic) > 0)
|
||||
return;
|
||||
|
||||
const int sh = InpEmaTrendBars;
|
||||
double h1 = 0.0, h2 = 0.0;
|
||||
if(!MacdHistAt(sym, InpTimeframe, InpMacdFast, InpMacdSlow, InpMacdSignal, 1, h1) ||
|
||||
!MacdHistAt(sym, InpTimeframe, InpMacdFast, InpMacdSlow, InpMacdSignal, 2, h2))
|
||||
return;
|
||||
|
||||
bool up = TrendUp(sym, sh);
|
||||
bool dn = TrendDown(sym, sh);
|
||||
|
||||
bool wantBuy = false;
|
||||
bool wantSell = false;
|
||||
|
||||
switch(InpEntryStyle)
|
||||
{
|
||||
case ENTRY_CCIZERO_MACD:
|
||||
if(up && CciCrossAboveZero(sym) && h1 > 0.0)
|
||||
wantBuy = true;
|
||||
if(dn && CciCrossBelowZero(sym) && h1 < 0.0)
|
||||
wantSell = true;
|
||||
break;
|
||||
|
||||
case ENTRY_LAMBERT:
|
||||
if(up && CciCrossAbove100(sym) && h1 > 0.0)
|
||||
wantBuy = true;
|
||||
if(dn && CciCrossBelowMinus100(sym) && h1 < 0.0)
|
||||
wantSell = true;
|
||||
break;
|
||||
|
||||
case ENTRY_PULLBACK:
|
||||
if(up && HadCciOversoldRecently(sym) && CciCrossAboveZero(sym) && h1 > 0.0 && PullbackNearFastEmaLong(sym))
|
||||
wantBuy = true;
|
||||
if(dn && HadCciOverboughtRecently(sym) && CciCrossBelowZero(sym) && h1 < 0.0 && PullbackNearFastEmaShort(sym))
|
||||
wantSell = true;
|
||||
break;
|
||||
}
|
||||
|
||||
MqlTick tick;
|
||||
if(!SymbolInfoTick(sym, tick))
|
||||
return;
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
|
||||
if(wantBuy && !wantSell)
|
||||
{
|
||||
ComputeSLTP(true, tick.ask, sl, tp);
|
||||
if(trade.Buy(InpLots, sym, tick.ask, sl, tp, "SuperEMA long"))
|
||||
Log(StringFormat("BUY ask=%.5f sl=%.5f cci=%.2f macdHist=%.5f", tick.ask, sl,
|
||||
CciAt(sym, InpTimeframe, InpCciPeriod, 1), h1));
|
||||
}
|
||||
else if(wantSell && !wantBuy)
|
||||
{
|
||||
ComputeSLTP(false, tick.bid, sl, tp);
|
||||
if(trade.Sell(InpLots, sym, tick.bid, sl, tp, "SuperEMA short"))
|
||||
Log(StringFormat("SELL bid=%.5f sl=%.5f cci=%.2f macdHist=%.5f", tick.bid, sl,
|
||||
CciAt(sym, InpTimeframe, InpCciPeriod, 1), h1));
|
||||
}
|
||||
}
|
||||
@@ -12,29 +12,18 @@
|
||||
//+------------------------------------------------------------------+
|
||||
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)
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
// 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;
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -76,14 +65,13 @@ 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;
|
||||
}
|
||||
}
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
return ticket;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -144,16 +132,42 @@ int CountPositionsByMagic(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)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Align volume to SYMBOL_VOLUME_STEP / min / max (avoids Invalid volume) |
|
||||
//+------------------------------------------------------------------+
|
||||
double United_NormalizeVolume(const string symbol, double volume)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
if(lotStep <= 0.0)
|
||||
lotStep = 0.01;
|
||||
|
||||
double v = MathFloor(volume / lotStep) * lotStep;
|
||||
|
||||
if(v < minLot)
|
||||
v = minLot;
|
||||
if(v > maxLot)
|
||||
v = maxLot;
|
||||
|
||||
int digits = (int)MathCeil(-MathLog10(lotStep));
|
||||
if(digits < 0)
|
||||
digits = 0;
|
||||
if(digits > 8)
|
||||
digits = 8;
|
||||
|
||||
return NormalizeDouble(v, digits);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -1,6 +1,18 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DarvasBoxStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
// MQL5: no #if — use #ifdef only (no defined() / || in one #if)
|
||||
#ifdef UNITED_V2_DYNAMIC_LOTS
|
||||
extern double g_DB_LotSize;
|
||||
#define DARVAS_TRADE_LOT (g_DB_LotSize)
|
||||
#else
|
||||
#ifdef CLUSTER0_ORCHESTRATOR
|
||||
extern double g_DB_LotSize;
|
||||
#define DARVAS_TRADE_LOT (g_DB_LotSize)
|
||||
#else
|
||||
#define DARVAS_TRADE_LOT 0.01
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool InitDarvasBox(string symbol)
|
||||
{
|
||||
@@ -24,7 +36,9 @@ bool InitDarvasBox(string symbol)
|
||||
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);
|
||||
// Same TF as box highs/lows (H1 loop in CalculateDarvasBox). PERIOD_CURRENT breaks when United EA
|
||||
// runs on a chart timeframe other than H1 (volume/breakout no longer match the box).
|
||||
dbData.volumeHandle = iVolumes(symbol, PERIOD_H1, VOLUME_TICK);
|
||||
|
||||
if(dbData.maHandle == INVALID_HANDLE || dbData.volumeHandle == INVALID_HANDLE)
|
||||
{
|
||||
@@ -133,6 +147,9 @@ bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE or
|
||||
|
||||
bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
if(!DB_UseTrendFilter)
|
||||
return true;
|
||||
|
||||
double ma[];
|
||||
ArraySetAsSeries(ma, true);
|
||||
|
||||
@@ -150,6 +167,9 @@ bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
|
||||
|
||||
bool CheckVolumeConditions()
|
||||
{
|
||||
if(!DB_UseVolumeSpikeFilter)
|
||||
return true;
|
||||
|
||||
double volumes[];
|
||||
ArraySetAsSeries(volumes, true);
|
||||
|
||||
@@ -162,8 +182,10 @@ bool CheckVolumeConditions()
|
||||
volumeMA /= DB_VolumeMA_Period;
|
||||
|
||||
double currentVolume = volumes[0];
|
||||
if(volumeMA <= 0.0)
|
||||
return (currentVolume > 0.0);
|
||||
|
||||
double volumeRatio = currentVolume / volumeMA;
|
||||
|
||||
return (volumeRatio > DB_VolumeThresholdMultiplier);
|
||||
}
|
||||
|
||||
@@ -191,13 +213,19 @@ bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
const double lot = United_NormalizeVolume(dbData.symbol, DARVAS_TRADE_LOT);
|
||||
if(lot <= 0.0)
|
||||
{
|
||||
Print("DarvasBox: Order rejected - invalid lot after normalize (raw=", DARVAS_TRADE_LOT, ")");
|
||||
return 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(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
result = dbData.trade.Buy(lot, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
else
|
||||
result = dbData.trade.Sell(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
result = dbData.trade.Sell(lot, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
|
||||
// Always log errors, success only if logging enabled
|
||||
if(result)
|
||||
@@ -242,7 +270,7 @@ void ProcessDarvasBox(string symbol)
|
||||
if(dbData.boxFormed)
|
||||
{
|
||||
double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK);
|
||||
long currentVolume_long = iVolume(dbData.symbol, PERIOD_CURRENT, 0);
|
||||
long currentVolume_long = iVolume(dbData.symbol, PERIOD_H1, 0);
|
||||
double currentVolume = (double)currentVolume_long;
|
||||
|
||||
if(DB_EnableLogging)
|
||||
@@ -14,6 +14,7 @@ bool InitEMASlopeDistance(string symbol)
|
||||
esData.crossover_detected = false;
|
||||
esData.trade_open_time = 0;
|
||||
esData.last_bar_time = 0;
|
||||
esData.es_last_sl_adjust_success_time = 0;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
@@ -48,6 +49,64 @@ void DeinitEMASlopeDistance()
|
||||
IndicatorRelease(esData.ema_handle);
|
||||
}
|
||||
|
||||
bool ES_IsWeeklyADXTrendFavorable(const ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
if(!ES_UseWeeklyADXFilter)
|
||||
return true;
|
||||
|
||||
int adxShift = ES_WeeklyADXBarShift;
|
||||
if(adxShift < 0)
|
||||
adxShift = 0;
|
||||
|
||||
int adx_handle = iADX(esData.symbol, PERIOD_W1, ES_WeeklyADXPeriod);
|
||||
if(adx_handle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
double adx_buf[], plus_di_buf[], minus_di_buf[];
|
||||
ArraySetAsSeries(adx_buf, true);
|
||||
ArraySetAsSeries(plus_di_buf, true);
|
||||
ArraySetAsSeries(minus_di_buf, true);
|
||||
|
||||
bool ok_adx = (CopyBuffer(adx_handle, 0, adxShift, 1, adx_buf) > 0);
|
||||
bool ok_plus = (CopyBuffer(adx_handle, 1, adxShift, 1, plus_di_buf) > 0);
|
||||
bool ok_minus = (CopyBuffer(adx_handle, 2, adxShift, 1, minus_di_buf) > 0);
|
||||
IndicatorRelease(adx_handle);
|
||||
|
||||
if(!ok_adx || !ok_plus || !ok_minus)
|
||||
return false;
|
||||
|
||||
double adx_value = adx_buf[0];
|
||||
double plus_di = plus_di_buf[0];
|
||||
double minus_di = minus_di_buf[0];
|
||||
|
||||
bool strength_ok = (adx_value >= ES_WeeklyADXMin);
|
||||
bool direction_ok = true;
|
||||
if(ES_WeeklyADXUseDirection)
|
||||
{
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
direction_ok = (plus_di > minus_di);
|
||||
else
|
||||
direction_ok = (minus_di > plus_di);
|
||||
}
|
||||
return strength_ok && direction_ok;
|
||||
}
|
||||
|
||||
bool ES_TrailingActivationReached(const double position_profit, const ENUM_POSITION_TYPE position_type,
|
||||
const double pips_multiplier)
|
||||
{
|
||||
if(ES_TrailingActivationPips <= 0.0)
|
||||
return (position_profit > 0.0);
|
||||
|
||||
const double open_px = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(esData.symbol, SYMBOL_BID);
|
||||
return ((bid - open_px) / SymbolInfoDouble(esData.symbol, SYMBOL_POINT) / pips_multiplier >= ES_TrailingActivationPips);
|
||||
}
|
||||
const double ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK);
|
||||
return ((open_px - ask) / SymbolInfoDouble(esData.symbol, SYMBOL_POINT) / pips_multiplier >= ES_TrailingActivationPips);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMA Berechnung (EMA Calculation) |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -172,6 +231,11 @@ void PrüfeTrigger()
|
||||
|
||||
if(bullish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
if(!ES_IsWeeklyADXTrendFavorable(ORDER_TYPE_BUY))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert BUY-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_BUY))
|
||||
{
|
||||
@@ -180,6 +244,11 @@ void PrüfeTrigger()
|
||||
}
|
||||
else if(bearish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
if(!ES_IsWeeklyADXTrendFavorable(ORDER_TYPE_SELL))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert SELL-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_SELL))
|
||||
{
|
||||
@@ -199,17 +268,23 @@ void PrüfeTrigger()
|
||||
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);
|
||||
|
||||
const double lot = United_NormalizeVolume(esData.symbol, g_ES_LotSize);
|
||||
Print("TRACE: Lot (raw): ", g_ES_LotSize, " normalized: ", lot);
|
||||
if(lot <= 0.0)
|
||||
{
|
||||
Print("TRACE: Abbruch — Lot nach Normalisierung ungültig");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
{
|
||||
success = esData.trade.Buy(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
success = esData.trade.Buy(lot, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
else
|
||||
{
|
||||
success = esData.trade.Sell(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
success = esData.trade.Sell(lot, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
|
||||
if(success)
|
||||
@@ -219,6 +294,7 @@ bool PlatziereTrade(ENUM_ORDER_TYPE order_type)
|
||||
|
||||
//--- Trade-Öffnungszeit speichern (Save trade opening time)
|
||||
esData.trade_open_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
esData.es_last_sl_adjust_success_time = 0;
|
||||
Print("TRACE: Trade-Öffnungszeit: ", TimeToString(esData.trade_open_time));
|
||||
|
||||
//--- Überwachung zurücksetzen (Reset monitoring)
|
||||
@@ -244,41 +320,51 @@ void VerwalteTrades()
|
||||
{
|
||||
if(!PositionSelectByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
return;
|
||||
|
||||
|
||||
if(ES_UseStaleStopLossExit && ES_StaleStopLossSeconds > 0)
|
||||
{
|
||||
const datetime stale_ref = (esData.es_last_sl_adjust_success_time > 0)
|
||||
? esData.es_last_sl_adjust_success_time
|
||||
: (datetime)PositionGetInteger(POSITION_TIME);
|
||||
if(TimeCurrent() - stale_ref >= ES_StaleStopLossSeconds)
|
||||
{
|
||||
SchließePosition("Stale stop loss - keine SL-Anpassung");
|
||||
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
|
||||
const double trail_dist = ES_TrailingStop * point * pips_multiplier;
|
||||
const long stops_level = SymbolInfoInteger(esData.symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
if(ES_UseTrailingStop && ES_TrailingStop > 0.0 && ES_TrailingActivationReached(position_profit, position_type, pips_multiplier))
|
||||
{
|
||||
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)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(esData.symbol, SYMBOL_BID);
|
||||
double new_stop_loss = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_stop_loss < min_dist)
|
||||
new_stop_loss = NormalizeDouble(bid - min_dist, digits);
|
||||
const double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
if(new_stop_loss < bid && new_stop_loss > 0.0 && 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)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK);
|
||||
double new_stop_loss = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_stop_loss - ask < min_dist)
|
||||
new_stop_loss = NormalizeDouble(ask + min_dist, digits);
|
||||
const double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
if(new_stop_loss > ask && new_stop_loss > 0.0 &&
|
||||
(new_stop_loss < current_stop_loss || current_stop_loss == 0.0))
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +452,7 @@ void ÄndereStopLoss(double new_stop_loss)
|
||||
|
||||
if(success)
|
||||
{
|
||||
esData.es_last_sl_adjust_success_time = TimeCurrent();
|
||||
Print("TRACE: Stop Loss erfolgreich geändert auf: ", new_stop_loss);
|
||||
}
|
||||
else
|
||||
@@ -386,6 +473,7 @@ void SchließePosition(string reason = "Unbekannt")
|
||||
|
||||
if(success)
|
||||
{
|
||||
esData.es_last_sl_adjust_success_time = 0;
|
||||
Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason);
|
||||
}
|
||||
else
|
||||
@@ -400,40 +488,34 @@ void SchließePosition(string reason = "Unbekannt")
|
||||
//+------------------------------------------------------------------+
|
||||
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.symbol = symbol;
|
||||
|
||||
const datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
const bool new_bar = (current_bar_time != esData.last_bar_time);
|
||||
const bool has_position = PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber);
|
||||
|
||||
if(ES_UseBarData && !new_bar && !has_position)
|
||||
return;
|
||||
|
||||
if(new_bar)
|
||||
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)
|
||||
|
||||
const bool run_signals = (!ES_UseBarData || new_bar);
|
||||
|
||||
if(run_signals && 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) ===");
|
||||
@@ -443,7 +525,7 @@ void ProcessEMASlopeDistance(string symbol)
|
||||
{
|
||||
Print("=== DEBUG INFO (Tick) ===");
|
||||
}
|
||||
|
||||
|
||||
Print("Aktueller Close: ", aktueller_close);
|
||||
Print("EMA: ", ema_aktuell);
|
||||
Print("Preis-Abstand: ", preis_abstand, " Pips");
|
||||
@@ -455,41 +537,39 @@ void ProcessEMASlopeDistance(string symbol)
|
||||
Print("Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover);
|
||||
Print("==================");
|
||||
}
|
||||
|
||||
//--- Überwachung prüfen (Check monitoring)
|
||||
if(esData.überwachung_aktiv)
|
||||
|
||||
if(run_signals)
|
||||
{
|
||||
if(ES_UseBarData)
|
||||
if(esData.überwachung_aktiv)
|
||||
{
|
||||
// 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)
|
||||
if(ES_UseBarData)
|
||||
{
|
||||
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");
|
||||
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
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrüfeTrigger();
|
||||
}
|
||||
|
||||
//--- Trigger-Bedingungen prüfen (Check trigger conditions)
|
||||
PrüfeTrigger();
|
||||
|
||||
//--- Trade Management (Trade management)
|
||||
|
||||
VerwalteTrades();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ bool InitRSICrossOverReversal(string symbol)
|
||||
}
|
||||
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
rcData.trade.SetDeviationInPoints(RC_slippage);
|
||||
rcData.isInitialized = true;
|
||||
Print("RSICrossOverReversal: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
@@ -190,7 +191,13 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
|
||||
double emaSlope = (currentEMA - previousEMA) * 100;
|
||||
const double closeCurr = iClose(rcData.symbol, RC_TimeFrame1, 0);
|
||||
double priceToEmaDistance = (closeCurr - currentEMA) * 10;
|
||||
// Raw (close-EMA)*10 blows past threshold on XAUUSD (~2600) almost every bar — blocks all entries.
|
||||
// Compare distance in pips so RC_emaDistanceThreshold matches intent across symbols.
|
||||
const double point = SymbolInfoDouble(rcData.symbol, SYMBOL_POINT);
|
||||
const int symDig = (int)SymbolInfoInteger(rcData.symbol, SYMBOL_DIGITS);
|
||||
const double pipMult = (symDig == 3 || symDig == 5) ? 10.0 : 1.0;
|
||||
const double pipSize = (point > 0.0 ? point * pipMult : point);
|
||||
const double priceToEmaPips = (pipSize > 0.0 ? MathAbs(closeCurr - currentEMA) / pipSize : 0.0);
|
||||
|
||||
bool isBuyPosition = false;
|
||||
bool isSellPosition = false;
|
||||
@@ -209,7 +216,8 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
ApplyTrailingStop();
|
||||
|
||||
bool cooldownPassed = (currentTime - rcData.lastTradeTime) >= RC_cooldownSeconds;
|
||||
bool isTrendStrong = MathAbs(emaSlope) > RC_emaSlopeThreshold || MathAbs(priceToEmaDistance) > RC_emaDistanceThreshold;
|
||||
const bool isTrendStrong = RC_UseTrendStrengthFilter &&
|
||||
(MathAbs(emaSlope) > RC_emaSlopeThreshold || priceToEmaPips > RC_emaDistanceThreshold);
|
||||
|
||||
if(isBuyPosition && currentRSI > RC_exitBuyRSI)
|
||||
{
|
||||
@@ -233,10 +241,12 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(rcData.symbol, g_RC_LotSize);
|
||||
if(vol > 0.0)
|
||||
{
|
||||
rcData.lastTradeTime = currentTime;
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
if(rcData.trade.Sell(vol, rcData.symbol, 0.0, 0.0, 0.0, "Sell Order"))
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,10 +254,12 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(rcData.symbol, g_RC_LotSize);
|
||||
if(vol > 0.0)
|
||||
{
|
||||
rcData.lastTradeTime = currentTime;
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
if(rcData.trade.Buy(vol, rcData.symbol, 0.0, 0.0, 0.0, "Buy Order"))
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
//| RSIMidPointHijackStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
double RM_NormalizedLot(const string sym)
|
||||
{
|
||||
return United_NormalizeVolume(sym, g_RM_LotSize);
|
||||
}
|
||||
|
||||
bool IsNewBar(string symbol)
|
||||
{
|
||||
datetime time[];
|
||||
@@ -111,7 +116,9 @@ void CheckRSIFollowStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "RSI Follow");
|
||||
}
|
||||
rmData.rsiOverbought = false;
|
||||
}
|
||||
@@ -120,7 +127,9 @@ void CheckRSIFollowStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "RSI Follow");
|
||||
}
|
||||
rmData.rsiOversold = false;
|
||||
}
|
||||
@@ -154,7 +163,9 @@ void CheckRSIReverseStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "RSI Reverse");
|
||||
}
|
||||
rmData.rsiReverseOverbought = false;
|
||||
}
|
||||
@@ -163,7 +174,9 @@ void CheckRSIReverseStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "RSI Reverse");
|
||||
}
|
||||
rmData.rsiReverseOversold = false;
|
||||
}
|
||||
@@ -223,7 +236,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
rmData.emaCrossBuySignal = false;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +267,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
rmData.emaCrossSellSignal = false;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +282,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "EMA Cross");
|
||||
}
|
||||
}
|
||||
else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose)
|
||||
@@ -273,7 +292,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "EMA Cross");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,19 +65,14 @@ bool IsAsianSession()
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTradingAllowed(RSIReversalAsianData& data)
|
||||
{
|
||||
// Check if market is open
|
||||
long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode != SYMBOL_TRADE_MODE_FULL)
|
||||
{
|
||||
// Do not require SYMBOL_TRADE_MODE_FULL: many symbols allow one side only (long/short).
|
||||
const long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode == SYMBOL_TRADE_MODE_DISABLED || tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have enough money
|
||||
|
||||
if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -290,7 +285,7 @@ bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol,
|
||||
// Set trade parameters
|
||||
data.trade.SetExpertMagicNumber(MagicNumber);
|
||||
data.trade.SetDeviationInPoints(Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetTypeFillingBySymbol(symbol);
|
||||
|
||||
// Initialize state
|
||||
data.isPositionOpen = false;
|
||||
@@ -444,16 +439,16 @@ void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize)
|
||||
if(data.UseTakeProfit && tp <= currentBid)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetDeviationInPoints(data.Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetTypeFillingBySymbol(data.symbol);
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(data.symbol, tradeLotSize);
|
||||
if(vol <= 0.0)
|
||||
return;
|
||||
|
||||
if(data.trade.Buy(vol, data.symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy"))
|
||||
{
|
||||
data.isPositionOpen = true;
|
||||
data.positionOpenPrice = currentAsk;
|
||||
@@ -472,16 +467,16 @@ void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize)
|
||||
if(data.UseTakeProfit && tp >= currentAsk)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetDeviationInPoints(data.Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetTypeFillingBySymbol(data.symbol);
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(data.symbol, tradeLotSize);
|
||||
if(vol <= 0.0)
|
||||
return;
|
||||
|
||||
if(data.trade.Sell(vol, data.symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell"))
|
||||
{
|
||||
data.isPositionOpen = true;
|
||||
data.positionOpenPrice = currentBid;
|
||||
@@ -120,6 +120,70 @@ void RS_TryReversalEscape(RSIScalpingData& data, const ENUM_TIMEFRAMES tf, const
|
||||
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(data.symbol, SYMBOL_DIGITS)));
|
||||
}
|
||||
|
||||
void RS_ApplyTrailingStop(RSIScalpingData& data, const int MagicNumber,
|
||||
const bool useTrailingStop,
|
||||
const double trailingStopDistancePoints,
|
||||
const double trailingActivationPoints)
|
||||
{
|
||||
if(!useTrailingStop || trailingStopDistancePoints <= 0.0)
|
||||
return;
|
||||
if(!PositionSelectByMagic(data.symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const double point = SymbolInfoDouble(data.symbol, SYMBOL_POINT);
|
||||
if(point <= 0.0)
|
||||
return;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(data.symbol, SYMBOL_DIGITS);
|
||||
const double trail_dist = trailingStopDistancePoints * point;
|
||||
const double activation_pts = (trailingActivationPoints > 0.0)
|
||||
? trailingActivationPoints
|
||||
: trailingStopDistancePoints;
|
||||
const double activation = activation_pts * point;
|
||||
const long stops_level = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double cur_sl = PositionGetDouble(POSITION_SL);
|
||||
const double cur_tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
||||
if(bid - entry <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_sl < min_dist)
|
||||
new_sl = NormalizeDouble(bid - min_dist, digits);
|
||||
|
||||
if(new_sl >= bid || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl <= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(data.trade, data.symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(data.symbol, SYMBOL_ASK);
|
||||
if(entry - ask <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_sl - ask < min_dist)
|
||||
new_sl = NormalizeDouble(ask + min_dist, digits);
|
||||
|
||||
if(new_sl <= ask || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl >= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(data.trade, data.symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
}
|
||||
|
||||
string ErrorDescription(int errorCode)
|
||||
{
|
||||
switch(errorCode)
|
||||
@@ -364,21 +428,7 @@ void CheckEntrySignals(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int Mag
|
||||
//+------------------------------------------------------------------+
|
||||
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;
|
||||
return United_NormalizeVolume(symbol, lotSize);
|
||||
}
|
||||
|
||||
void OpenBuyPosition(RSIScalpingData& data, int MagicNumber, double LotSize)
|
||||
@@ -520,7 +570,8 @@ void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES Ti
|
||||
double RSI_Oversold, double RSI_Target_Buy, double RSI_Target_Sell,
|
||||
int BarsToWait, double LotSize, int MagicNumber,
|
||||
bool UseReversalEscape, int ReversalATRPeriod, double ReversalAdverseAtrMult,
|
||||
int ReversalSignsRequired, double ReversalRsiVelocity, double ReversalBodyAtrMult)
|
||||
int ReversalSignsRequired, double ReversalRsiVelocity, double ReversalBodyAtrMult,
|
||||
bool UseTrailingStop, double TrailingStopDistancePoints, double TrailingActivationPoints)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!data.isInitialized)
|
||||
@@ -543,6 +594,10 @@ void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES Ti
|
||||
RS_TryReversalEscape(data, TimeFrame, MagicNumber, ReversalATRPeriod, ReversalAdverseAtrMult,
|
||||
ReversalSignsRequired, ReversalRsiVelocity, ReversalBodyAtrMult);
|
||||
|
||||
if(in_pos)
|
||||
RS_ApplyTrailingStop(data, MagicNumber, UseTrailingStop,
|
||||
TrailingStopDistancePoints, TrailingActivationPoints);
|
||||
|
||||
if(!new_bar)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSISecretSauceStrategy.mqh |
|
||||
//| Cluster-0 orchestrator: RSI leave extreme then peak/bottom entry |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef RSI_SECRET_SAUCE_STRATEGY_MQH
|
||||
#define RSI_SECRET_SAUCE_STRATEGY_MQH
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
#include <Trade/PositionInfo.mqh>
|
||||
|
||||
struct RSISecretSauceOrcData
|
||||
{
|
||||
string actualSymbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
CPositionInfo positionInfo;
|
||||
int rsiHandle;
|
||||
int atrHandle;
|
||||
double rsiBuffer[];
|
||||
double atrBuffer[];
|
||||
double highBuffer[];
|
||||
double lowBuffer[];
|
||||
bool rsiWasOverbought;
|
||||
bool rsiWasOversold;
|
||||
bool rsiBackInRange;
|
||||
datetime lastRSIExitTime;
|
||||
datetime lastRSIReentryTime;
|
||||
datetime lastTradeTime;
|
||||
datetime lastBarTime;
|
||||
};
|
||||
|
||||
bool RSS_UpdateIndicators(RSISecretSauceOrcData &d)
|
||||
{
|
||||
int rsiBarsNeeded = RSS_RSILookback + 5;
|
||||
if(CopyBuffer(d.rsiHandle, 0, 0, rsiBarsNeeded, d.rsiBuffer) < rsiBarsNeeded)
|
||||
return false;
|
||||
if(CopyBuffer(d.atrHandle, 0, 0, 2, d.atrBuffer) < 2)
|
||||
return false;
|
||||
if(CopyHigh(d.actualSymbol, RSS_Timeframe, 0, RSS_SwingLookback + 5, d.highBuffer) < RSS_SwingLookback + 5)
|
||||
return false;
|
||||
if(CopyLow(d.actualSymbol, RSS_Timeframe, 0, RSS_SwingLookback + 5, d.lowBuffer) < RSS_SwingLookback + 5)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RSS_UpdateRSIState(RSISecretSauceOrcData &d)
|
||||
{
|
||||
double rsiCurrent = d.rsiBuffer[0];
|
||||
double rsiPrev = d.rsiBuffer[1];
|
||||
|
||||
if(rsiPrev >= RSS_RSIOverbought && rsiCurrent < RSS_RSIOverbought)
|
||||
{
|
||||
d.rsiWasOverbought = true;
|
||||
d.rsiBackInRange = true;
|
||||
d.lastRSIExitTime = TimeCurrent();
|
||||
d.lastRSIReentryTime = TimeCurrent();
|
||||
}
|
||||
|
||||
if(rsiPrev <= RSS_RSIOversold && rsiCurrent > RSS_RSIOversold)
|
||||
{
|
||||
d.rsiWasOversold = true;
|
||||
d.rsiBackInRange = true;
|
||||
d.lastRSIExitTime = TimeCurrent();
|
||||
d.lastRSIReentryTime = TimeCurrent();
|
||||
}
|
||||
|
||||
if(rsiCurrent >= RSS_RSIOverbought)
|
||||
{
|
||||
d.rsiWasOverbought = false;
|
||||
d.rsiBackInRange = false;
|
||||
}
|
||||
|
||||
if(rsiCurrent <= RSS_RSIOversold)
|
||||
{
|
||||
d.rsiWasOversold = false;
|
||||
d.rsiBackInRange = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RSS_IsRSIPeak(RSISecretSauceOrcData &d)
|
||||
{
|
||||
if(ArraySize(d.rsiBuffer) < RSS_PeakBars + 2)
|
||||
return false;
|
||||
double currentRSI = d.rsiBuffer[0];
|
||||
bool isPeak = true;
|
||||
for(int i = 1; i <= RSS_PeakBars; i++)
|
||||
{
|
||||
if(d.rsiBuffer[i] >= currentRSI)
|
||||
{
|
||||
isPeak = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d.rsiBuffer[1] >= currentRSI)
|
||||
isPeak = false;
|
||||
return isPeak;
|
||||
}
|
||||
|
||||
bool RSS_IsRSIBottom(RSISecretSauceOrcData &d)
|
||||
{
|
||||
if(ArraySize(d.rsiBuffer) < RSS_PeakBars + 2)
|
||||
return false;
|
||||
double currentRSI = d.rsiBuffer[0];
|
||||
bool isBottom = true;
|
||||
for(int i = 1; i <= RSS_PeakBars; i++)
|
||||
{
|
||||
if(d.rsiBuffer[i] <= currentRSI)
|
||||
{
|
||||
isBottom = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d.rsiBuffer[1] <= currentRSI)
|
||||
isBottom = false;
|
||||
return isBottom;
|
||||
}
|
||||
|
||||
double RSS_GetSwingStopLoss(RSISecretSauceOrcData &d, double currentPrice, ENUM_POSITION_TYPE type)
|
||||
{
|
||||
if(type == POSITION_TYPE_BUY)
|
||||
{
|
||||
double lowestLow = d.lowBuffer[0];
|
||||
for(int i = 1; i < RSS_SwingLookback && i < ArraySize(d.lowBuffer); i++)
|
||||
{
|
||||
if(d.lowBuffer[i] < lowestLow)
|
||||
lowestLow = d.lowBuffer[i];
|
||||
}
|
||||
return lowestLow;
|
||||
}
|
||||
double highestHigh = d.highBuffer[0];
|
||||
for(int i = 1; i < RSS_SwingLookback && i < ArraySize(d.highBuffer); i++)
|
||||
{
|
||||
if(d.highBuffer[i] > highestHigh)
|
||||
highestHigh = d.highBuffer[i];
|
||||
}
|
||||
return highestHigh;
|
||||
}
|
||||
|
||||
bool RSS_CalculateStops(RSISecretSauceOrcData &d, double price, ENUM_POSITION_TYPE type, double &sl, double &tp)
|
||||
{
|
||||
double atrValue = d.atrBuffer[0];
|
||||
if(atrValue <= 0)
|
||||
atrValue = price * 0.01;
|
||||
|
||||
double slDistance = atrValue * RSS_StopLossATR;
|
||||
double tpDistance = atrValue * RSS_TakeProfitATR;
|
||||
|
||||
int digits = (int)SymbolInfoInteger(d.actualSymbol, SYMBOL_DIGITS);
|
||||
double point = SymbolInfoDouble(d.actualSymbol, SYMBOL_POINT);
|
||||
int stopsLevel = (int)SymbolInfoInteger(d.actualSymbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double minStopDistance = MathMax(stopsLevel * point, point * 10);
|
||||
|
||||
if(RSS_UseSwingStopLoss)
|
||||
{
|
||||
double swingStop = RSS_GetSwingStopLoss(d, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool RSS_CanOpenNewPosition(RSISecretSauceOrcData &d)
|
||||
{
|
||||
int positionCount = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(d.positionInfo.SelectByIndex(i))
|
||||
{
|
||||
if(d.positionInfo.Symbol() == d.actualSymbol && d.positionInfo.Magic() == RSS_MagicNumber)
|
||||
positionCount++;
|
||||
}
|
||||
}
|
||||
if(positionCount >= RSS_MaxPositions)
|
||||
return false;
|
||||
|
||||
if(d.lastTradeTime > 0)
|
||||
{
|
||||
int barsSince = Bars(d.actualSymbol, RSS_Timeframe, d.lastTradeTime, TimeCurrent());
|
||||
if(barsSince < RSS_MinBarsBetweenTrades)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RSS_OpenPosition(RSISecretSauceOrcData &d, ENUM_POSITION_TYPE type, const double lotSize)
|
||||
{
|
||||
double price = (type == POSITION_TYPE_BUY) ?
|
||||
SymbolInfoDouble(d.actualSymbol, SYMBOL_ASK) :
|
||||
SymbolInfoDouble(d.actualSymbol, SYMBOL_BID);
|
||||
|
||||
if(price <= 0)
|
||||
return;
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(!RSS_CalculateStops(d, price, type, sl, tp))
|
||||
return;
|
||||
|
||||
string comment = "RSI_Secret_" + (type == POSITION_TYPE_BUY ? "LONG" : "SHORT");
|
||||
|
||||
bool result = false;
|
||||
if(type == POSITION_TYPE_BUY)
|
||||
result = d.trade.Buy(lotSize, d.actualSymbol, 0, sl, tp, comment);
|
||||
else
|
||||
result = d.trade.Sell(lotSize, d.actualSymbol, 0, sl, tp, comment);
|
||||
|
||||
if(result)
|
||||
{
|
||||
d.lastTradeTime = TimeCurrent();
|
||||
if(type == POSITION_TYPE_BUY)
|
||||
d.rsiWasOverbought = false;
|
||||
else
|
||||
d.rsiWasOversold = false;
|
||||
d.rsiBackInRange = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RSS_CheckEntrySignals(RSISecretSauceOrcData &d, const double lotSize)
|
||||
{
|
||||
if(d.rsiWasOverbought && d.rsiBackInRange)
|
||||
{
|
||||
if(d.rsiBuffer[0] < RSS_RSIOverbought && RSS_IsRSIPeak(d))
|
||||
RSS_OpenPosition(d, POSITION_TYPE_BUY, lotSize);
|
||||
}
|
||||
|
||||
if(d.rsiWasOversold && d.rsiBackInRange)
|
||||
{
|
||||
if(d.rsiBuffer[0] > RSS_RSIOversold && RSS_IsRSIBottom(d))
|
||||
RSS_OpenPosition(d, POSITION_TYPE_SELL, lotSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool InitRSISecretSauce(RSISecretSauceOrcData &d, const string symbol)
|
||||
{
|
||||
d.isInitialized = false;
|
||||
d.rsiHandle = INVALID_HANDLE;
|
||||
d.atrHandle = INVALID_HANDLE;
|
||||
d.rsiWasOverbought = false;
|
||||
d.rsiWasOversold = false;
|
||||
d.rsiBackInRange = false;
|
||||
d.lastRSIExitTime = 0;
|
||||
d.lastRSIReentryTime = 0;
|
||||
d.lastTradeTime = 0;
|
||||
d.lastBarTime = 0;
|
||||
d.actualSymbol = symbol;
|
||||
StringTrimLeft(d.actualSymbol);
|
||||
StringTrimRight(d.actualSymbol);
|
||||
if(StringLen(d.actualSymbol) == 0)
|
||||
d.actualSymbol = _Symbol;
|
||||
|
||||
if(!SymbolSelect(d.actualSymbol, true))
|
||||
{
|
||||
Print("RSISecretSauce: symbol not available '", d.actualSymbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
d.rsiHandle = iRSI(d.actualSymbol, RSS_Timeframe, RSS_RSIPeriod, PRICE_CLOSE);
|
||||
d.atrHandle = iATR(d.actualSymbol, RSS_Timeframe, RSS_ATRPeriod);
|
||||
if(d.rsiHandle == INVALID_HANDLE || d.atrHandle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
ArraySetAsSeries(d.rsiBuffer, true);
|
||||
ArraySetAsSeries(d.atrBuffer, true);
|
||||
ArraySetAsSeries(d.highBuffer, true);
|
||||
ArraySetAsSeries(d.lowBuffer, true);
|
||||
|
||||
d.trade.SetExpertMagicNumber(RSS_MagicNumber);
|
||||
d.trade.SetDeviationInPoints(RSS_Slippage);
|
||||
d.trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
d.isInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitRSISecretSauce(RSISecretSauceOrcData &d)
|
||||
{
|
||||
if(d.rsiHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.rsiHandle);
|
||||
if(d.atrHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.atrHandle);
|
||||
d.rsiHandle = INVALID_HANDLE;
|
||||
d.atrHandle = INVALID_HANDLE;
|
||||
d.isInitialized = false;
|
||||
}
|
||||
|
||||
void ProcessRSISecretSauce(RSISecretSauceOrcData &d, const double lotSize)
|
||||
{
|
||||
if(!d.isInitialized)
|
||||
return;
|
||||
|
||||
int requiredBars = MathMax(RSS_RSILookback, RSS_SwingLookback) + 10;
|
||||
if(Bars(d.actualSymbol, RSS_Timeframe) < requiredBars)
|
||||
return;
|
||||
|
||||
datetime currentBarTime = iTime(d.actualSymbol, RSS_Timeframe, 0);
|
||||
if(currentBarTime == d.lastBarTime)
|
||||
return;
|
||||
|
||||
d.lastBarTime = currentBarTime;
|
||||
|
||||
if(!RSS_UpdateIndicators(d))
|
||||
return;
|
||||
|
||||
RSS_UpdateRSIState(d);
|
||||
|
||||
if(RSS_CanOpenNewPosition(d))
|
||||
RSS_CheckEntrySignals(d, lotSize);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -51,6 +51,12 @@ void SuperEMA_Log(SuperEMAData &d, const string s)
|
||||
Print("[SuperEMA] ", s);
|
||||
}
|
||||
|
||||
double SuperEMA_Point(const SuperEMAData &d)
|
||||
{
|
||||
double pt = SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
return (pt > 0.0 ? pt : _Point);
|
||||
}
|
||||
|
||||
bool SuperEMA_IsNewBar(SuperEMAData &d)
|
||||
{
|
||||
datetime t = iTime(d.symbol, d.tf, 0);
|
||||
@@ -176,7 +182,8 @@ bool SuperEMA_PullbackNearFastEmaLong(SuperEMAData &d)
|
||||
double lo = iLow(d.symbol, d.tf, 1);
|
||||
if(emaF <= 0.0)
|
||||
return false;
|
||||
return (lo <= emaF + d.slBufferPoints * _Point * 3.0);
|
||||
const double pt = SuperEMA_Point(d);
|
||||
return (lo <= emaF + d.slBufferPoints * pt * 3.0);
|
||||
}
|
||||
|
||||
bool SuperEMA_PullbackNearFastEmaShort(SuperEMAData &d)
|
||||
@@ -185,7 +192,8 @@ bool SuperEMA_PullbackNearFastEmaShort(SuperEMAData &d)
|
||||
double hi = iHigh(d.symbol, d.tf, 1);
|
||||
if(emaF <= 0.0)
|
||||
return false;
|
||||
return (hi >= emaF - d.slBufferPoints * _Point * 3.0);
|
||||
const double pt = SuperEMA_Point(d);
|
||||
return (hi >= emaF - d.slBufferPoints * pt * 3.0);
|
||||
}
|
||||
|
||||
int SuperEMA_PositionsByMagic(SuperEMAData &d)
|
||||
@@ -196,6 +204,8 @@ int SuperEMA_PositionsByMagic(SuperEMAData &d)
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(t))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == d.symbol && (int)PositionGetInteger(POSITION_MAGIC) == d.magic)
|
||||
n++;
|
||||
}
|
||||
@@ -209,7 +219,7 @@ void SuperEMA_ComputeSLTP(SuperEMAData &d, const bool isBuy, double &sl, double
|
||||
if(!d.useStructuralSL)
|
||||
return;
|
||||
double emaM = SuperEMA_EmaAt(d, d.emaMid, d.emaTrendBars);
|
||||
double buf = d.slBufferPoints * _Point;
|
||||
double buf = d.slBufferPoints * SuperEMA_Point(d);
|
||||
if(isBuy)
|
||||
sl = emaM - buf;
|
||||
else
|
||||
@@ -418,9 +428,13 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
|
||||
SuperEMA_ManageExits(d);
|
||||
|
||||
// Same order as standalone SuperEMAXAUUSD: skip entry logic when flat is not allowed.
|
||||
if(d.oneTradeOnly && SuperEMA_PositionsByMagic(d) > 0)
|
||||
return;
|
||||
|
||||
const int sh = d.emaTrendBars;
|
||||
double h1 = 0.0, h2 = 0.0;
|
||||
if(!SuperEMA_MacdHistAt(d, 1, h1) || !SuperEMA_MacdHistAt(d, 2, h2))
|
||||
double h1 = 0.0;
|
||||
if(!SuperEMA_MacdHistAt(d, 1, h1))
|
||||
return;
|
||||
|
||||
bool up = SuperEMA_TrendUp(d, sh);
|
||||
@@ -453,14 +467,14 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
break;
|
||||
}
|
||||
|
||||
if(d.oneTradeOnly && SuperEMA_PositionsByMagic(d) > 0)
|
||||
if(!wantBuy && !wantSell)
|
||||
return;
|
||||
|
||||
const double vol = United_NormalizeVolume(d.symbol, lots);
|
||||
if(vol <= 0.0)
|
||||
{
|
||||
if(wantBuy && !United_MayOpenNewEntry(d.symbol, (ulong)d.magic, true))
|
||||
wantBuy = false;
|
||||
if(wantSell && !United_MayOpenNewEntry(d.symbol, (ulong)d.magic, false))
|
||||
wantSell = false;
|
||||
if(!wantBuy && !wantSell)
|
||||
return;
|
||||
SuperEMA_Log(d, "Skip entry: normalized volume <= 0");
|
||||
return;
|
||||
}
|
||||
|
||||
MqlTick tick;
|
||||
@@ -474,7 +488,7 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
#ifndef UNITED_MARTINGALE_NO_SELF_CLOSE
|
||||
SuperEMA_ComputeSLTP(d, true, sl, tp);
|
||||
#endif
|
||||
if(d.trade.Buy(lots, d.symbol, tick.ask, sl, tp, "United SuperEMA long"))
|
||||
if(d.trade.Buy(vol, d.symbol, tick.ask, sl, tp, "United SuperEMA long"))
|
||||
SuperEMA_Log(d, StringFormat("BUY ask=%.5f sl=%.5f", tick.ask, sl));
|
||||
}
|
||||
else if(wantSell && !wantBuy)
|
||||
@@ -482,7 +496,7 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
#ifndef UNITED_MARTINGALE_NO_SELF_CLOSE
|
||||
SuperEMA_ComputeSLTP(d, false, sl, tp);
|
||||
#endif
|
||||
if(d.trade.Sell(lots, d.symbol, tick.bid, sl, tp, "United SuperEMA short"))
|
||||
if(d.trade.Sell(vol, d.symbol, tick.bid, sl, tp, "United SuperEMA short"))
|
||||
SuperEMA_Log(d, StringFormat("SELL bid=%.5f sl=%.5f", tick.bid, sl));
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,17 @@
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property version "1.21"
|
||||
#property strict
|
||||
#property description "LOT_* nominal at ORCH_ReferenceBalance; scale = balance/equity ÷ reference (clamped). No performance-evaluator ranking."
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#define UNITED_V2_DYNAMIC_LOTS
|
||||
double g_DB_LotSize;
|
||||
// Include strategy implementations early so structs are available
|
||||
#include "Strategies/DarvasBoxStrategy.mqh"
|
||||
#include "Strategies/EMASlopeDistanceStrategy.mqh"
|
||||
@@ -22,6 +25,8 @@
|
||||
#include "Strategies/SuperEMAStrategy.mqh"
|
||||
#include "Strategies/RSIReversalAsianStrategy.mqh"
|
||||
#include "Strategies/RSIConsolidationStrategy.mqh"
|
||||
#include "Strategies/SimpleTrendlineStrategy.mqh"
|
||||
#include "Strategies/RSISecretSauceStrategy.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Lot Size Variables (for dynamic lot sizing) |
|
||||
@@ -29,10 +34,20 @@
|
||||
double g_ES_LotSize; // EMA Slope Distance lot size
|
||||
double g_RC_LotSize; // RSI CrossOver Reversal lot size
|
||||
double g_RM_LotSize; // RSI MidPoint Hijack lot size
|
||||
double g_LotScaleGrowth = 1.0;
|
||||
double g_LotScaleMargin = 1.0;
|
||||
double g_LotScaleFinal = 1.0;
|
||||
datetime g_LastScaleLogTime = 0;
|
||||
|
||||
double g_Pos_RS_APPL;
|
||||
double g_Pos_RS_BTCUSD;
|
||||
double g_Pos_RS_NVDA;
|
||||
double g_Pos_RS_TSLA;
|
||||
double g_Pos_RS_XAUUSD;
|
||||
double g_Pos_RRA_EURUSD;
|
||||
double g_Pos_RRA_AUDUSD;
|
||||
double g_Pos_SE;
|
||||
double g_Pos_RCO;
|
||||
double g_Pos_ST_BTCUSD;
|
||||
double g_Pos_ST_XAUUSD;
|
||||
double g_Pos_ST_GER40;
|
||||
double g_RSS_LotSize;
|
||||
|
||||
bool United_MayOpenNewEntry(const string symbol, const ulong magic, const bool isBuy)
|
||||
{
|
||||
@@ -58,139 +73,36 @@ input bool EnableSuperEMA = true;
|
||||
input bool EnableRSIConsolidation = true;
|
||||
input bool EnableRSIReversalAsianEURUSD = true;
|
||||
input bool EnableRSIReversalAsianAUDUSD = true;
|
||||
input bool EnableSimpleTrendlineBTCUSD = true;
|
||||
input bool EnableSimpleTrendlineXAUUSD = true;
|
||||
input bool EnableSimpleTrendlineGER40 = true;
|
||||
input bool EnableRSISecretSauce = true;
|
||||
|
||||
input group "=== Centralized Lot Size (Granular Per Robot) ==="
|
||||
input double LOT_DB_DarvasBox = 0.05;
|
||||
input double LOT_ES_EMASlopeDistance = 0.05;
|
||||
input double LOT_RC_RSICrossOver = 0.1;
|
||||
input double LOT_RM_RSIMidPointHijack = 0.01;
|
||||
input double LOT_RS_APPL = 100.0;
|
||||
input double LOT_RS_BTCUSD = 0.15;
|
||||
input double LOT_RS_NVDA = 60.0;
|
||||
input double LOT_RS_TSLA = 20.0;
|
||||
input double LOT_RS_XAUUSD = 0.02;
|
||||
input double LOT_RRA_EURUSD = 0.01;
|
||||
input double LOT_RRA_AUDUSD = 0.10;
|
||||
input double LOT_SE_SuperEMA = 0.01;
|
||||
input double LOT_RCO_RSIConsolidation = 0.04;
|
||||
input double LOT_RC_RSICrossOver = 0.06;
|
||||
input double LOT_RM_RSIMidPointHijack = 0.03;
|
||||
input double LOT_RS_APPL = 5.0;
|
||||
input double LOT_RS_BTCUSD = 0.1;
|
||||
input double LOT_RS_NVDA = 10.0;
|
||||
input double LOT_RS_TSLA = 15.0;
|
||||
input double LOT_RS_XAUUSD = 0.1;
|
||||
input double LOT_RRA_EURUSD = 0.05;
|
||||
input double LOT_RRA_AUDUSD = 0.08;
|
||||
input double LOT_SE_SuperEMA = 0.02;
|
||||
input double LOT_RCO_RSIConsolidation = 0.02;
|
||||
input double LOT_ST_BTCUSD = 0.07;
|
||||
input double LOT_ST_XAUUSD = 0.01;
|
||||
input double LOT_ST_GER40 = 0.10;
|
||||
input double LOT_RSS_SecretSauce = 0.01;
|
||||
|
||||
input group "=== Auto Lot Scaling (Equity/Balance + Margin Guard) ==="
|
||||
input bool EnableAutoLotScaling = true;
|
||||
input double ScalingReferenceBalanceUSD = 1000.0;
|
||||
input double ScalingCurveExponent = 0.70; // 1.0=linear, <1 smoother, >1 aggressive
|
||||
input double ScaleMinMultiplier = 0.50;
|
||||
input double ScaleMaxMultiplier = 3.00;
|
||||
input bool EnableMarginGuard = true;
|
||||
input double MarginSafeLevelPercent = 420.0; // >= safe: no reduction
|
||||
input double MarginCriticalLevelPercent = 170.0;
|
||||
input double MarginGuardMinMultiplier = 0.20;
|
||||
input int ScaleLogIntervalSeconds = 300;
|
||||
input bool ShowScaleStatusOnChart = true;
|
||||
|
||||
input group "=== Minimum Balance Recommendation ==="
|
||||
input bool PrintMinimumBalanceRecommendation = true;
|
||||
input double MinBalPerLot_ES = 1200.0;
|
||||
input double MinBalPerLot_RC = 900.0;
|
||||
input double MinBalPerLot_RM = 900.0;
|
||||
input double MinBalPerLot_RS_APPL = 8.0;
|
||||
input double MinBalPerLot_RS_BTCUSD = 2500.0;
|
||||
input double MinBalPerLot_RS_NVDA = 8.0;
|
||||
input double MinBalPerLot_RS_TSLA = 8.0;
|
||||
input double MinBalPerLot_RS_XAUUSD = 3000.0;
|
||||
input double MinBalPerLot_RRA_EURUSD = 1200.0;
|
||||
input double MinBalPerLot_RRA_AUDUSD = 1200.0;
|
||||
input double MinBalPerLot_SE = 1500.0;
|
||||
input double MinBalPerLot_RCO = 1800.0;
|
||||
input double MinBalanceSafetyBufferPercent = 25.0;
|
||||
input double MinAbsoluteRecommendedBalance = 300.0;
|
||||
|
||||
double ClampValue(const double v, const double minV, const double maxV)
|
||||
{
|
||||
if(v < minV) return minV;
|
||||
if(v > maxV) return maxV;
|
||||
return v;
|
||||
}
|
||||
|
||||
double GetAutoScaledLot(const double baseLot)
|
||||
{
|
||||
const double scaled = baseLot * g_LotScaleFinal;
|
||||
if(scaled <= 0.0)
|
||||
return 0.0;
|
||||
return scaled;
|
||||
}
|
||||
|
||||
double ComputeRecommendedMinBalance()
|
||||
{
|
||||
double required = 0.0;
|
||||
required += LOT_ES_EMASlopeDistance * MinBalPerLot_ES;
|
||||
required += LOT_RC_RSICrossOver * MinBalPerLot_RC;
|
||||
required += LOT_RM_RSIMidPointHijack * MinBalPerLot_RM;
|
||||
required += LOT_RS_APPL * MinBalPerLot_RS_APPL;
|
||||
required += LOT_RS_BTCUSD * MinBalPerLot_RS_BTCUSD;
|
||||
required += LOT_RS_NVDA * MinBalPerLot_RS_NVDA;
|
||||
required += LOT_RS_TSLA * MinBalPerLot_RS_TSLA;
|
||||
required += LOT_RS_XAUUSD * MinBalPerLot_RS_XAUUSD;
|
||||
required += LOT_RRA_EURUSD * MinBalPerLot_RRA_EURUSD;
|
||||
required += LOT_RRA_AUDUSD * MinBalPerLot_RRA_AUDUSD;
|
||||
required += LOT_SE_SuperEMA * MinBalPerLot_SE;
|
||||
required += LOT_RCO_RSIConsolidation * MinBalPerLot_RCO;
|
||||
required *= (1.0 + MinBalanceSafetyBufferPercent / 100.0);
|
||||
if(required < MinAbsoluteRecommendedBalance)
|
||||
required = MinAbsoluteRecommendedBalance;
|
||||
return required;
|
||||
}
|
||||
|
||||
void UpdateAutoLotScaling()
|
||||
{
|
||||
if(!EnableAutoLotScaling)
|
||||
{
|
||||
g_LotScaleGrowth = 1.0;
|
||||
g_LotScaleMargin = 1.0;
|
||||
g_LotScaleFinal = 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
double refBalance = ScalingReferenceBalanceUSD;
|
||||
if(refBalance < 1.0)
|
||||
refBalance = 1.0;
|
||||
|
||||
const double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
const double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||||
double base = MathMax(balance, equity);
|
||||
if(base < 1.0)
|
||||
base = 1.0;
|
||||
|
||||
const double growthRatio = base / refBalance;
|
||||
g_LotScaleGrowth = MathPow(growthRatio, ScalingCurveExponent);
|
||||
g_LotScaleGrowth = ClampValue(g_LotScaleGrowth, ScaleMinMultiplier, ScaleMaxMultiplier);
|
||||
|
||||
g_LotScaleMargin = 1.0;
|
||||
if(EnableMarginGuard)
|
||||
{
|
||||
const double ml = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
|
||||
if(ml <= 0.0 || ml != ml)
|
||||
{
|
||||
g_LotScaleMargin = 1.0;
|
||||
}
|
||||
else if(ml >= MarginSafeLevelPercent)
|
||||
{
|
||||
g_LotScaleMargin = 1.0;
|
||||
}
|
||||
else if(ml <= MarginCriticalLevelPercent)
|
||||
{
|
||||
g_LotScaleMargin = MarginGuardMinMultiplier;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double span = MarginSafeLevelPercent - MarginCriticalLevelPercent;
|
||||
const double t = (span > 0.0) ? (ml - MarginCriticalLevelPercent) / span : 0.0;
|
||||
g_LotScaleMargin = MarginGuardMinMultiplier + t * (1.0 - MarginGuardMinMultiplier);
|
||||
}
|
||||
g_LotScaleMargin = ClampValue(g_LotScaleMargin, MarginGuardMinMultiplier, 1.0);
|
||||
}
|
||||
|
||||
g_LotScaleFinal = g_LotScaleGrowth * g_LotScaleMargin;
|
||||
g_LotScaleFinal = ClampValue(g_LotScaleFinal, ScaleMinMultiplier, ScaleMaxMultiplier);
|
||||
}
|
||||
input group "=== Balance-based position sizing ==="
|
||||
input bool ORCH_ScaleLotsByBalance = true;
|
||||
input bool ORCH_UseEquityInsteadOfBalance = false;
|
||||
input double ORCH_ReferenceBalance = 10000.0;
|
||||
input double ORCH_MinBalanceScale = 0.1;
|
||||
input double ORCH_MaxBalanceScale = 10.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 1: DarvasBoxXAUUSD |
|
||||
@@ -203,7 +115,7 @@ input int DB_VolumeThreshold = 0; // Set to 0 to disable volume threshold ch
|
||||
input double DB_StopLoss = 1665;
|
||||
input double DB_TakeProfit = 3685;
|
||||
input bool DB_EnableLogging = false;
|
||||
input color DB_BoxColor = clrBlue;
|
||||
input color DB_BoxColor = (color)16711680;
|
||||
input int DB_BoxWidth = 1;
|
||||
input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2;
|
||||
input int DB_MA_Period = 125;
|
||||
@@ -212,6 +124,8 @@ 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 bool DB_UseVolumeSpikeFilter = true;
|
||||
input bool DB_UseTrendFilter = true;
|
||||
input int DB_MagicNumber = 135790;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -224,7 +138,11 @@ 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_TrailingStop = 370.0;
|
||||
input bool ES_UseTrailingStop = true;
|
||||
input double ES_TrailingActivationPips = 0.0;
|
||||
input bool ES_UseStaleStopLossExit = false;
|
||||
input int ES_StaleStopLossSeconds = 33800;
|
||||
input double ES_LotGröße = 0.03;
|
||||
input int ES_MagicNumber = 12350;
|
||||
input bool ES_UseSpreadAdjustment = true;
|
||||
@@ -233,6 +151,11 @@ input bool ES_UseBarData = true;
|
||||
input int ES_MaxTradesPerCrossover = 9;
|
||||
input int ES_ProfitCheckBars = 18;
|
||||
input bool ES_CloseUnprofitableTrades = true;
|
||||
input bool ES_UseWeeklyADXFilter = true;
|
||||
input int ES_WeeklyADXPeriod = 15;
|
||||
input double ES_WeeklyADXMin = 40.0;
|
||||
input int ES_WeeklyADXBarShift = 2;
|
||||
input bool ES_WeeklyADXUseDirection = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 3: RSICrossOverReversalXAUUSD |
|
||||
@@ -258,6 +181,7 @@ input double RC_exitBuyRSI = 86;
|
||||
input double RC_exitSellRSI = 10;
|
||||
input double RC_TrailingStop = 295;
|
||||
input double RC_emaDistanceThreshold = 165;
|
||||
input bool RC_UseTrendStrengthFilter = true;
|
||||
input int RC_tradingHourOneBegin = 24;
|
||||
input int RC_tradingHourOneEnd = 22;
|
||||
input int RC_tradingHourTwoBegin = 6;
|
||||
@@ -330,7 +254,7 @@ input int RM_InpEMADistancePeriod = 26;
|
||||
//| 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 string RS_APPL_Symbol = "AAPL.NAS"; // Pepperstone / match tester set (also try AAPL.US)
|
||||
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;
|
||||
@@ -358,7 +282,7 @@ 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 string RS_NVDA_Symbol = "NVDA.NAS"; // Pepperstone / match tester set (also try NVDA.US)
|
||||
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;
|
||||
@@ -372,7 +296,7 @@ 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 string RS_TSLA_Symbol = "TSLA.NAS"; // Pepperstone / match tester set (also try TSLA.US)
|
||||
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;
|
||||
@@ -407,6 +331,31 @@ input int RS_ReversalSignsRequired = 2;
|
||||
input double RS_ReversalRsiVelocity = 16.0;
|
||||
input double RS_ReversalBodyAtrMult = 5.1;
|
||||
|
||||
input group "=== RSI Scalping APPL — Trailing (cluster-fuck BTC-style defaults) ==="
|
||||
input bool RS_APPL_UseTrailingStop = true;
|
||||
input double RS_APPL_TrailDistancePoints = 120.0;
|
||||
input double RS_APPL_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping BTCUSD — Trailing ==="
|
||||
input bool RS_BTCUSD_UseTrailingStop = true;
|
||||
input double RS_BTCUSD_TrailDistancePoints = 120.0;
|
||||
input double RS_BTCUSD_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping NVDA — Trailing ==="
|
||||
input bool RS_NVDA_UseTrailingStop = true;
|
||||
input double RS_NVDA_TrailDistancePoints = 375.0;
|
||||
input double RS_NVDA_TrailActivationPoints = 75.0;
|
||||
|
||||
input group "=== RSI Scalping TSLA — Trailing ==="
|
||||
input bool RS_TSLA_UseTrailingStop = true;
|
||||
input double RS_TSLA_TrailDistancePoints = 900.0;
|
||||
input double RS_TSLA_TrailActivationPoints = 950.0;
|
||||
|
||||
input group "=== RSI Scalping XAUUSD — Trailing ==="
|
||||
input bool RS_XAUUSD_UseTrailingStop = true;
|
||||
input double RS_XAUUSD_TrailDistancePoints = 71.0;
|
||||
input double RS_XAUUSD_TrailActivationPoints = 41.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 11-12: RSI Reversal Asian Strategies |
|
||||
//| Each RSI Reversal Asian strategy trades on its own symbol: |
|
||||
@@ -508,6 +457,108 @@ input ulong RCO_MagicNumber = 20250420;
|
||||
input int RCO_Slippage = 10;
|
||||
input int RCO_MaxSpreadPoints = 28;
|
||||
|
||||
input group "=== SimpleTrendline BTCUSD ==="
|
||||
input string ST_BTC_Symbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES ST_BTC_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_BTC_HigherTF = PERIOD_H4;
|
||||
input int ST_BTC_MAPeriod = 150;
|
||||
input ENUM_MA_METHOD ST_BTC_MAMethod = MODE_SMMA;
|
||||
input ENUM_APPLIED_PRICE ST_BTC_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_BTC_HTFBarsToScan = 1200;
|
||||
input double ST_BTC_LineTouchTolerance = 170.0;
|
||||
input double ST_BTC_BreakBuffer = 90.0;
|
||||
input ulong ST_BTC_MagicNumber = 26042501;
|
||||
input bool ST_BTC_DrawTrendline = true;
|
||||
|
||||
input group "=== SimpleTrendline XAUUSD ==="
|
||||
input string ST_XAU_Symbol = "XAUUSD";
|
||||
input ENUM_TIMEFRAMES ST_XAU_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_XAU_HigherTF = PERIOD_M10;
|
||||
input int ST_XAU_MAPeriod = 65;
|
||||
input ENUM_MA_METHOD ST_XAU_MAMethod = MODE_EMA;
|
||||
input ENUM_APPLIED_PRICE ST_XAU_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_XAU_HTFBarsToScan = 500;
|
||||
input double ST_XAU_LineTouchTolerance = 220.0;
|
||||
input double ST_XAU_BreakBuffer = 110.0;
|
||||
input ulong ST_XAU_MagicNumber = 26042503;
|
||||
input bool ST_XAU_DrawTrendline = true;
|
||||
|
||||
input group "=== SimpleTrendline GER40 ==="
|
||||
input string ST_GER_Symbol = "GER40";
|
||||
input ENUM_TIMEFRAMES ST_GER_SignalTF = PERIOD_M15;
|
||||
input ENUM_TIMEFRAMES ST_GER_HigherTF = PERIOD_M15;
|
||||
input int ST_GER_MAPeriod = 65;
|
||||
input ENUM_MA_METHOD ST_GER_MAMethod = MODE_LWMA;
|
||||
input ENUM_APPLIED_PRICE ST_GER_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_GER_HTFBarsToScan = 1200;
|
||||
input double ST_GER_LineTouchTolerance = 100.0;
|
||||
input double ST_GER_BreakBuffer = 80.0;
|
||||
input ulong ST_GER_MagicNumber = 26042502;
|
||||
input bool ST_GER_DrawTrendline = true;
|
||||
|
||||
input group "=== RSI Secret Sauce XAUUSD ==="
|
||||
input string RSS_Symbol = "XAUUSD";
|
||||
input int RSS_MagicNumber = 789012;
|
||||
input int RSS_Slippage = 10;
|
||||
input ENUM_TIMEFRAMES RSS_Timeframe = PERIOD_M30;
|
||||
input int RSS_RSIPeriod = 16;
|
||||
input double RSS_RSIOverbought = 72.5;
|
||||
input double RSS_RSIOversold = 32.5;
|
||||
input int RSS_RSILookback = 60;
|
||||
input int RSS_PeakBars = 2;
|
||||
input double RSS_StopLossATR = 2.75;
|
||||
input double RSS_TakeProfitATR = 5.0;
|
||||
input int RSS_ATRPeriod = 14;
|
||||
input bool RSS_UseSwingStopLoss = false;
|
||||
input int RSS_SwingLookback = 30;
|
||||
input int RSS_MaxPositions = 1;
|
||||
input int RSS_MinBarsBetweenTrades = 7;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Balance scaling: LOT_* = nominal size at ORCH_ReferenceBalance |
|
||||
//+------------------------------------------------------------------+
|
||||
double United_BalanceScaleFactor()
|
||||
{
|
||||
if(!ORCH_ScaleLotsByBalance || ORCH_ReferenceBalance <= 0.0)
|
||||
return 1.0;
|
||||
const double money = ORCH_UseEquityInsteadOfBalance
|
||||
? AccountInfoDouble(ACCOUNT_EQUITY)
|
||||
: AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
double raw = money / ORCH_ReferenceBalance;
|
||||
if(raw < ORCH_MinBalanceScale)
|
||||
raw = ORCH_MinBalanceScale;
|
||||
if(raw > ORCH_MaxBalanceScale)
|
||||
raw = ORCH_MaxBalanceScale;
|
||||
return raw;
|
||||
}
|
||||
|
||||
double United_ScaledLot(const double baseLot)
|
||||
{
|
||||
const double lot = baseLot * United_BalanceScaleFactor();
|
||||
return (lot > 0.0 ? lot : 0.0);
|
||||
}
|
||||
|
||||
void United_RefreshScaledLots()
|
||||
{
|
||||
g_DB_LotSize = United_ScaledLot(LOT_DB_DarvasBox);
|
||||
g_ES_LotSize = United_ScaledLot(LOT_ES_EMASlopeDistance);
|
||||
g_RC_LotSize = United_ScaledLot(LOT_RC_RSICrossOver);
|
||||
g_RM_LotSize = United_ScaledLot(LOT_RM_RSIMidPointHijack);
|
||||
g_Pos_RS_APPL = United_ScaledLot(LOT_RS_APPL);
|
||||
g_Pos_RS_BTCUSD = United_ScaledLot(LOT_RS_BTCUSD);
|
||||
g_Pos_RS_NVDA = United_ScaledLot(LOT_RS_NVDA);
|
||||
g_Pos_RS_TSLA = United_ScaledLot(LOT_RS_TSLA);
|
||||
g_Pos_RS_XAUUSD = United_ScaledLot(LOT_RS_XAUUSD);
|
||||
g_Pos_RRA_EURUSD = United_ScaledLot(LOT_RRA_EURUSD);
|
||||
g_Pos_RRA_AUDUSD = United_ScaledLot(LOT_RRA_AUDUSD);
|
||||
g_Pos_SE = United_ScaledLot(LOT_SE_SuperEMA);
|
||||
g_Pos_RCO = United_ScaledLot(LOT_RCO_RSIConsolidation);
|
||||
g_Pos_ST_BTCUSD = United_ScaledLot(LOT_ST_BTCUSD);
|
||||
g_Pos_ST_XAUUSD = United_ScaledLot(LOT_ST_XAUUSD);
|
||||
g_Pos_ST_GER40 = United_ScaledLot(LOT_ST_GER40);
|
||||
g_RSS_LotSize = United_ScaledLot(LOT_RSS_SecretSauce);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -545,6 +596,7 @@ struct EMASlopeData {
|
||||
bool crossover_detected;
|
||||
datetime trade_open_time;
|
||||
datetime last_bar_time;
|
||||
datetime es_last_sl_adjust_success_time;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -606,6 +658,10 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
SuperEMAData seData;
|
||||
RSIConsolidationData rcoData;
|
||||
SimpleTrendlineData stBTCData;
|
||||
SimpleTrendlineData stXAUData;
|
||||
SimpleTrendlineData stGERData;
|
||||
RSISecretSauceOrcData rssData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI Reversal Asian |
|
||||
@@ -619,22 +675,8 @@ RSIReversalAsianData rraAUDUSDData;
|
||||
int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
UpdateAutoLotScaling();
|
||||
|
||||
// Initialize global lot size variables
|
||||
g_ES_LotSize = GetAutoScaledLot(LOT_ES_EMASlopeDistance);
|
||||
g_RC_LotSize = GetAutoScaledLot(LOT_RC_RSICrossOver);
|
||||
g_RM_LotSize = GetAutoScaledLot(LOT_RM_RSIMidPointHijack);
|
||||
|
||||
if(PrintMinimumBalanceRecommendation)
|
||||
{
|
||||
const double minBalance = ComputeRecommendedMinBalance();
|
||||
Print("Lot scaling initialized | scale=", DoubleToString(g_LotScaleFinal, 3),
|
||||
" (growth=", DoubleToString(g_LotScaleGrowth, 3),
|
||||
", margin=", DoubleToString(g_LotScaleMargin, 3), ")",
|
||||
" | recommended minimum balance=", DoubleToString(minBalance, 2));
|
||||
}
|
||||
United_RefreshScaledLots();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
if(EnableDarvasBox)
|
||||
@@ -669,6 +711,10 @@ int OnInit()
|
||||
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);
|
||||
|
||||
if(EnableRSISecretSauce)
|
||||
if(!InitRSISecretSauce(rssData, RSS_Symbol))
|
||||
Print("Warning: RSI Secret Sauce failed to initialize for symbol '", RSS_Symbol, "'");
|
||||
|
||||
if(EnableSuperEMA)
|
||||
if(!InitSuperEMA(seData, SE_Symbol, SE_Timeframe, SE_SlippagePoints, SE_MagicNumber,
|
||||
SE_EmaFast, SE_EmaMid, SE_EmaSlow, SE_EmaTrendBars,
|
||||
@@ -704,6 +750,24 @@ int OnInit()
|
||||
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, "'");
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
if(!InitSimpleTrendline(stBTCData, ST_BTC_Symbol, ST_BTC_SignalTF, ST_BTC_HigherTF, ST_BTC_MAPeriod,
|
||||
ST_BTC_MAMethod, ST_BTC_AppliedPrice, ST_BTC_HTFBarsToScan,
|
||||
ST_BTC_LineTouchTolerance, ST_BTC_BreakBuffer, ST_BTC_MagicNumber, ST_BTC_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineBTCUSD failed to initialize for symbol '", ST_BTC_Symbol, "'");
|
||||
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
if(!InitSimpleTrendline(stXAUData, ST_XAU_Symbol, ST_XAU_SignalTF, ST_XAU_HigherTF, ST_XAU_MAPeriod,
|
||||
ST_XAU_MAMethod, ST_XAU_AppliedPrice, ST_XAU_HTFBarsToScan,
|
||||
ST_XAU_LineTouchTolerance, ST_XAU_BreakBuffer, ST_XAU_MagicNumber, ST_XAU_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineXAUUSD failed to initialize for symbol '", ST_XAU_Symbol, "'");
|
||||
|
||||
if(EnableSimpleTrendlineGER40)
|
||||
if(!InitSimpleTrendline(stGERData, ST_GER_Symbol, ST_GER_SignalTF, ST_GER_HigherTF, ST_GER_MAPeriod,
|
||||
ST_GER_MAMethod, ST_GER_AppliedPrice, ST_GER_HTFBarsToScan,
|
||||
ST_GER_LineTouchTolerance, ST_GER_BreakBuffer, ST_GER_MagicNumber, ST_GER_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineGER40 failed to initialize for symbol '", ST_GER_Symbol, "'");
|
||||
|
||||
Print("United EA initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
@@ -715,10 +779,14 @@ int OnInit()
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""),
|
||||
(EnableRSISecretSauce ? "RSISecretSauce " : ""),
|
||||
(EnableSuperEMA ? "SuperEMA " : ""),
|
||||
(EnableRSIConsolidation ? "RSIConsolidation " : ""),
|
||||
(EnableRSIReversalAsianEURUSD ? "RSIReversalAsianEURUSD " : ""),
|
||||
(EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : ""));
|
||||
(EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : ""),
|
||||
(EnableSimpleTrendlineBTCUSD ? "SimpleTrendlineBTCUSD " : ""),
|
||||
(EnableSimpleTrendlineXAUUSD ? "SimpleTrendlineXAUUSD " : ""),
|
||||
(EnableSimpleTrendlineGER40 ? "SimpleTrendlineGER40 " : ""));
|
||||
|
||||
return initResult;
|
||||
}
|
||||
@@ -755,6 +823,9 @@ void OnDeinit(const int reason)
|
||||
if(EnableRSIScalpingXAUUSD)
|
||||
DeinitRSIScalping(rsXAUUSDData);
|
||||
|
||||
if(EnableRSISecretSauce)
|
||||
DeinitRSISecretSauce(rssData);
|
||||
|
||||
if(EnableSuperEMA)
|
||||
DeinitSuperEMA(seData);
|
||||
|
||||
@@ -766,6 +837,13 @@ void OnDeinit(const int reason)
|
||||
|
||||
if(EnableRSIReversalAsianAUDUSD)
|
||||
DeinitRSIReversalAsian(rraAUDUSDData);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
DeinitSimpleTrendline(stBTCData);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
DeinitSimpleTrendline(stXAUData);
|
||||
if(EnableSimpleTrendlineGER40)
|
||||
DeinitSimpleTrendline(stGERData);
|
||||
|
||||
Print("United EA deinitialized. Reason: ", reason);
|
||||
}
|
||||
@@ -775,35 +853,7 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
UpdateAutoLotScaling();
|
||||
g_ES_LotSize = GetAutoScaledLot(LOT_ES_EMASlopeDistance);
|
||||
g_RC_LotSize = GetAutoScaledLot(LOT_RC_RSICrossOver);
|
||||
g_RM_LotSize = GetAutoScaledLot(LOT_RM_RSIMidPointHijack);
|
||||
|
||||
if(ScaleLogIntervalSeconds > 0)
|
||||
{
|
||||
const datetime now = TimeCurrent();
|
||||
if(g_LastScaleLogTime == 0 || (now - g_LastScaleLogTime) >= ScaleLogIntervalSeconds)
|
||||
{
|
||||
g_LastScaleLogTime = now;
|
||||
Print("Lot scale update | final=", DoubleToString(g_LotScaleFinal, 3),
|
||||
" growth=", DoubleToString(g_LotScaleGrowth, 3),
|
||||
" margin=", DoubleToString(g_LotScaleMargin, 3),
|
||||
" ml=", DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 1),
|
||||
" eq=", DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2),
|
||||
" bal=", DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2));
|
||||
}
|
||||
}
|
||||
|
||||
if(ShowScaleStatusOnChart)
|
||||
{
|
||||
const double minBalance = ComputeRecommendedMinBalance();
|
||||
Comment("Scale=", DoubleToString(g_LotScaleFinal, 3),
|
||||
" (growth=", DoubleToString(g_LotScaleGrowth, 3),
|
||||
", margin=", DoubleToString(g_LotScaleMargin, 3), ")",
|
||||
" | Rec.Min.Balance=", DoubleToString(minBalance, 2),
|
||||
" | MarginLevel=", DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 1), "%");
|
||||
}
|
||||
United_RefreshScaledLots();
|
||||
|
||||
if(EnableDarvasBox)
|
||||
ProcessDarvasBox(DB_Symbol);
|
||||
@@ -820,49 +870,64 @@ void OnTick()
|
||||
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, GetAutoScaledLot(LOT_RS_APPL), RS_APPL_MagicNumber,
|
||||
RS_APPL_BarsToWait, g_Pos_RS_APPL, RS_APPL_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_APPL_UseTrailingStop, RS_APPL_TrailDistancePoints, RS_APPL_TrailActivationPoints);
|
||||
|
||||
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, GetAutoScaledLot(LOT_RS_BTCUSD), RS_BTCUSD_MagicNumber,
|
||||
RS_BTCUSD_BarsToWait, g_Pos_RS_BTCUSD, RS_BTCUSD_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_BTCUSD_UseTrailingStop, RS_BTCUSD_TrailDistancePoints, RS_BTCUSD_TrailActivationPoints);
|
||||
|
||||
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, GetAutoScaledLot(LOT_RS_NVDA), RS_NVDA_MagicNumber,
|
||||
RS_NVDA_BarsToWait, g_Pos_RS_NVDA, RS_NVDA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_NVDA_UseTrailingStop, RS_NVDA_TrailDistancePoints, RS_NVDA_TrailActivationPoints);
|
||||
|
||||
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, GetAutoScaledLot(LOT_RS_TSLA), RS_TSLA_MagicNumber,
|
||||
RS_TSLA_BarsToWait, g_Pos_RS_TSLA, RS_TSLA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_TSLA_UseTrailingStop, RS_TSLA_TrailDistancePoints, RS_TSLA_TrailActivationPoints);
|
||||
|
||||
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, GetAutoScaledLot(LOT_RS_XAUUSD), RS_XAUUSD_MagicNumber,
|
||||
RS_XAUUSD_BarsToWait, g_Pos_RS_XAUUSD, RS_XAUUSD_MagicNumber,
|
||||
RS_UseReversalEscape, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_XAUUSD_UseTrailingStop, RS_XAUUSD_TrailDistancePoints, RS_XAUUSD_TrailActivationPoints);
|
||||
|
||||
if(EnableRSISecretSauce)
|
||||
ProcessRSISecretSauce(rssData, g_RSS_LotSize);
|
||||
|
||||
if(EnableRSIReversalAsianEURUSD)
|
||||
ProcessRSIReversalAsian(rraEURUSDData, GetAutoScaledLot(LOT_RRA_EURUSD));
|
||||
ProcessRSIReversalAsian(rraEURUSDData, g_Pos_RRA_EURUSD);
|
||||
|
||||
if(EnableRSIReversalAsianAUDUSD)
|
||||
ProcessRSIReversalAsian(rraAUDUSDData, GetAutoScaledLot(LOT_RRA_AUDUSD));
|
||||
ProcessRSIReversalAsian(rraAUDUSDData, g_Pos_RRA_AUDUSD);
|
||||
|
||||
if(EnableSuperEMA)
|
||||
ProcessSuperEMA(seData, GetAutoScaledLot(LOT_SE_SuperEMA));
|
||||
ProcessSuperEMA(seData, g_Pos_SE);
|
||||
|
||||
if(EnableRSIConsolidation)
|
||||
ProcessRSIConsolidation(rcoData, GetAutoScaledLot(LOT_RCO_RSIConsolidation));
|
||||
ProcessRSIConsolidation(rcoData, g_Pos_RCO);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
ProcessSimpleTrendline(stBTCData, g_Pos_ST_BTCUSD);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
ProcessSimpleTrendline(stXAUData, g_Pos_ST_XAUUSD);
|
||||
if(EnableSimpleTrendlineGER40)
|
||||
ProcessSimpleTrendline(stGERData, g_Pos_ST_GER40);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@@ -13,7 +13,6 @@
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#include "PerformanceEvaluator.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy Enable/Disable Switches |
|
||||
@@ -62,7 +61,11 @@ 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_TrailingStop = 370.0;
|
||||
input bool ES_UseTrailingStop = true;
|
||||
input double ES_TrailingActivationPips = 0.0;
|
||||
input bool ES_UseStaleStopLossExit = false;
|
||||
input int ES_StaleStopLossSeconds = 33800;
|
||||
input double ES_LotGröße = 0.03;
|
||||
input int ES_MagicNumber = 12350;
|
||||
input bool ES_UseSpreadAdjustment = true;
|
||||
@@ -71,6 +74,11 @@ input bool ES_UseBarData = true;
|
||||
input int ES_MaxTradesPerCrossover = 9;
|
||||
input int ES_ProfitCheckBars = 18;
|
||||
input bool ES_CloseUnprofitableTrades = true;
|
||||
input bool ES_UseWeeklyADXFilter = true;
|
||||
input int ES_WeeklyADXPeriod = 15;
|
||||
input double ES_WeeklyADXMin = 40.0;
|
||||
input int ES_WeeklyADXBarShift = 2;
|
||||
input bool ES_WeeklyADXUseDirection = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 3: RSICrossOverReversalXAUUSD |
|
||||
@@ -252,6 +260,44 @@ input double RS_XAUUSD_LotSize = 0.1;
|
||||
input int RS_XAUUSD_MagicNumber = 129102315;
|
||||
input int RS_XAUUSD_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping Reversal escape (XAUUSD) ==="
|
||||
input bool RS_UseReversalEscape = false;
|
||||
input int RS_ReversalATRPeriod = 14;
|
||||
input double RS_ReversalAdverseAtrMult = 5.25;
|
||||
input int RS_ReversalSignsRequired = 2;
|
||||
input double RS_ReversalRsiVelocity = 16.0;
|
||||
input double RS_ReversalBodyAtrMult = 5.1;
|
||||
|
||||
input group "=== RSI Scalping APPL — Trailing ==="
|
||||
input bool RS_APPL_UseTrailingStop = true;
|
||||
input double RS_APPL_TrailDistancePoints = 120.0;
|
||||
input double RS_APPL_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping BTCUSD — Trailing ==="
|
||||
input bool RS_BTCUSD_UseTrailingStop = true;
|
||||
input double RS_BTCUSD_TrailDistancePoints = 120.0;
|
||||
input double RS_BTCUSD_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping MSFT — Trailing ==="
|
||||
input bool RS_MSFT_UseTrailingStop = true;
|
||||
input double RS_MSFT_TrailDistancePoints = 375.0;
|
||||
input double RS_MSFT_TrailActivationPoints = 75.0;
|
||||
|
||||
input group "=== RSI Scalping NVDA — Trailing ==="
|
||||
input bool RS_NVDA_UseTrailingStop = true;
|
||||
input double RS_NVDA_TrailDistancePoints = 375.0;
|
||||
input double RS_NVDA_TrailActivationPoints = 75.0;
|
||||
|
||||
input group "=== RSI Scalping TSLA — Trailing ==="
|
||||
input bool RS_TSLA_UseTrailingStop = true;
|
||||
input double RS_TSLA_TrailDistancePoints = 900.0;
|
||||
input double RS_TSLA_TrailActivationPoints = 950.0;
|
||||
|
||||
input group "=== RSI Scalping XAUUSD — Trailing ==="
|
||||
input bool RS_XAUUSD_UseTrailingStop = true;
|
||||
input double RS_XAUUSD_TrailDistancePoints = 71.0;
|
||||
input double RS_XAUUSD_TrailActivationPoints = 41.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -289,6 +335,7 @@ struct EMASlopeData {
|
||||
bool crossover_detected;
|
||||
datetime trade_open_time;
|
||||
datetime last_bar_time;
|
||||
datetime es_last_sl_adjust_success_time;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -371,19 +418,18 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables for Dynamic Lot Sizes |
|
||||
//| Global Variables for lot sizes (from inputs below) |
|
||||
//+------------------------------------------------------------------+
|
||||
// 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_MSFT_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
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)
|
||||
double g_DB_LotSize = 0.01;
|
||||
double g_ES_LotSize;
|
||||
double g_RC_LotSize;
|
||||
double g_RM_LotSize;
|
||||
double g_RS_APPL_LotSize;
|
||||
double g_RS_BTCUSD_LotSize;
|
||||
double g_RS_MSFT_LotSize;
|
||||
double g_RS_NVDA_LotSize;
|
||||
double g_RS_TSLA_LotSize;
|
||||
double g_RS_XAUUSD_LotSize;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
@@ -391,148 +437,52 @@ double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01)
|
||||
int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize Performance Evaluator
|
||||
InitPerformanceTracking();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
|
||||
g_ES_LotSize = ES_LotGröße;
|
||||
g_RC_LotSize = RC_lotSize;
|
||||
g_RM_LotSize = RM_InpLotSize;
|
||||
g_RS_APPL_LotSize = RS_APPL_LotSize;
|
||||
g_RS_BTCUSD_LotSize = RS_BTCUSD_LotSize;
|
||||
g_RS_MSFT_LotSize = RS_MSFT_LotSize;
|
||||
g_RS_NVDA_LotSize = RS_NVDA_LotSize;
|
||||
g_RS_TSLA_LotSize = RS_TSLA_LotSize;
|
||||
g_RS_XAUUSD_LotSize = RS_XAUUSD_LotSize;
|
||||
|
||||
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(EnableRSIScalpingMSFT)
|
||||
{
|
||||
InitRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price, RS_MSFT_MagicNumber, RS_MSFT_Slippage);
|
||||
RegisterStrategy("RSIScalpingMSFT", RS_MSFT_MagicNumber, RS_MSFT_LotSize, RS_MSFT_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_MSFT_Symbol);
|
||||
g_RS_MSFT_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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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: ",
|
||||
|
||||
Print("United EA (self-evaluate build) initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
(EnableEMASlopeDistance ? "EMASlope " : ""),
|
||||
(EnableRSICrossOverReversal ? "RSICrossOver " : ""),
|
||||
@@ -543,10 +493,7 @@ int OnInit()
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""));
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print(GetPerformanceSummary());
|
||||
|
||||
|
||||
return initResult;
|
||||
}
|
||||
|
||||
@@ -593,41 +540,6 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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);
|
||||
|
||||
@@ -643,32 +555,50 @@ void OnTick()
|
||||
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);
|
||||
RS_APPL_BarsToWait, g_RS_APPL_LotSize, RS_APPL_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_APPL_UseTrailingStop, RS_APPL_TrailDistancePoints, RS_APPL_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_BTCUSD_BarsToWait, g_RS_BTCUSD_LotSize, RS_BTCUSD_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_BTCUSD_UseTrailingStop, RS_BTCUSD_TrailDistancePoints, RS_BTCUSD_TrailActivationPoints);
|
||||
|
||||
if(EnableRSIScalpingMSFT)
|
||||
ProcessRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price,
|
||||
RS_MSFT_RSI_Overbought, RS_MSFT_RSI_Oversold, RS_MSFT_RSI_Target_Buy, RS_MSFT_RSI_Target_Sell,
|
||||
RS_MSFT_BarsToWait, g_RS_MSFT_LotSize, RS_MSFT_MagicNumber);
|
||||
RS_MSFT_BarsToWait, g_RS_MSFT_LotSize, RS_MSFT_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_MSFT_UseTrailingStop, RS_MSFT_TrailDistancePoints, RS_MSFT_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_NVDA_BarsToWait, g_RS_NVDA_LotSize, RS_NVDA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_NVDA_UseTrailingStop, RS_NVDA_TrailDistancePoints, RS_NVDA_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_TSLA_BarsToWait, g_RS_TSLA_LotSize, RS_TSLA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_TSLA_UseTrailingStop, RS_TSLA_TrailDistancePoints, RS_TSLA_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_XAUUSD_BarsToWait, g_RS_XAUUSD_LotSize, RS_XAUUSD_MagicNumber,
|
||||
RS_UseReversalEscape, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_XAUUSD_UseTrailingStop, RS_XAUUSD_TrailDistancePoints, RS_XAUUSD_TrailActivationPoints);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -12,29 +12,18 @@
|
||||
//+------------------------------------------------------------------+
|
||||
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)
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
// 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;
|
||||
ulong ticket = PositionGetTicket(i);
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -76,14 +65,13 @@ 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;
|
||||
}
|
||||
}
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
return ticket;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -144,16 +132,42 @@ int CountPositionsByMagic(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)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if(ticket == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(ticket))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == symbol &&
|
||||
(ulong)PositionGetInteger(POSITION_MAGIC) == magic_number)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Align volume to SYMBOL_VOLUME_STEP / min / max (avoids Invalid volume) |
|
||||
//+------------------------------------------------------------------+
|
||||
double United_NormalizeVolume(const string symbol, double volume)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||||
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||||
if(lotStep <= 0.0)
|
||||
lotStep = 0.01;
|
||||
|
||||
double v = MathFloor(volume / lotStep) * lotStep;
|
||||
|
||||
if(v < minLot)
|
||||
v = minLot;
|
||||
if(v > maxLot)
|
||||
v = maxLot;
|
||||
|
||||
int digits = (int)MathCeil(-MathLog10(lotStep));
|
||||
if(digits < 0)
|
||||
digits = 0;
|
||||
if(digits > 8)
|
||||
digits = 8;
|
||||
|
||||
return NormalizeDouble(v, digits);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -1,6 +1,18 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DarvasBoxStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
// MQL5: no #if — use #ifdef only (no defined() / || in one #if)
|
||||
#ifdef UNITED_V2_DYNAMIC_LOTS
|
||||
extern double g_DB_LotSize;
|
||||
#define DARVAS_TRADE_LOT (g_DB_LotSize)
|
||||
#else
|
||||
#ifdef CLUSTER0_ORCHESTRATOR
|
||||
extern double g_DB_LotSize;
|
||||
#define DARVAS_TRADE_LOT (g_DB_LotSize)
|
||||
#else
|
||||
#define DARVAS_TRADE_LOT 0.01
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool InitDarvasBox(string symbol)
|
||||
{
|
||||
@@ -24,7 +36,9 @@ bool InitDarvasBox(string symbol)
|
||||
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);
|
||||
// Same TF as box highs/lows (H1 loop in CalculateDarvasBox). PERIOD_CURRENT breaks when United EA
|
||||
// runs on a chart timeframe other than H1 (volume/breakout no longer match the box).
|
||||
dbData.volumeHandle = iVolumes(symbol, PERIOD_H1, VOLUME_TICK);
|
||||
|
||||
if(dbData.maHandle == INVALID_HANDLE || dbData.volumeHandle == INVALID_HANDLE)
|
||||
{
|
||||
@@ -133,6 +147,9 @@ bool ValidateStopLevels(double price, double &sl, double &tp, ENUM_ORDER_TYPE or
|
||||
|
||||
bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
|
||||
{
|
||||
if(!DB_UseTrendFilter)
|
||||
return true;
|
||||
|
||||
double ma[];
|
||||
ArraySetAsSeries(ma, true);
|
||||
|
||||
@@ -150,6 +167,9 @@ bool IsTrendFavorable(ENUM_ORDER_TYPE orderType)
|
||||
|
||||
bool CheckVolumeConditions()
|
||||
{
|
||||
if(!DB_UseVolumeSpikeFilter)
|
||||
return true;
|
||||
|
||||
double volumes[];
|
||||
ArraySetAsSeries(volumes, true);
|
||||
|
||||
@@ -162,8 +182,10 @@ bool CheckVolumeConditions()
|
||||
volumeMA /= DB_VolumeMA_Period;
|
||||
|
||||
double currentVolume = volumes[0];
|
||||
if(volumeMA <= 0.0)
|
||||
return (currentVolume > 0.0);
|
||||
|
||||
double volumeRatio = currentVolume / volumeMA;
|
||||
|
||||
return (volumeRatio > DB_VolumeThresholdMultiplier);
|
||||
}
|
||||
|
||||
@@ -191,13 +213,19 @@ bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
const double lot = United_NormalizeVolume(dbData.symbol, DARVAS_TRADE_LOT);
|
||||
if(lot <= 0.0)
|
||||
{
|
||||
Print("DarvasBox: Order rejected - invalid lot after normalize (raw=", DARVAS_TRADE_LOT, ")");
|
||||
return 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(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
result = dbData.trade.Buy(lot, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
else
|
||||
result = dbData.trade.Sell(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
result = dbData.trade.Sell(lot, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
|
||||
// Always log errors, success only if logging enabled
|
||||
if(result)
|
||||
@@ -242,7 +270,7 @@ void ProcessDarvasBox(string symbol)
|
||||
if(dbData.boxFormed)
|
||||
{
|
||||
double currentPrice = SymbolInfoDouble(dbData.symbol, SYMBOL_ASK);
|
||||
long currentVolume_long = iVolume(dbData.symbol, PERIOD_CURRENT, 0);
|
||||
long currentVolume_long = iVolume(dbData.symbol, PERIOD_H1, 0);
|
||||
double currentVolume = (double)currentVolume_long;
|
||||
|
||||
if(DB_EnableLogging)
|
||||
@@ -14,6 +14,7 @@ bool InitEMASlopeDistance(string symbol)
|
||||
esData.crossover_detected = false;
|
||||
esData.trade_open_time = 0;
|
||||
esData.last_bar_time = 0;
|
||||
esData.es_last_sl_adjust_success_time = 0;
|
||||
|
||||
// Check if symbol exists
|
||||
if(!SymbolSelect(symbol, true))
|
||||
@@ -48,6 +49,64 @@ void DeinitEMASlopeDistance()
|
||||
IndicatorRelease(esData.ema_handle);
|
||||
}
|
||||
|
||||
bool ES_IsWeeklyADXTrendFavorable(const ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
if(!ES_UseWeeklyADXFilter)
|
||||
return true;
|
||||
|
||||
int adxShift = ES_WeeklyADXBarShift;
|
||||
if(adxShift < 0)
|
||||
adxShift = 0;
|
||||
|
||||
int adx_handle = iADX(esData.symbol, PERIOD_W1, ES_WeeklyADXPeriod);
|
||||
if(adx_handle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
double adx_buf[], plus_di_buf[], minus_di_buf[];
|
||||
ArraySetAsSeries(adx_buf, true);
|
||||
ArraySetAsSeries(plus_di_buf, true);
|
||||
ArraySetAsSeries(minus_di_buf, true);
|
||||
|
||||
bool ok_adx = (CopyBuffer(adx_handle, 0, adxShift, 1, adx_buf) > 0);
|
||||
bool ok_plus = (CopyBuffer(adx_handle, 1, adxShift, 1, plus_di_buf) > 0);
|
||||
bool ok_minus = (CopyBuffer(adx_handle, 2, adxShift, 1, minus_di_buf) > 0);
|
||||
IndicatorRelease(adx_handle);
|
||||
|
||||
if(!ok_adx || !ok_plus || !ok_minus)
|
||||
return false;
|
||||
|
||||
double adx_value = adx_buf[0];
|
||||
double plus_di = plus_di_buf[0];
|
||||
double minus_di = minus_di_buf[0];
|
||||
|
||||
bool strength_ok = (adx_value >= ES_WeeklyADXMin);
|
||||
bool direction_ok = true;
|
||||
if(ES_WeeklyADXUseDirection)
|
||||
{
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
direction_ok = (plus_di > minus_di);
|
||||
else
|
||||
direction_ok = (minus_di > plus_di);
|
||||
}
|
||||
return strength_ok && direction_ok;
|
||||
}
|
||||
|
||||
bool ES_TrailingActivationReached(const double position_profit, const ENUM_POSITION_TYPE position_type,
|
||||
const double pips_multiplier)
|
||||
{
|
||||
if(ES_TrailingActivationPips <= 0.0)
|
||||
return (position_profit > 0.0);
|
||||
|
||||
const double open_px = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(esData.symbol, SYMBOL_BID);
|
||||
return ((bid - open_px) / SymbolInfoDouble(esData.symbol, SYMBOL_POINT) / pips_multiplier >= ES_TrailingActivationPips);
|
||||
}
|
||||
const double ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK);
|
||||
return ((open_px - ask) / SymbolInfoDouble(esData.symbol, SYMBOL_POINT) / pips_multiplier >= ES_TrailingActivationPips);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMA Berechnung (EMA Calculation) |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -172,6 +231,11 @@ void PrüfeTrigger()
|
||||
|
||||
if(bullish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
if(!ES_IsWeeklyADXTrendFavorable(ORDER_TYPE_BUY))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert BUY-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_BUY))
|
||||
{
|
||||
@@ -180,6 +244,11 @@ void PrüfeTrigger()
|
||||
}
|
||||
else if(bearish_signal && !PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
{
|
||||
if(!ES_IsWeeklyADXTrendFavorable(ORDER_TYPE_SELL))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert SELL-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", esData.trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_SELL))
|
||||
{
|
||||
@@ -199,17 +268,23 @@ void PrüfeTrigger()
|
||||
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);
|
||||
|
||||
const double lot = United_NormalizeVolume(esData.symbol, g_ES_LotSize);
|
||||
Print("TRACE: Lot (raw): ", g_ES_LotSize, " normalized: ", lot);
|
||||
if(lot <= 0.0)
|
||||
{
|
||||
Print("TRACE: Abbruch — Lot nach Normalisierung ungültig");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
{
|
||||
success = esData.trade.Buy(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
success = esData.trade.Buy(lot, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
else
|
||||
{
|
||||
success = esData.trade.Sell(g_ES_LotSize, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
success = esData.trade.Sell(lot, esData.symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
|
||||
if(success)
|
||||
@@ -219,6 +294,7 @@ bool PlatziereTrade(ENUM_ORDER_TYPE order_type)
|
||||
|
||||
//--- Trade-Öffnungszeit speichern (Save trade opening time)
|
||||
esData.trade_open_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
esData.es_last_sl_adjust_success_time = 0;
|
||||
Print("TRACE: Trade-Öffnungszeit: ", TimeToString(esData.trade_open_time));
|
||||
|
||||
//--- Überwachung zurücksetzen (Reset monitoring)
|
||||
@@ -244,41 +320,51 @@ void VerwalteTrades()
|
||||
{
|
||||
if(!PositionSelectByMagic(esData.symbol, (ulong)ES_MagicNumber))
|
||||
return;
|
||||
|
||||
|
||||
if(ES_UseStaleStopLossExit && ES_StaleStopLossSeconds > 0)
|
||||
{
|
||||
const datetime stale_ref = (esData.es_last_sl_adjust_success_time > 0)
|
||||
? esData.es_last_sl_adjust_success_time
|
||||
: (datetime)PositionGetInteger(POSITION_TIME);
|
||||
if(TimeCurrent() - stale_ref >= ES_StaleStopLossSeconds)
|
||||
{
|
||||
SchließePosition("Stale stop loss - keine SL-Anpassung");
|
||||
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
|
||||
const double trail_dist = ES_TrailingStop * point * pips_multiplier;
|
||||
const long stops_level = SymbolInfoInteger(esData.symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
if(ES_UseTrailingStop && ES_TrailingStop > 0.0 && ES_TrailingActivationReached(position_profit, position_type, pips_multiplier))
|
||||
{
|
||||
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)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(esData.symbol, SYMBOL_BID);
|
||||
double new_stop_loss = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_stop_loss < min_dist)
|
||||
new_stop_loss = NormalizeDouble(bid - min_dist, digits);
|
||||
const double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
if(new_stop_loss < bid && new_stop_loss > 0.0 && 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)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(esData.symbol, SYMBOL_ASK);
|
||||
double new_stop_loss = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_stop_loss - ask < min_dist)
|
||||
new_stop_loss = NormalizeDouble(ask + min_dist, digits);
|
||||
const double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
if(new_stop_loss > ask && new_stop_loss > 0.0 &&
|
||||
(new_stop_loss < current_stop_loss || current_stop_loss == 0.0))
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,6 +452,7 @@ void ÄndereStopLoss(double new_stop_loss)
|
||||
|
||||
if(success)
|
||||
{
|
||||
esData.es_last_sl_adjust_success_time = TimeCurrent();
|
||||
Print("TRACE: Stop Loss erfolgreich geändert auf: ", new_stop_loss);
|
||||
}
|
||||
else
|
||||
@@ -386,6 +473,7 @@ void SchließePosition(string reason = "Unbekannt")
|
||||
|
||||
if(success)
|
||||
{
|
||||
esData.es_last_sl_adjust_success_time = 0;
|
||||
Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason);
|
||||
}
|
||||
else
|
||||
@@ -400,40 +488,34 @@ void SchließePosition(string reason = "Unbekannt")
|
||||
//+------------------------------------------------------------------+
|
||||
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.symbol = symbol;
|
||||
|
||||
const datetime current_bar_time = iTime(esData.symbol, ES_Timeframe, 0);
|
||||
const bool new_bar = (current_bar_time != esData.last_bar_time);
|
||||
const bool has_position = PositionExistsByMagic(esData.symbol, (ulong)ES_MagicNumber);
|
||||
|
||||
if(ES_UseBarData && !new_bar && !has_position)
|
||||
return;
|
||||
|
||||
if(new_bar)
|
||||
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)
|
||||
|
||||
const bool run_signals = (!ES_UseBarData || new_bar);
|
||||
|
||||
if(run_signals && 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) ===");
|
||||
@@ -443,7 +525,7 @@ void ProcessEMASlopeDistance(string symbol)
|
||||
{
|
||||
Print("=== DEBUG INFO (Tick) ===");
|
||||
}
|
||||
|
||||
|
||||
Print("Aktueller Close: ", aktueller_close);
|
||||
Print("EMA: ", ema_aktuell);
|
||||
Print("Preis-Abstand: ", preis_abstand, " Pips");
|
||||
@@ -455,41 +537,39 @@ void ProcessEMASlopeDistance(string symbol)
|
||||
Print("Trades im aktuellen Crossover: ", esData.trades_in_current_crossover, "/", ES_MaxTradesPerCrossover);
|
||||
Print("==================");
|
||||
}
|
||||
|
||||
//--- Überwachung prüfen (Check monitoring)
|
||||
if(esData.überwachung_aktiv)
|
||||
|
||||
if(run_signals)
|
||||
{
|
||||
if(ES_UseBarData)
|
||||
if(esData.überwachung_aktiv)
|
||||
{
|
||||
// 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)
|
||||
if(ES_UseBarData)
|
||||
{
|
||||
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");
|
||||
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
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrüfeTrigger();
|
||||
}
|
||||
|
||||
//--- Trigger-Bedingungen prüfen (Check trigger conditions)
|
||||
PrüfeTrigger();
|
||||
|
||||
//--- Trade Management (Trade management)
|
||||
|
||||
VerwalteTrades();
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ bool InitRSICrossOverReversal(string symbol)
|
||||
}
|
||||
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
rcData.trade.SetDeviationInPoints(RC_slippage);
|
||||
rcData.isInitialized = true;
|
||||
Print("RSICrossOverReversal: Successfully initialized for symbol '", symbol, "'");
|
||||
return true;
|
||||
@@ -190,7 +191,13 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
|
||||
double emaSlope = (currentEMA - previousEMA) * 100;
|
||||
const double closeCurr = iClose(rcData.symbol, RC_TimeFrame1, 0);
|
||||
double priceToEmaDistance = (closeCurr - currentEMA) * 10;
|
||||
// Raw (close-EMA)*10 blows past threshold on XAUUSD (~2600) almost every bar — blocks all entries.
|
||||
// Compare distance in pips so RC_emaDistanceThreshold matches intent across symbols.
|
||||
const double point = SymbolInfoDouble(rcData.symbol, SYMBOL_POINT);
|
||||
const int symDig = (int)SymbolInfoInteger(rcData.symbol, SYMBOL_DIGITS);
|
||||
const double pipMult = (symDig == 3 || symDig == 5) ? 10.0 : 1.0;
|
||||
const double pipSize = (point > 0.0 ? point * pipMult : point);
|
||||
const double priceToEmaPips = (pipSize > 0.0 ? MathAbs(closeCurr - currentEMA) / pipSize : 0.0);
|
||||
|
||||
bool isBuyPosition = false;
|
||||
bool isSellPosition = false;
|
||||
@@ -209,7 +216,8 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
ApplyTrailingStop();
|
||||
|
||||
bool cooldownPassed = (currentTime - rcData.lastTradeTime) >= RC_cooldownSeconds;
|
||||
bool isTrendStrong = MathAbs(emaSlope) > RC_emaSlopeThreshold || MathAbs(priceToEmaDistance) > RC_emaDistanceThreshold;
|
||||
const bool isTrendStrong = RC_UseTrendStrengthFilter &&
|
||||
(MathAbs(emaSlope) > RC_emaSlopeThreshold || priceToEmaPips > RC_emaDistanceThreshold);
|
||||
|
||||
if(isBuyPosition && currentRSI > RC_exitBuyRSI)
|
||||
{
|
||||
@@ -233,10 +241,12 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(rcData.symbol, g_RC_LotSize);
|
||||
if(vol > 0.0)
|
||||
{
|
||||
rcData.lastTradeTime = currentTime;
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
if(rcData.trade.Sell(vol, rcData.symbol, 0.0, 0.0, 0.0, "Sell Order"))
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,10 +254,12 @@ void ProcessRSICrossOverReversal(string symbol)
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(rcData.symbol, g_RC_LotSize);
|
||||
if(vol > 0.0)
|
||||
{
|
||||
rcData.lastTradeTime = currentTime;
|
||||
rcData.trade.SetExpertMagicNumber(RC_MagicNumber);
|
||||
if(rcData.trade.Buy(vol, rcData.symbol, 0.0, 0.0, 0.0, "Buy Order"))
|
||||
rcData.lastTradeTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
//| RSIMidPointHijackStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
double RM_NormalizedLot(const string sym)
|
||||
{
|
||||
return United_NormalizeVolume(sym, g_RM_LotSize);
|
||||
}
|
||||
|
||||
bool IsNewBar(string symbol)
|
||||
{
|
||||
datetime time[];
|
||||
@@ -111,7 +116,9 @@ void CheckRSIFollowStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "RSI Follow");
|
||||
}
|
||||
rmData.rsiOverbought = false;
|
||||
}
|
||||
@@ -120,7 +127,9 @@ void CheckRSIFollowStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIFollow))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIFollow);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Follow");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "RSI Follow");
|
||||
}
|
||||
rmData.rsiOversold = false;
|
||||
}
|
||||
@@ -154,7 +163,9 @@ void CheckRSIReverseStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "RSI Reverse");
|
||||
}
|
||||
rmData.rsiReverseOverbought = false;
|
||||
}
|
||||
@@ -163,7 +174,9 @@ void CheckRSIReverseStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberRSIReverse))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberRSIReverse);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "RSI Reverse");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "RSI Reverse");
|
||||
}
|
||||
rmData.rsiReverseOversold = false;
|
||||
}
|
||||
@@ -223,7 +236,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
rmData.emaCrossBuySignal = false;
|
||||
}
|
||||
}
|
||||
@@ -252,7 +267,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(distanceConditionMet && !HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "EMA Cross Distance");
|
||||
rmData.emaCrossSellSignal = false;
|
||||
}
|
||||
}
|
||||
@@ -265,7 +282,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Buy(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Buy(vol, symbol, 0, 0, 0, "EMA Cross");
|
||||
}
|
||||
}
|
||||
else if(rmData.lastBarEMAPrev > rmData.lastBarClosePrev && rmData.lastBarEMA < rmData.lastBarClose)
|
||||
@@ -273,7 +292,9 @@ void CheckEMACrossStrategy(string symbol)
|
||||
if(!HasPosition(symbol, RM_InpMagicNumberEMACross))
|
||||
{
|
||||
rmData.trade.SetExpertMagicNumber(RM_InpMagicNumberEMACross);
|
||||
rmData.trade.Sell(g_RM_LotSize, symbol, 0, 0, 0, "EMA Cross");
|
||||
const double vol = RM_NormalizedLot(symbol);
|
||||
if(vol > 0.0)
|
||||
rmData.trade.Sell(vol, symbol, 0, 0, 0, "EMA Cross");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,19 +65,14 @@ bool IsAsianSession()
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsTradingAllowed(RSIReversalAsianData& data)
|
||||
{
|
||||
// Check if market is open
|
||||
long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode != SYMBOL_TRADE_MODE_FULL)
|
||||
{
|
||||
// Do not require SYMBOL_TRADE_MODE_FULL: many symbols allow one side only (long/short).
|
||||
const long tradeMode = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_MODE);
|
||||
if(tradeMode == SYMBOL_TRADE_MODE_DISABLED || tradeMode == SYMBOL_TRADE_MODE_CLOSEONLY)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if we have enough money
|
||||
|
||||
if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -290,7 +285,7 @@ bool InitRSIReversalAsian(RSIReversalAsianData& data, string symbol,
|
||||
// Set trade parameters
|
||||
data.trade.SetExpertMagicNumber(MagicNumber);
|
||||
data.trade.SetDeviationInPoints(Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetTypeFillingBySymbol(symbol);
|
||||
|
||||
// Initialize state
|
||||
data.isPositionOpen = false;
|
||||
@@ -444,16 +439,16 @@ void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize)
|
||||
if(data.UseTakeProfit && tp <= currentBid)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetDeviationInPoints(data.Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetTypeFillingBySymbol(data.symbol);
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(data.symbol, tradeLotSize);
|
||||
if(vol <= 0.0)
|
||||
return;
|
||||
|
||||
if(data.trade.Buy(vol, data.symbol, currentAsk, sl, tp, "RSI Oversold Crossover Buy"))
|
||||
{
|
||||
data.isPositionOpen = true;
|
||||
data.positionOpenPrice = currentAsk;
|
||||
@@ -472,16 +467,16 @@ void ProcessRSIReversalAsian(RSIReversalAsianData& data, double lotSize)
|
||||
if(data.UseTakeProfit && tp >= currentAsk)
|
||||
return;
|
||||
|
||||
// Set trade parameters
|
||||
data.trade.SetDeviationInPoints(data.Slippage);
|
||||
data.trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
data.trade.SetTypeFillingBySymbol(data.symbol);
|
||||
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"))
|
||||
const double vol = United_NormalizeVolume(data.symbol, tradeLotSize);
|
||||
if(vol <= 0.0)
|
||||
return;
|
||||
|
||||
if(data.trade.Sell(vol, data.symbol, currentBid, sl, tp, "RSI Overbought Crossover Sell"))
|
||||
{
|
||||
data.isPositionOpen = true;
|
||||
data.positionOpenPrice = currentBid;
|
||||
@@ -120,6 +120,70 @@ void RS_TryReversalEscape(RSIScalpingData& data, const ENUM_TIMEFRAMES tf, const
|
||||
" ATR=", DoubleToString(atr, (int)SymbolInfoInteger(data.symbol, SYMBOL_DIGITS)));
|
||||
}
|
||||
|
||||
void RS_ApplyTrailingStop(RSIScalpingData& data, const int MagicNumber,
|
||||
const bool useTrailingStop,
|
||||
const double trailingStopDistancePoints,
|
||||
const double trailingActivationPoints)
|
||||
{
|
||||
if(!useTrailingStop || trailingStopDistancePoints <= 0.0)
|
||||
return;
|
||||
if(!PositionSelectByMagic(data.symbol, (ulong)MagicNumber))
|
||||
return;
|
||||
|
||||
const double point = SymbolInfoDouble(data.symbol, SYMBOL_POINT);
|
||||
if(point <= 0.0)
|
||||
return;
|
||||
|
||||
const int digits = (int)SymbolInfoInteger(data.symbol, SYMBOL_DIGITS);
|
||||
const double trail_dist = trailingStopDistancePoints * point;
|
||||
const double activation_pts = (trailingActivationPoints > 0.0)
|
||||
? trailingActivationPoints
|
||||
: trailingStopDistancePoints;
|
||||
const double activation = activation_pts * point;
|
||||
const long stops_level = SymbolInfoInteger(data.symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
const double min_dist = (double)stops_level * point;
|
||||
|
||||
const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
const double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
const double cur_sl = PositionGetDouble(POSITION_SL);
|
||||
const double cur_tp = PositionGetDouble(POSITION_TP);
|
||||
|
||||
if(ptype == POSITION_TYPE_BUY)
|
||||
{
|
||||
const double bid = SymbolInfoDouble(data.symbol, SYMBOL_BID);
|
||||
if(bid - entry <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(bid - trail_dist, digits);
|
||||
if(min_dist > 0.0 && bid - new_sl < min_dist)
|
||||
new_sl = NormalizeDouble(bid - min_dist, digits);
|
||||
|
||||
if(new_sl >= bid || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl <= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(data.trade, data.symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
else if(ptype == POSITION_TYPE_SELL)
|
||||
{
|
||||
const double ask = SymbolInfoDouble(data.symbol, SYMBOL_ASK);
|
||||
if(entry - ask <= activation)
|
||||
return;
|
||||
|
||||
double new_sl = NormalizeDouble(ask + trail_dist, digits);
|
||||
if(min_dist > 0.0 && new_sl - ask < min_dist)
|
||||
new_sl = NormalizeDouble(ask + min_dist, digits);
|
||||
|
||||
if(new_sl <= ask || new_sl <= 0.0)
|
||||
return;
|
||||
if(cur_sl > 0.0 && new_sl >= cur_sl)
|
||||
return;
|
||||
|
||||
ModifyPositionByMagic(data.trade, data.symbol, (ulong)MagicNumber, new_sl, cur_tp);
|
||||
}
|
||||
}
|
||||
|
||||
string ErrorDescription(int errorCode)
|
||||
{
|
||||
switch(errorCode)
|
||||
@@ -364,21 +428,7 @@ void CheckEntrySignals(RSIScalpingData& data, ENUM_TIMEFRAMES TimeFrame, int Mag
|
||||
//+------------------------------------------------------------------+
|
||||
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;
|
||||
return United_NormalizeVolume(symbol, lotSize);
|
||||
}
|
||||
|
||||
void OpenBuyPosition(RSIScalpingData& data, int MagicNumber, double LotSize)
|
||||
@@ -520,7 +570,8 @@ void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES Ti
|
||||
double RSI_Oversold, double RSI_Target_Buy, double RSI_Target_Sell,
|
||||
int BarsToWait, double LotSize, int MagicNumber,
|
||||
bool UseReversalEscape, int ReversalATRPeriod, double ReversalAdverseAtrMult,
|
||||
int ReversalSignsRequired, double ReversalRsiVelocity, double ReversalBodyAtrMult)
|
||||
int ReversalSignsRequired, double ReversalRsiVelocity, double ReversalBodyAtrMult,
|
||||
bool UseTrailingStop, double TrailingStopDistancePoints, double TrailingActivationPoints)
|
||||
{
|
||||
// Skip if not initialized (symbol not available)
|
||||
if(!data.isInitialized)
|
||||
@@ -543,6 +594,10 @@ void ProcessRSIScalping(RSIScalpingData& data, string symbol, ENUM_TIMEFRAMES Ti
|
||||
RS_TryReversalEscape(data, TimeFrame, MagicNumber, ReversalATRPeriod, ReversalAdverseAtrMult,
|
||||
ReversalSignsRequired, ReversalRsiVelocity, ReversalBodyAtrMult);
|
||||
|
||||
if(in_pos)
|
||||
RS_ApplyTrailingStop(data, MagicNumber, UseTrailingStop,
|
||||
TrailingStopDistancePoints, TrailingActivationPoints);
|
||||
|
||||
if(!new_bar)
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| RSISecretSauceStrategy.mqh |
|
||||
//| Cluster-0 orchestrator: RSI leave extreme then peak/bottom entry |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef RSI_SECRET_SAUCE_STRATEGY_MQH
|
||||
#define RSI_SECRET_SAUCE_STRATEGY_MQH
|
||||
|
||||
#include <Trade/Trade.mqh>
|
||||
#include <Trade/PositionInfo.mqh>
|
||||
|
||||
struct RSISecretSauceOrcData
|
||||
{
|
||||
string actualSymbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
CPositionInfo positionInfo;
|
||||
int rsiHandle;
|
||||
int atrHandle;
|
||||
double rsiBuffer[];
|
||||
double atrBuffer[];
|
||||
double highBuffer[];
|
||||
double lowBuffer[];
|
||||
bool rsiWasOverbought;
|
||||
bool rsiWasOversold;
|
||||
bool rsiBackInRange;
|
||||
datetime lastRSIExitTime;
|
||||
datetime lastRSIReentryTime;
|
||||
datetime lastTradeTime;
|
||||
datetime lastBarTime;
|
||||
};
|
||||
|
||||
bool RSS_UpdateIndicators(RSISecretSauceOrcData &d)
|
||||
{
|
||||
int rsiBarsNeeded = RSS_RSILookback + 5;
|
||||
if(CopyBuffer(d.rsiHandle, 0, 0, rsiBarsNeeded, d.rsiBuffer) < rsiBarsNeeded)
|
||||
return false;
|
||||
if(CopyBuffer(d.atrHandle, 0, 0, 2, d.atrBuffer) < 2)
|
||||
return false;
|
||||
if(CopyHigh(d.actualSymbol, RSS_Timeframe, 0, RSS_SwingLookback + 5, d.highBuffer) < RSS_SwingLookback + 5)
|
||||
return false;
|
||||
if(CopyLow(d.actualSymbol, RSS_Timeframe, 0, RSS_SwingLookback + 5, d.lowBuffer) < RSS_SwingLookback + 5)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RSS_UpdateRSIState(RSISecretSauceOrcData &d)
|
||||
{
|
||||
double rsiCurrent = d.rsiBuffer[0];
|
||||
double rsiPrev = d.rsiBuffer[1];
|
||||
|
||||
if(rsiPrev >= RSS_RSIOverbought && rsiCurrent < RSS_RSIOverbought)
|
||||
{
|
||||
d.rsiWasOverbought = true;
|
||||
d.rsiBackInRange = true;
|
||||
d.lastRSIExitTime = TimeCurrent();
|
||||
d.lastRSIReentryTime = TimeCurrent();
|
||||
}
|
||||
|
||||
if(rsiPrev <= RSS_RSIOversold && rsiCurrent > RSS_RSIOversold)
|
||||
{
|
||||
d.rsiWasOversold = true;
|
||||
d.rsiBackInRange = true;
|
||||
d.lastRSIExitTime = TimeCurrent();
|
||||
d.lastRSIReentryTime = TimeCurrent();
|
||||
}
|
||||
|
||||
if(rsiCurrent >= RSS_RSIOverbought)
|
||||
{
|
||||
d.rsiWasOverbought = false;
|
||||
d.rsiBackInRange = false;
|
||||
}
|
||||
|
||||
if(rsiCurrent <= RSS_RSIOversold)
|
||||
{
|
||||
d.rsiWasOversold = false;
|
||||
d.rsiBackInRange = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RSS_IsRSIPeak(RSISecretSauceOrcData &d)
|
||||
{
|
||||
if(ArraySize(d.rsiBuffer) < RSS_PeakBars + 2)
|
||||
return false;
|
||||
double currentRSI = d.rsiBuffer[0];
|
||||
bool isPeak = true;
|
||||
for(int i = 1; i <= RSS_PeakBars; i++)
|
||||
{
|
||||
if(d.rsiBuffer[i] >= currentRSI)
|
||||
{
|
||||
isPeak = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d.rsiBuffer[1] >= currentRSI)
|
||||
isPeak = false;
|
||||
return isPeak;
|
||||
}
|
||||
|
||||
bool RSS_IsRSIBottom(RSISecretSauceOrcData &d)
|
||||
{
|
||||
if(ArraySize(d.rsiBuffer) < RSS_PeakBars + 2)
|
||||
return false;
|
||||
double currentRSI = d.rsiBuffer[0];
|
||||
bool isBottom = true;
|
||||
for(int i = 1; i <= RSS_PeakBars; i++)
|
||||
{
|
||||
if(d.rsiBuffer[i] <= currentRSI)
|
||||
{
|
||||
isBottom = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d.rsiBuffer[1] <= currentRSI)
|
||||
isBottom = false;
|
||||
return isBottom;
|
||||
}
|
||||
|
||||
double RSS_GetSwingStopLoss(RSISecretSauceOrcData &d, double currentPrice, ENUM_POSITION_TYPE type)
|
||||
{
|
||||
if(type == POSITION_TYPE_BUY)
|
||||
{
|
||||
double lowestLow = d.lowBuffer[0];
|
||||
for(int i = 1; i < RSS_SwingLookback && i < ArraySize(d.lowBuffer); i++)
|
||||
{
|
||||
if(d.lowBuffer[i] < lowestLow)
|
||||
lowestLow = d.lowBuffer[i];
|
||||
}
|
||||
return lowestLow;
|
||||
}
|
||||
double highestHigh = d.highBuffer[0];
|
||||
for(int i = 1; i < RSS_SwingLookback && i < ArraySize(d.highBuffer); i++)
|
||||
{
|
||||
if(d.highBuffer[i] > highestHigh)
|
||||
highestHigh = d.highBuffer[i];
|
||||
}
|
||||
return highestHigh;
|
||||
}
|
||||
|
||||
bool RSS_CalculateStops(RSISecretSauceOrcData &d, double price, ENUM_POSITION_TYPE type, double &sl, double &tp)
|
||||
{
|
||||
double atrValue = d.atrBuffer[0];
|
||||
if(atrValue <= 0)
|
||||
atrValue = price * 0.01;
|
||||
|
||||
double slDistance = atrValue * RSS_StopLossATR;
|
||||
double tpDistance = atrValue * RSS_TakeProfitATR;
|
||||
|
||||
int digits = (int)SymbolInfoInteger(d.actualSymbol, SYMBOL_DIGITS);
|
||||
double point = SymbolInfoDouble(d.actualSymbol, SYMBOL_POINT);
|
||||
int stopsLevel = (int)SymbolInfoInteger(d.actualSymbol, SYMBOL_TRADE_STOPS_LEVEL);
|
||||
double minStopDistance = MathMax(stopsLevel * point, point * 10);
|
||||
|
||||
if(RSS_UseSwingStopLoss)
|
||||
{
|
||||
double swingStop = RSS_GetSwingStopLoss(d, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool RSS_CanOpenNewPosition(RSISecretSauceOrcData &d)
|
||||
{
|
||||
int positionCount = 0;
|
||||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||||
{
|
||||
if(d.positionInfo.SelectByIndex(i))
|
||||
{
|
||||
if(d.positionInfo.Symbol() == d.actualSymbol && d.positionInfo.Magic() == RSS_MagicNumber)
|
||||
positionCount++;
|
||||
}
|
||||
}
|
||||
if(positionCount >= RSS_MaxPositions)
|
||||
return false;
|
||||
|
||||
if(d.lastTradeTime > 0)
|
||||
{
|
||||
int barsSince = Bars(d.actualSymbol, RSS_Timeframe, d.lastTradeTime, TimeCurrent());
|
||||
if(barsSince < RSS_MinBarsBetweenTrades)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RSS_OpenPosition(RSISecretSauceOrcData &d, ENUM_POSITION_TYPE type, const double lotSize)
|
||||
{
|
||||
double price = (type == POSITION_TYPE_BUY) ?
|
||||
SymbolInfoDouble(d.actualSymbol, SYMBOL_ASK) :
|
||||
SymbolInfoDouble(d.actualSymbol, SYMBOL_BID);
|
||||
|
||||
if(price <= 0)
|
||||
return;
|
||||
|
||||
double sl = 0.0, tp = 0.0;
|
||||
if(!RSS_CalculateStops(d, price, type, sl, tp))
|
||||
return;
|
||||
|
||||
string comment = "RSI_Secret_" + (type == POSITION_TYPE_BUY ? "LONG" : "SHORT");
|
||||
|
||||
bool result = false;
|
||||
if(type == POSITION_TYPE_BUY)
|
||||
result = d.trade.Buy(lotSize, d.actualSymbol, 0, sl, tp, comment);
|
||||
else
|
||||
result = d.trade.Sell(lotSize, d.actualSymbol, 0, sl, tp, comment);
|
||||
|
||||
if(result)
|
||||
{
|
||||
d.lastTradeTime = TimeCurrent();
|
||||
if(type == POSITION_TYPE_BUY)
|
||||
d.rsiWasOverbought = false;
|
||||
else
|
||||
d.rsiWasOversold = false;
|
||||
d.rsiBackInRange = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RSS_CheckEntrySignals(RSISecretSauceOrcData &d, const double lotSize)
|
||||
{
|
||||
if(d.rsiWasOverbought && d.rsiBackInRange)
|
||||
{
|
||||
if(d.rsiBuffer[0] < RSS_RSIOverbought && RSS_IsRSIPeak(d))
|
||||
RSS_OpenPosition(d, POSITION_TYPE_BUY, lotSize);
|
||||
}
|
||||
|
||||
if(d.rsiWasOversold && d.rsiBackInRange)
|
||||
{
|
||||
if(d.rsiBuffer[0] > RSS_RSIOversold && RSS_IsRSIBottom(d))
|
||||
RSS_OpenPosition(d, POSITION_TYPE_SELL, lotSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool InitRSISecretSauce(RSISecretSauceOrcData &d, const string symbol)
|
||||
{
|
||||
d.isInitialized = false;
|
||||
d.rsiHandle = INVALID_HANDLE;
|
||||
d.atrHandle = INVALID_HANDLE;
|
||||
d.rsiWasOverbought = false;
|
||||
d.rsiWasOversold = false;
|
||||
d.rsiBackInRange = false;
|
||||
d.lastRSIExitTime = 0;
|
||||
d.lastRSIReentryTime = 0;
|
||||
d.lastTradeTime = 0;
|
||||
d.lastBarTime = 0;
|
||||
d.actualSymbol = symbol;
|
||||
StringTrimLeft(d.actualSymbol);
|
||||
StringTrimRight(d.actualSymbol);
|
||||
if(StringLen(d.actualSymbol) == 0)
|
||||
d.actualSymbol = _Symbol;
|
||||
|
||||
if(!SymbolSelect(d.actualSymbol, true))
|
||||
{
|
||||
Print("RSISecretSauce: symbol not available '", d.actualSymbol, "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
d.rsiHandle = iRSI(d.actualSymbol, RSS_Timeframe, RSS_RSIPeriod, PRICE_CLOSE);
|
||||
d.atrHandle = iATR(d.actualSymbol, RSS_Timeframe, RSS_ATRPeriod);
|
||||
if(d.rsiHandle == INVALID_HANDLE || d.atrHandle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
ArraySetAsSeries(d.rsiBuffer, true);
|
||||
ArraySetAsSeries(d.atrBuffer, true);
|
||||
ArraySetAsSeries(d.highBuffer, true);
|
||||
ArraySetAsSeries(d.lowBuffer, true);
|
||||
|
||||
d.trade.SetExpertMagicNumber(RSS_MagicNumber);
|
||||
d.trade.SetDeviationInPoints(RSS_Slippage);
|
||||
d.trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||||
|
||||
d.isInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitRSISecretSauce(RSISecretSauceOrcData &d)
|
||||
{
|
||||
if(d.rsiHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.rsiHandle);
|
||||
if(d.atrHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.atrHandle);
|
||||
d.rsiHandle = INVALID_HANDLE;
|
||||
d.atrHandle = INVALID_HANDLE;
|
||||
d.isInitialized = false;
|
||||
}
|
||||
|
||||
void ProcessRSISecretSauce(RSISecretSauceOrcData &d, const double lotSize)
|
||||
{
|
||||
if(!d.isInitialized)
|
||||
return;
|
||||
|
||||
int requiredBars = MathMax(RSS_RSILookback, RSS_SwingLookback) + 10;
|
||||
if(Bars(d.actualSymbol, RSS_Timeframe) < requiredBars)
|
||||
return;
|
||||
|
||||
datetime currentBarTime = iTime(d.actualSymbol, RSS_Timeframe, 0);
|
||||
if(currentBarTime == d.lastBarTime)
|
||||
return;
|
||||
|
||||
d.lastBarTime = currentBarTime;
|
||||
|
||||
if(!RSS_UpdateIndicators(d))
|
||||
return;
|
||||
|
||||
RSS_UpdateRSIState(d);
|
||||
|
||||
if(RSS_CanOpenNewPosition(d))
|
||||
RSS_CheckEntrySignals(d, lotSize);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,318 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SimpleTrendlineStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
#define SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
|
||||
struct SimpleTrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct SimpleTrendlineData
|
||||
{
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
ENUM_TIMEFRAMES signalTF;
|
||||
ENUM_TIMEFRAMES higherTF;
|
||||
int maPeriod;
|
||||
ENUM_MA_METHOD maMethod;
|
||||
ENUM_APPLIED_PRICE appliedPrice;
|
||||
int htfBarsToScan;
|
||||
double touchTolerancePoints;
|
||||
double breakBufferPoints;
|
||||
ulong magic;
|
||||
bool drawTrendline;
|
||||
int maHandle;
|
||||
datetime lastSignalBarTime;
|
||||
string lineName;
|
||||
};
|
||||
|
||||
double ST_NormalizeVolume(const string sym, double vol)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
||||
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
||||
if(step > 0.0)
|
||||
vol = MathFloor(vol / step) * step;
|
||||
if(vol < minLot)
|
||||
vol = minLot;
|
||||
if(vol > maxLot)
|
||||
vol = maxLot;
|
||||
return vol;
|
||||
}
|
||||
|
||||
bool ST_GetPosition(const string sym, const ulong magic, ENUM_POSITION_TYPE &type, double &volume)
|
||||
{
|
||||
if(!PositionSelectByMagic(sym, magic))
|
||||
return false;
|
||||
type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
int ST_FindRecentCrossPoints(SimpleTrendlineData &d, datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
if(d.maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(d.htfBarsToScan, d.maPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
int copiedRates = CopyRates(d.symbol, d.higherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(d.maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
for(int i = 2; i < bars - 1; i++)
|
||||
{
|
||||
double d0 = rates[i].close - maBuf[i];
|
||||
double d1 = rates[i + 1].close - maBuf[i + 1];
|
||||
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
|
||||
{
|
||||
int n = ArraySize(times);
|
||||
ArrayResize(times, n + 1);
|
||||
ArrayResize(prices, n + 1);
|
||||
times[n] = rates[i].time;
|
||||
prices[n] = rates[i].close;
|
||||
if(ArraySize(times) >= 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ArraySize(times);
|
||||
}
|
||||
|
||||
bool ST_BuildTrendline(SimpleTrendlineData &d, SimpleTrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
if(ST_FindRecentCrossPoints(d, ts, ps) < 3)
|
||||
return false;
|
||||
|
||||
datetime tOld[3];
|
||||
double pOld[3];
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
tOld[i] = ts[2 - i];
|
||||
pOld[i] = ps[2 - i];
|
||||
}
|
||||
|
||||
long t0 = (long)tOld[0];
|
||||
double x1 = 0.0;
|
||||
double x2 = (double)((long)tOld[1] - t0);
|
||||
double x3 = (double)((long)tOld[2] - t0);
|
||||
double y1 = pOld[0];
|
||||
double y2 = pOld[1];
|
||||
double y3 = pOld[2];
|
||||
|
||||
double sx = x1 + x2 + x3;
|
||||
double sy = y1 + y2 + y3;
|
||||
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
|
||||
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
|
||||
double den = 3.0 * sxx - sx * sx;
|
||||
if(MathAbs(den) < 1e-10)
|
||||
return false;
|
||||
|
||||
m.a = (3.0 * sxy - sx * sy) / den;
|
||||
m.b = (sy - m.a * sx) / 3.0;
|
||||
m.t1 = tOld[0];
|
||||
m.t2 = tOld[1];
|
||||
m.t3 = tOld[2];
|
||||
m.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double ST_LinePriceAt(const SimpleTrendlineModel &m, const datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void ST_DrawTrendline(SimpleTrendlineData &d, const SimpleTrendlineModel &m)
|
||||
{
|
||||
if(!d.drawTrendline || !m.valid || d.symbol != _Symbol)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(d.symbol, d.signalTF, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(d.signalTF) * 20;
|
||||
|
||||
double pStart = ST_LinePriceAt(m, tStart);
|
||||
double pEnd = ST_LinePriceAt(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, d.lineName) < 0)
|
||||
ObjectCreate(0, d.lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, d.lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, d.lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
void ST_TryExitOnBreak(SimpleTrendlineData &d, const SimpleTrendlineModel &m)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType;
|
||||
double vol;
|
||||
if(!ST_GetPosition(d.symbol, d.magic, posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(d.symbol, d.signalTF, 1);
|
||||
datetime t1 = iTime(d.symbol, d.signalTF, 1);
|
||||
double line1 = ST_LinePriceAt(m, t1);
|
||||
double buf = d.breakBufferPoints * SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
|
||||
bool closePos = false;
|
||||
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
|
||||
closePos = true;
|
||||
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
|
||||
closePos = true;
|
||||
|
||||
if(closePos)
|
||||
ClosePositionByMagic(d.trade, d.symbol, d.magic);
|
||||
}
|
||||
|
||||
void ST_TryPullbackEntry(SimpleTrendlineData &d, const SimpleTrendlineModel &m, const double lots)
|
||||
{
|
||||
if(PositionExistsByMagic(d.symbol, d.magic))
|
||||
return;
|
||||
|
||||
MqlRates b1[], b2[];
|
||||
ArraySetAsSeries(b1, true);
|
||||
ArraySetAsSeries(b2, true);
|
||||
if(CopyRates(d.symbol, d.signalTF, 1, 1, b1) != 1)
|
||||
return;
|
||||
if(CopyRates(d.symbol, d.signalTF, 2, 1, b2) != 1)
|
||||
return;
|
||||
if(ArraySize(b1) < 1 || ArraySize(b2) < 1)
|
||||
return;
|
||||
|
||||
double line1 = ST_LinePriceAt(m, b1[0].time);
|
||||
double tol = d.touchTolerancePoints * SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
double vol = ST_NormalizeVolume(d.symbol, lots);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1[0].low <= (line1 + tol));
|
||||
bool reclaim = (b1[0].close > line1);
|
||||
bool bullish = (b1[0].close > b1[0].open);
|
||||
bool stillHealthy = (b2[0].close >= ST_LinePriceAt(m, b2[0].time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
if(!d.trade.Buy(vol, d.symbol, 0.0, 0.0, 0.0, "SimpleTrendline BUY"))
|
||||
Print("SimpleTrendline BUY failed [", d.symbol, "] retcode=", d.trade.ResultRetcode(), " ", d.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1[0].high >= (line1 - tol));
|
||||
bool reject = (b1[0].close < line1);
|
||||
bool bearish = (b1[0].close < b1[0].open);
|
||||
bool stillWeak = (b2[0].close <= ST_LinePriceAt(m, b2[0].time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
if(!d.trade.Sell(vol, d.symbol, 0.0, 0.0, 0.0, "SimpleTrendline SELL"))
|
||||
Print("SimpleTrendline SELL failed [", d.symbol, "] retcode=", d.trade.ResultRetcode(), " ", d.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool InitSimpleTrendline(SimpleTrendlineData &d,
|
||||
const string symbol,
|
||||
const ENUM_TIMEFRAMES signalTF,
|
||||
const ENUM_TIMEFRAMES higherTF,
|
||||
const int maPeriod,
|
||||
const ENUM_MA_METHOD maMethod,
|
||||
const ENUM_APPLIED_PRICE appliedPrice,
|
||||
const int htfBarsToScan,
|
||||
const double touchTolerancePoints,
|
||||
const double breakBufferPoints,
|
||||
const ulong magic,
|
||||
const bool drawTrendline)
|
||||
{
|
||||
d.isInitialized = false;
|
||||
d.symbol = symbol;
|
||||
StringTrimLeft(d.symbol);
|
||||
StringTrimRight(d.symbol);
|
||||
if(StringLen(d.symbol) == 0)
|
||||
d.symbol = _Symbol;
|
||||
|
||||
if(!SymbolSelect(d.symbol, true))
|
||||
return false;
|
||||
|
||||
d.signalTF = signalTF;
|
||||
d.higherTF = higherTF;
|
||||
d.maPeriod = maPeriod;
|
||||
d.maMethod = maMethod;
|
||||
d.appliedPrice = appliedPrice;
|
||||
d.htfBarsToScan = htfBarsToScan;
|
||||
d.touchTolerancePoints = touchTolerancePoints;
|
||||
d.breakBufferPoints = breakBufferPoints;
|
||||
d.magic = magic;
|
||||
d.drawTrendline = drawTrendline;
|
||||
d.lastSignalBarTime = 0;
|
||||
d.lineName = "SimpleTrendline_" + d.symbol + "_" + IntegerToString((int)d.magic);
|
||||
|
||||
d.trade.SetExpertMagicNumber((long)d.magic);
|
||||
d.trade.SetTypeFillingBySymbol(d.symbol);
|
||||
d.trade.SetDeviationInPoints(20);
|
||||
|
||||
d.maHandle = iMA(d.symbol, d.higherTF, d.maPeriod, 0, d.maMethod, d.appliedPrice);
|
||||
if(d.maHandle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
d.isInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitSimpleTrendline(SimpleTrendlineData &d)
|
||||
{
|
||||
if(d.maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.maHandle);
|
||||
d.maHandle = INVALID_HANDLE;
|
||||
if(ObjectFind(0, d.lineName) >= 0)
|
||||
ObjectDelete(0, d.lineName);
|
||||
d.isInitialized = false;
|
||||
}
|
||||
|
||||
void ProcessSimpleTrendline(SimpleTrendlineData &d, const double lots)
|
||||
{
|
||||
if(!d.isInitialized)
|
||||
return;
|
||||
|
||||
datetime bar0 = iTime(d.symbol, d.signalTF, 0);
|
||||
if(bar0 == 0 || bar0 == d.lastSignalBarTime)
|
||||
return;
|
||||
d.lastSignalBarTime = bar0;
|
||||
|
||||
SimpleTrendlineModel m;
|
||||
if(!ST_BuildTrendline(d, m))
|
||||
return;
|
||||
|
||||
ST_DrawTrendline(d, m);
|
||||
ST_TryExitOnBreak(d, m);
|
||||
ST_TryPullbackEntry(d, m, lots);
|
||||
}
|
||||
|
||||
#endif // SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
@@ -51,6 +51,12 @@ void SuperEMA_Log(SuperEMAData &d, const string s)
|
||||
Print("[SuperEMA] ", s);
|
||||
}
|
||||
|
||||
double SuperEMA_Point(const SuperEMAData &d)
|
||||
{
|
||||
double pt = SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
return (pt > 0.0 ? pt : _Point);
|
||||
}
|
||||
|
||||
bool SuperEMA_IsNewBar(SuperEMAData &d)
|
||||
{
|
||||
datetime t = iTime(d.symbol, d.tf, 0);
|
||||
@@ -176,7 +182,8 @@ bool SuperEMA_PullbackNearFastEmaLong(SuperEMAData &d)
|
||||
double lo = iLow(d.symbol, d.tf, 1);
|
||||
if(emaF <= 0.0)
|
||||
return false;
|
||||
return (lo <= emaF + d.slBufferPoints * _Point * 3.0);
|
||||
const double pt = SuperEMA_Point(d);
|
||||
return (lo <= emaF + d.slBufferPoints * pt * 3.0);
|
||||
}
|
||||
|
||||
bool SuperEMA_PullbackNearFastEmaShort(SuperEMAData &d)
|
||||
@@ -185,7 +192,8 @@ bool SuperEMA_PullbackNearFastEmaShort(SuperEMAData &d)
|
||||
double hi = iHigh(d.symbol, d.tf, 1);
|
||||
if(emaF <= 0.0)
|
||||
return false;
|
||||
return (hi >= emaF - d.slBufferPoints * _Point * 3.0);
|
||||
const double pt = SuperEMA_Point(d);
|
||||
return (hi >= emaF - d.slBufferPoints * pt * 3.0);
|
||||
}
|
||||
|
||||
int SuperEMA_PositionsByMagic(SuperEMAData &d)
|
||||
@@ -196,6 +204,8 @@ int SuperEMA_PositionsByMagic(SuperEMAData &d)
|
||||
ulong t = PositionGetTicket(i);
|
||||
if(t == 0)
|
||||
continue;
|
||||
if(!PositionSelectByTicket(t))
|
||||
continue;
|
||||
if(PositionGetString(POSITION_SYMBOL) == d.symbol && (int)PositionGetInteger(POSITION_MAGIC) == d.magic)
|
||||
n++;
|
||||
}
|
||||
@@ -209,7 +219,7 @@ void SuperEMA_ComputeSLTP(SuperEMAData &d, const bool isBuy, double &sl, double
|
||||
if(!d.useStructuralSL)
|
||||
return;
|
||||
double emaM = SuperEMA_EmaAt(d, d.emaMid, d.emaTrendBars);
|
||||
double buf = d.slBufferPoints * _Point;
|
||||
double buf = d.slBufferPoints * SuperEMA_Point(d);
|
||||
if(isBuy)
|
||||
sl = emaM - buf;
|
||||
else
|
||||
@@ -418,9 +428,13 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
|
||||
SuperEMA_ManageExits(d);
|
||||
|
||||
// Same order as standalone SuperEMAXAUUSD: skip entry logic when flat is not allowed.
|
||||
if(d.oneTradeOnly && SuperEMA_PositionsByMagic(d) > 0)
|
||||
return;
|
||||
|
||||
const int sh = d.emaTrendBars;
|
||||
double h1 = 0.0, h2 = 0.0;
|
||||
if(!SuperEMA_MacdHistAt(d, 1, h1) || !SuperEMA_MacdHistAt(d, 2, h2))
|
||||
double h1 = 0.0;
|
||||
if(!SuperEMA_MacdHistAt(d, 1, h1))
|
||||
return;
|
||||
|
||||
bool up = SuperEMA_TrendUp(d, sh);
|
||||
@@ -453,14 +467,14 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
break;
|
||||
}
|
||||
|
||||
if(d.oneTradeOnly && SuperEMA_PositionsByMagic(d) > 0)
|
||||
if(!wantBuy && !wantSell)
|
||||
return;
|
||||
|
||||
const double vol = United_NormalizeVolume(d.symbol, lots);
|
||||
if(vol <= 0.0)
|
||||
{
|
||||
if(wantBuy && !United_MayOpenNewEntry(d.symbol, (ulong)d.magic, true))
|
||||
wantBuy = false;
|
||||
if(wantSell && !United_MayOpenNewEntry(d.symbol, (ulong)d.magic, false))
|
||||
wantSell = false;
|
||||
if(!wantBuy && !wantSell)
|
||||
return;
|
||||
SuperEMA_Log(d, "Skip entry: normalized volume <= 0");
|
||||
return;
|
||||
}
|
||||
|
||||
MqlTick tick;
|
||||
@@ -474,7 +488,7 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
#ifndef UNITED_MARTINGALE_NO_SELF_CLOSE
|
||||
SuperEMA_ComputeSLTP(d, true, sl, tp);
|
||||
#endif
|
||||
if(d.trade.Buy(lots, d.symbol, tick.ask, sl, tp, "United SuperEMA long"))
|
||||
if(d.trade.Buy(vol, d.symbol, tick.ask, sl, tp, "United SuperEMA long"))
|
||||
SuperEMA_Log(d, StringFormat("BUY ask=%.5f sl=%.5f", tick.ask, sl));
|
||||
}
|
||||
else if(wantSell && !wantBuy)
|
||||
@@ -482,7 +496,7 @@ void ProcessSuperEMA(SuperEMAData &d, const double lots)
|
||||
#ifndef UNITED_MARTINGALE_NO_SELF_CLOSE
|
||||
SuperEMA_ComputeSLTP(d, false, sl, tp);
|
||||
#endif
|
||||
if(d.trade.Sell(lots, d.symbol, tick.bid, sl, tp, "United SuperEMA short"))
|
||||
if(d.trade.Sell(vol, d.symbol, tick.bid, sl, tp, "United SuperEMA short"))
|
||||
SuperEMA_Log(d, StringFormat("SELL bid=%.5f sl=%.5f", tick.bid, sl));
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,17 @@
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property version "1.20"
|
||||
#property strict
|
||||
#property description "LOT_* nominal at ORCH_ReferenceBalance; scale = balance/equity ÷ reference (clamped). No performance-evaluator ranking."
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#define UNITED_V2_DYNAMIC_LOTS
|
||||
double g_DB_LotSize;
|
||||
// Include strategy implementations early so structs are available
|
||||
#include "Strategies/DarvasBoxStrategy.mqh"
|
||||
#include "Strategies/EMASlopeDistanceStrategy.mqh"
|
||||
@@ -23,6 +26,7 @@
|
||||
#include "Strategies/RSIReversalAsianStrategy.mqh"
|
||||
#include "Strategies/RSIConsolidationStrategy.mqh"
|
||||
#include "Strategies/SimpleTrendlineStrategy.mqh"
|
||||
#include "Strategies/RSISecretSauceStrategy.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Lot Size Variables (for dynamic lot sizing) |
|
||||
@@ -31,6 +35,19 @@ double g_ES_LotSize; // EMA Slope Distance lot size
|
||||
double g_RC_LotSize; // RSI CrossOver Reversal lot size
|
||||
double g_RM_LotSize; // RSI MidPoint Hijack lot size
|
||||
|
||||
double g_Pos_RS_APPL;
|
||||
double g_Pos_RS_BTCUSD;
|
||||
double g_Pos_RS_NVDA;
|
||||
double g_Pos_RS_TSLA;
|
||||
double g_Pos_RS_XAUUSD;
|
||||
double g_Pos_RRA_EURUSD;
|
||||
double g_Pos_RRA_AUDUSD;
|
||||
double g_Pos_SE;
|
||||
double g_Pos_RCO;
|
||||
double g_Pos_ST_BTCUSD;
|
||||
double g_Pos_ST_XAUUSD;
|
||||
double g_RSS_LotSize;
|
||||
|
||||
bool United_MayOpenNewEntry(const string symbol, const ulong magic, const bool isBuy)
|
||||
{
|
||||
if(PositionExistsByMagic(symbol, magic))
|
||||
@@ -45,47 +62,57 @@ 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 EnableRSIMidPointHijack = false;
|
||||
input bool EnableRSIScalpingAPPL = false;
|
||||
input bool EnableRSIScalpingBTCUSD = false;
|
||||
input bool EnableRSIScalpingNVDA = false;
|
||||
input bool EnableRSIScalpingTSLA = false;
|
||||
input bool EnableRSIScalpingXAUUSD = true;
|
||||
input bool EnableSuperEMA = true;
|
||||
input bool EnableRSIConsolidation = true;
|
||||
input bool EnableRSIReversalAsianEURUSD = true;
|
||||
input bool EnableRSIReversalAsianAUDUSD = true;
|
||||
input bool EnableSimpleTrendlineBTCUSD = true;
|
||||
input bool EnableRSIReversalAsianEURUSD = false;
|
||||
input bool EnableRSIReversalAsianAUDUSD = false;
|
||||
input bool EnableSimpleTrendlineBTCUSD = false;
|
||||
input bool EnableSimpleTrendlineXAUUSD = true;
|
||||
input bool EnableRSISecretSauce = false;
|
||||
|
||||
input group "=== Centralized Lot Size (Granular Per Robot) ==="
|
||||
input double LOT_ES_EMASlopeDistance = 0.05;
|
||||
input double LOT_DB_DarvasBox = 0.04;
|
||||
input double LOT_ES_EMASlopeDistance = 0.09;
|
||||
input double LOT_RC_RSICrossOver = 0.1;
|
||||
input double LOT_RM_RSIMidPointHijack = 0.01;
|
||||
input double LOT_RS_APPL = 100.0;
|
||||
input double LOT_RS_BTCUSD = 0.15;
|
||||
input double LOT_RS_NVDA = 60.0;
|
||||
input double LOT_RS_TSLA = 20.0;
|
||||
input double LOT_RS_XAUUSD = 0.02;
|
||||
input double LOT_RRA_EURUSD = 0.01;
|
||||
input double LOT_RRA_AUDUSD = 0.10;
|
||||
input double LOT_SE_SuperEMA = 0.01;
|
||||
input double LOT_RCO_RSIConsolidation = 0.04;
|
||||
input double LOT_ST_BTCUSD = 0.19;
|
||||
input double LOT_RS_APPL = 5.0;
|
||||
input double LOT_RS_BTCUSD = 0.1;
|
||||
input double LOT_RS_NVDA = 10.0;
|
||||
input double LOT_RS_TSLA = 15.0;
|
||||
input double LOT_RS_XAUUSD = 0.05;
|
||||
input double LOT_RRA_EURUSD = 0.05;
|
||||
input double LOT_RRA_AUDUSD = 0.08;
|
||||
input double LOT_SE_SuperEMA = 0.02;
|
||||
input double LOT_RCO_RSIConsolidation = 0.02;
|
||||
input double LOT_ST_BTCUSD = 0.07;
|
||||
input double LOT_ST_XAUUSD = 0.02;
|
||||
input double LOT_RSS_SecretSauce = 0.01;
|
||||
|
||||
input group "=== Balance-based position sizing ==="
|
||||
input bool ORCH_ScaleLotsByBalance = true;
|
||||
input bool ORCH_UseEquityInsteadOfBalance = false;
|
||||
input double ORCH_ReferenceBalance = 10000.0;
|
||||
input double ORCH_MinBalanceScale = 0.1;
|
||||
input double ORCH_MaxBalanceScale = 10.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 1: DarvasBoxXAUUSD |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== DarvasBox Strategy ==="
|
||||
input string DB_Symbol = "XAUUSD";
|
||||
input string DB_Symbol = "XAU";
|
||||
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 color DB_BoxColor = (color)16711680;
|
||||
input int DB_BoxWidth = 1;
|
||||
input ENUM_TIMEFRAMES DB_TrendTimeframe = PERIOD_H2;
|
||||
input int DB_MA_Period = 125;
|
||||
@@ -94,6 +121,8 @@ 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 bool DB_UseVolumeSpikeFilter = true;
|
||||
input bool DB_UseTrendFilter = true;
|
||||
input int DB_MagicNumber = 135790;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -101,12 +130,16 @@ input int DB_MagicNumber = 135790;
|
||||
//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== EMA Slope Distance Strategy ==="
|
||||
input string ES_Symbol = "XAUUSD";
|
||||
input string ES_Symbol = "XAU";
|
||||
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_TrailingStop = 370.0;
|
||||
input bool ES_UseTrailingStop = true;
|
||||
input double ES_TrailingActivationPips = 0.0;
|
||||
input bool ES_UseStaleStopLossExit = false;
|
||||
input int ES_StaleStopLossSeconds = 33800;
|
||||
input double ES_LotGröße = 0.03;
|
||||
input int ES_MagicNumber = 12350;
|
||||
input bool ES_UseSpreadAdjustment = true;
|
||||
@@ -115,13 +148,18 @@ input bool ES_UseBarData = true;
|
||||
input int ES_MaxTradesPerCrossover = 9;
|
||||
input int ES_ProfitCheckBars = 18;
|
||||
input bool ES_CloseUnprofitableTrades = true;
|
||||
input bool ES_UseWeeklyADXFilter = true;
|
||||
input int ES_WeeklyADXPeriod = 15;
|
||||
input double ES_WeeklyADXMin = 40.0;
|
||||
input int ES_WeeklyADXBarShift = 2;
|
||||
input bool ES_WeeklyADXUseDirection = 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 string RC_Symbol = "XAU";
|
||||
input int RC_MagicNumber = 7;
|
||||
input int RC_rsiPeriod = 19;
|
||||
input int RC_overboughtLevel = 93;
|
||||
@@ -140,6 +178,7 @@ input double RC_exitBuyRSI = 86;
|
||||
input double RC_exitSellRSI = 10;
|
||||
input double RC_TrailingStop = 295;
|
||||
input double RC_emaDistanceThreshold = 165;
|
||||
input bool RC_UseTrendStrengthFilter = true;
|
||||
input int RC_tradingHourOneBegin = 24;
|
||||
input int RC_tradingHourOneEnd = 22;
|
||||
input int RC_tradingHourTwoBegin = 6;
|
||||
@@ -157,7 +196,7 @@ input bool RC_Saturday = false;
|
||||
//| PEPPERSTONE US: Gold symbol is typically "XAUUSD" or "GOLD" |
|
||||
//+------------------------------------------------------------------+
|
||||
input group "=== RSI MidPoint Hijack Strategy ==="
|
||||
input string RM_Symbol = "XAUUSD";
|
||||
input string RM_Symbol = "XAU";
|
||||
input ENUM_TIMEFRAMES RM_InpTimeframe = PERIOD_H1;
|
||||
input double RM_InpLotSize = 0.02;
|
||||
input int RM_InpMagicNumberRSIFollow = 1001;
|
||||
@@ -212,7 +251,7 @@ input int RM_InpEMADistancePeriod = 26;
|
||||
//| 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 string RS_APPL_Symbol = "AAPL.NAS"; // Pepperstone / match tester set (also try AAPL.US)
|
||||
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;
|
||||
@@ -240,7 +279,7 @@ 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 string RS_NVDA_Symbol = "NVDA.NAS"; // Pepperstone / match tester set (also try NVDA.US)
|
||||
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;
|
||||
@@ -254,7 +293,7 @@ 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 string RS_TSLA_Symbol = "TSLA.NAS"; // Pepperstone / match tester set (also try TSLA.US)
|
||||
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;
|
||||
@@ -268,7 +307,7 @@ input int RS_TSLA_MagicNumber = 125421321;
|
||||
input int RS_TSLA_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping XAUUSD ==="
|
||||
input string RS_XAUUSD_Symbol = "XAUUSD";
|
||||
input string RS_XAUUSD_Symbol = "XAU";
|
||||
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;
|
||||
@@ -289,6 +328,31 @@ input int RS_ReversalSignsRequired = 2;
|
||||
input double RS_ReversalRsiVelocity = 16.0;
|
||||
input double RS_ReversalBodyAtrMult = 5.1;
|
||||
|
||||
input group "=== RSI Scalping APPL — Trailing (cluster-fuck BTC-style defaults) ==="
|
||||
input bool RS_APPL_UseTrailingStop = true;
|
||||
input double RS_APPL_TrailDistancePoints = 120.0;
|
||||
input double RS_APPL_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping BTCUSD — Trailing ==="
|
||||
input bool RS_BTCUSD_UseTrailingStop = true;
|
||||
input double RS_BTCUSD_TrailDistancePoints = 120.0;
|
||||
input double RS_BTCUSD_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping NVDA — Trailing ==="
|
||||
input bool RS_NVDA_UseTrailingStop = true;
|
||||
input double RS_NVDA_TrailDistancePoints = 375.0;
|
||||
input double RS_NVDA_TrailActivationPoints = 75.0;
|
||||
|
||||
input group "=== RSI Scalping TSLA — Trailing ==="
|
||||
input bool RS_TSLA_UseTrailingStop = true;
|
||||
input double RS_TSLA_TrailDistancePoints = 900.0;
|
||||
input double RS_TSLA_TrailActivationPoints = 950.0;
|
||||
|
||||
input group "=== RSI Scalping XAUUSD — Trailing ==="
|
||||
input bool RS_XAUUSD_UseTrailingStop = true;
|
||||
input double RS_XAUUSD_TrailDistancePoints = 1000.0;
|
||||
input double RS_XAUUSD_TrailActivationPoints = 550.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 11-12: RSI Reversal Asian Strategies |
|
||||
//| Each RSI Reversal Asian strategy trades on its own symbol: |
|
||||
@@ -334,7 +398,7 @@ input int RRA_AUDUSD_MagicNumber = 30002;
|
||||
input int RRA_AUDUSD_Slippage = 3;
|
||||
|
||||
input group "=== SuperEMA (EMA + CCI + MACD) ==="
|
||||
input string SE_Symbol = "XAUUSD";
|
||||
input string SE_Symbol = "XAU";
|
||||
input ENUM_TIMEFRAMES SE_Timeframe = PERIOD_M15;
|
||||
input double SE_LotSize = 0.01;
|
||||
input int SE_SlippagePoints = 55;
|
||||
@@ -362,7 +426,7 @@ input bool SE_ExitBelowMidEma = false;
|
||||
input bool SE_DebugLogs = false;
|
||||
|
||||
input group "=== RSI Consolidation (ranging / mean-reversion) ==="
|
||||
input string RCO_Symbol = "XAUUSD";
|
||||
input string RCO_Symbol = "XAU";
|
||||
input ENUM_TIMEFRAMES RCO_SignalTF = PERIOD_M15;
|
||||
input bool RCO_EntryOnNewBarOnly = true;
|
||||
input int RCO_ADX_Period = 23;
|
||||
@@ -404,7 +468,7 @@ input ulong ST_BTC_MagicNumber = 26042501;
|
||||
input bool ST_BTC_DrawTrendline = true;
|
||||
|
||||
input group "=== SimpleTrendline XAUUSD ==="
|
||||
input string ST_XAU_Symbol = "XAUUSD";
|
||||
input string ST_XAU_Symbol = "XAU";
|
||||
input ENUM_TIMEFRAMES ST_XAU_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_XAU_HigherTF = PERIOD_M10;
|
||||
input int ST_XAU_MAPeriod = 65;
|
||||
@@ -416,6 +480,68 @@ input double ST_XAU_BreakBuffer = 110.0;
|
||||
input ulong ST_XAU_MagicNumber = 26042503;
|
||||
input bool ST_XAU_DrawTrendline = true;
|
||||
|
||||
input group "=== RSI Secret Sauce XAUUSD ==="
|
||||
input string RSS_Symbol = "XAU";
|
||||
input int RSS_MagicNumber = 789012;
|
||||
input int RSS_Slippage = 10;
|
||||
input ENUM_TIMEFRAMES RSS_Timeframe = PERIOD_M30;
|
||||
input int RSS_RSIPeriod = 16;
|
||||
input double RSS_RSIOverbought = 72.5;
|
||||
input double RSS_RSIOversold = 32.5;
|
||||
input int RSS_RSILookback = 60;
|
||||
input int RSS_PeakBars = 2;
|
||||
input double RSS_StopLossATR = 2.75;
|
||||
input double RSS_TakeProfitATR = 5.0;
|
||||
input int RSS_ATRPeriod = 14;
|
||||
input bool RSS_UseSwingStopLoss = false;
|
||||
input int RSS_SwingLookback = 30;
|
||||
input int RSS_MaxPositions = 1;
|
||||
input int RSS_MinBarsBetweenTrades = 7;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Balance scaling: LOT_* = nominal size at ORCH_ReferenceBalance |
|
||||
//+------------------------------------------------------------------+
|
||||
double United_BalanceScaleFactor()
|
||||
{
|
||||
if(!ORCH_ScaleLotsByBalance || ORCH_ReferenceBalance <= 0.0)
|
||||
return 1.0;
|
||||
const double money = ORCH_UseEquityInsteadOfBalance
|
||||
? AccountInfoDouble(ACCOUNT_EQUITY)
|
||||
: AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
double raw = money / ORCH_ReferenceBalance;
|
||||
if(raw < ORCH_MinBalanceScale)
|
||||
raw = ORCH_MinBalanceScale;
|
||||
if(raw > ORCH_MaxBalanceScale)
|
||||
raw = ORCH_MaxBalanceScale;
|
||||
return raw;
|
||||
}
|
||||
|
||||
double United_ScaledLot(const double baseLot)
|
||||
{
|
||||
const double lot = baseLot * United_BalanceScaleFactor();
|
||||
return (lot > 0.0 ? lot : 0.0);
|
||||
}
|
||||
|
||||
void United_RefreshScaledLots()
|
||||
{
|
||||
g_DB_LotSize = United_ScaledLot(LOT_DB_DarvasBox);
|
||||
g_ES_LotSize = United_ScaledLot(LOT_ES_EMASlopeDistance);
|
||||
g_RC_LotSize = United_ScaledLot(LOT_RC_RSICrossOver);
|
||||
g_RM_LotSize = United_ScaledLot(LOT_RM_RSIMidPointHijack);
|
||||
g_Pos_RS_APPL = United_ScaledLot(LOT_RS_APPL);
|
||||
g_Pos_RS_BTCUSD = United_ScaledLot(LOT_RS_BTCUSD);
|
||||
g_Pos_RS_NVDA = United_ScaledLot(LOT_RS_NVDA);
|
||||
g_Pos_RS_TSLA = United_ScaledLot(LOT_RS_TSLA);
|
||||
g_Pos_RS_XAUUSD = United_ScaledLot(LOT_RS_XAUUSD);
|
||||
g_Pos_RRA_EURUSD = United_ScaledLot(LOT_RRA_EURUSD);
|
||||
g_Pos_RRA_AUDUSD = United_ScaledLot(LOT_RRA_AUDUSD);
|
||||
g_Pos_SE = United_ScaledLot(LOT_SE_SuperEMA);
|
||||
g_Pos_RCO = United_ScaledLot(LOT_RCO_RSIConsolidation);
|
||||
g_Pos_ST_BTCUSD = United_ScaledLot(LOT_ST_BTCUSD);
|
||||
g_Pos_ST_XAUUSD = United_ScaledLot(LOT_ST_XAUUSD);
|
||||
g_RSS_LotSize = United_ScaledLot(LOT_RSS_SecretSauce);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -453,6 +579,7 @@ struct EMASlopeData {
|
||||
bool crossover_detected;
|
||||
datetime trade_open_time;
|
||||
datetime last_bar_time;
|
||||
datetime es_last_sl_adjust_success_time;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -516,6 +643,7 @@ SuperEMAData seData;
|
||||
RSIConsolidationData rcoData;
|
||||
SimpleTrendlineData stBTCData;
|
||||
SimpleTrendlineData stXAUData;
|
||||
RSISecretSauceOrcData rssData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI Reversal Asian |
|
||||
@@ -530,10 +658,7 @@ int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize global lot size variables
|
||||
g_ES_LotSize = LOT_ES_EMASlopeDistance;
|
||||
g_RC_LotSize = LOT_RC_RSICrossOver;
|
||||
g_RM_LotSize = LOT_RM_RSIMidPointHijack;
|
||||
United_RefreshScaledLots();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
if(EnableDarvasBox)
|
||||
@@ -568,6 +693,10 @@ int OnInit()
|
||||
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);
|
||||
|
||||
if(EnableRSISecretSauce)
|
||||
if(!InitRSISecretSauce(rssData, RSS_Symbol))
|
||||
Print("Warning: RSI Secret Sauce failed to initialize for symbol '", RSS_Symbol, "'");
|
||||
|
||||
if(EnableSuperEMA)
|
||||
if(!InitSuperEMA(seData, SE_Symbol, SE_Timeframe, SE_SlippagePoints, SE_MagicNumber,
|
||||
SE_EmaFast, SE_EmaMid, SE_EmaSlow, SE_EmaTrendBars,
|
||||
@@ -626,6 +755,7 @@ int OnInit()
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""),
|
||||
(EnableRSISecretSauce ? "RSISecretSauce " : ""),
|
||||
(EnableSuperEMA ? "SuperEMA " : ""),
|
||||
(EnableRSIConsolidation ? "RSIConsolidation " : ""),
|
||||
(EnableRSIReversalAsianEURUSD ? "RSIReversalAsianEURUSD " : ""),
|
||||
@@ -668,6 +798,9 @@ void OnDeinit(const int reason)
|
||||
if(EnableRSIScalpingXAUUSD)
|
||||
DeinitRSIScalping(rsXAUUSDData);
|
||||
|
||||
if(EnableRSISecretSauce)
|
||||
DeinitRSISecretSauce(rssData);
|
||||
|
||||
if(EnableSuperEMA)
|
||||
DeinitSuperEMA(seData);
|
||||
|
||||
@@ -693,6 +826,8 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
United_RefreshScaledLots();
|
||||
|
||||
if(EnableDarvasBox)
|
||||
ProcessDarvasBox(DB_Symbol);
|
||||
|
||||
@@ -708,54 +843,62 @@ void OnTick()
|
||||
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, LOT_RS_APPL, RS_APPL_MagicNumber,
|
||||
RS_APPL_BarsToWait, g_Pos_RS_APPL, RS_APPL_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_APPL_UseTrailingStop, RS_APPL_TrailDistancePoints, RS_APPL_TrailActivationPoints);
|
||||
|
||||
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, LOT_RS_BTCUSD, RS_BTCUSD_MagicNumber,
|
||||
RS_BTCUSD_BarsToWait, g_Pos_RS_BTCUSD, RS_BTCUSD_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_BTCUSD_UseTrailingStop, RS_BTCUSD_TrailDistancePoints, RS_BTCUSD_TrailActivationPoints);
|
||||
|
||||
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, LOT_RS_NVDA, RS_NVDA_MagicNumber,
|
||||
RS_NVDA_BarsToWait, g_Pos_RS_NVDA, RS_NVDA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_NVDA_UseTrailingStop, RS_NVDA_TrailDistancePoints, RS_NVDA_TrailActivationPoints);
|
||||
|
||||
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, LOT_RS_TSLA, RS_TSLA_MagicNumber,
|
||||
RS_TSLA_BarsToWait, g_Pos_RS_TSLA, RS_TSLA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_TSLA_UseTrailingStop, RS_TSLA_TrailDistancePoints, RS_TSLA_TrailActivationPoints);
|
||||
|
||||
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, LOT_RS_XAUUSD, RS_XAUUSD_MagicNumber,
|
||||
RS_XAUUSD_BarsToWait, g_Pos_RS_XAUUSD, RS_XAUUSD_MagicNumber,
|
||||
RS_UseReversalEscape, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_XAUUSD_UseTrailingStop, RS_XAUUSD_TrailDistancePoints, RS_XAUUSD_TrailActivationPoints);
|
||||
|
||||
if(EnableRSISecretSauce)
|
||||
ProcessRSISecretSauce(rssData, g_RSS_LotSize);
|
||||
|
||||
if(EnableRSIReversalAsianEURUSD)
|
||||
ProcessRSIReversalAsian(rraEURUSDData, LOT_RRA_EURUSD);
|
||||
ProcessRSIReversalAsian(rraEURUSDData, g_Pos_RRA_EURUSD);
|
||||
|
||||
if(EnableRSIReversalAsianAUDUSD)
|
||||
ProcessRSIReversalAsian(rraAUDUSDData, LOT_RRA_AUDUSD);
|
||||
ProcessRSIReversalAsian(rraAUDUSDData, g_Pos_RRA_AUDUSD);
|
||||
|
||||
if(EnableSuperEMA)
|
||||
ProcessSuperEMA(seData, LOT_SE_SuperEMA);
|
||||
ProcessSuperEMA(seData, g_Pos_SE);
|
||||
|
||||
if(EnableRSIConsolidation)
|
||||
ProcessRSIConsolidation(rcoData, LOT_RCO_RSIConsolidation);
|
||||
ProcessRSIConsolidation(rcoData, g_Pos_RCO);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
ProcessSimpleTrendline(stBTCData, LOT_ST_BTCUSD);
|
||||
ProcessSimpleTrendline(stBTCData, g_Pos_ST_BTCUSD);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
ProcessSimpleTrendline(stXAUData, LOT_ST_XAUUSD);
|
||||
ProcessSimpleTrendline(stXAUData, g_Pos_ST_XAUUSD);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@@ -13,7 +13,6 @@
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#include "PerformanceEvaluator.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy Enable/Disable Switches |
|
||||
@@ -62,7 +61,11 @@ 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_TrailingStop = 370.0;
|
||||
input bool ES_UseTrailingStop = true;
|
||||
input double ES_TrailingActivationPips = 0.0;
|
||||
input bool ES_UseStaleStopLossExit = false;
|
||||
input int ES_StaleStopLossSeconds = 33800;
|
||||
input double ES_LotGröße = 0.03;
|
||||
input int ES_MagicNumber = 12350;
|
||||
input bool ES_UseSpreadAdjustment = true;
|
||||
@@ -71,6 +74,11 @@ input bool ES_UseBarData = true;
|
||||
input int ES_MaxTradesPerCrossover = 9;
|
||||
input int ES_ProfitCheckBars = 18;
|
||||
input bool ES_CloseUnprofitableTrades = true;
|
||||
input bool ES_UseWeeklyADXFilter = true;
|
||||
input int ES_WeeklyADXPeriod = 15;
|
||||
input double ES_WeeklyADXMin = 40.0;
|
||||
input int ES_WeeklyADXBarShift = 2;
|
||||
input bool ES_WeeklyADXUseDirection = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 3: RSICrossOverReversalXAUUSD |
|
||||
@@ -252,6 +260,44 @@ input double RS_XAUUSD_LotSize = 0.1;
|
||||
input int RS_XAUUSD_MagicNumber = 129102315;
|
||||
input int RS_XAUUSD_Slippage = 3;
|
||||
|
||||
input group "=== RSI Scalping Reversal escape (XAUUSD) ==="
|
||||
input bool RS_UseReversalEscape = false;
|
||||
input int RS_ReversalATRPeriod = 14;
|
||||
input double RS_ReversalAdverseAtrMult = 5.25;
|
||||
input int RS_ReversalSignsRequired = 2;
|
||||
input double RS_ReversalRsiVelocity = 16.0;
|
||||
input double RS_ReversalBodyAtrMult = 5.1;
|
||||
|
||||
input group "=== RSI Scalping APPL — Trailing ==="
|
||||
input bool RS_APPL_UseTrailingStop = true;
|
||||
input double RS_APPL_TrailDistancePoints = 120.0;
|
||||
input double RS_APPL_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping BTCUSD — Trailing ==="
|
||||
input bool RS_BTCUSD_UseTrailingStop = true;
|
||||
input double RS_BTCUSD_TrailDistancePoints = 120.0;
|
||||
input double RS_BTCUSD_TrailActivationPoints = 0.0;
|
||||
|
||||
input group "=== RSI Scalping MSFT — Trailing ==="
|
||||
input bool RS_MSFT_UseTrailingStop = true;
|
||||
input double RS_MSFT_TrailDistancePoints = 375.0;
|
||||
input double RS_MSFT_TrailActivationPoints = 75.0;
|
||||
|
||||
input group "=== RSI Scalping NVDA — Trailing ==="
|
||||
input bool RS_NVDA_UseTrailingStop = true;
|
||||
input double RS_NVDA_TrailDistancePoints = 375.0;
|
||||
input double RS_NVDA_TrailActivationPoints = 75.0;
|
||||
|
||||
input group "=== RSI Scalping TSLA — Trailing ==="
|
||||
input bool RS_TSLA_UseTrailingStop = true;
|
||||
input double RS_TSLA_TrailDistancePoints = 900.0;
|
||||
input double RS_TSLA_TrailActivationPoints = 950.0;
|
||||
|
||||
input group "=== RSI Scalping XAUUSD — Trailing ==="
|
||||
input bool RS_XAUUSD_UseTrailingStop = true;
|
||||
input double RS_XAUUSD_TrailDistancePoints = 71.0;
|
||||
input double RS_XAUUSD_TrailActivationPoints = 41.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -289,6 +335,7 @@ struct EMASlopeData {
|
||||
bool crossover_detected;
|
||||
datetime trade_open_time;
|
||||
datetime last_bar_time;
|
||||
datetime es_last_sl_adjust_success_time;
|
||||
};
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -371,19 +418,18 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables for Dynamic Lot Sizes |
|
||||
//| Global Variables for lot sizes (from inputs below) |
|
||||
//+------------------------------------------------------------------+
|
||||
// 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_MSFT_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
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)
|
||||
double g_DB_LotSize = 0.01;
|
||||
double g_ES_LotSize;
|
||||
double g_RC_LotSize;
|
||||
double g_RM_LotSize;
|
||||
double g_RS_APPL_LotSize;
|
||||
double g_RS_BTCUSD_LotSize;
|
||||
double g_RS_MSFT_LotSize;
|
||||
double g_RS_NVDA_LotSize;
|
||||
double g_RS_TSLA_LotSize;
|
||||
double g_RS_XAUUSD_LotSize;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
@@ -391,148 +437,52 @@ double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01)
|
||||
int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize Performance Evaluator
|
||||
InitPerformanceTracking();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
|
||||
g_ES_LotSize = ES_LotGröße;
|
||||
g_RC_LotSize = RC_lotSize;
|
||||
g_RM_LotSize = RM_InpLotSize;
|
||||
g_RS_APPL_LotSize = RS_APPL_LotSize;
|
||||
g_RS_BTCUSD_LotSize = RS_BTCUSD_LotSize;
|
||||
g_RS_MSFT_LotSize = RS_MSFT_LotSize;
|
||||
g_RS_NVDA_LotSize = RS_NVDA_LotSize;
|
||||
g_RS_TSLA_LotSize = RS_TSLA_LotSize;
|
||||
g_RS_XAUUSD_LotSize = RS_XAUUSD_LotSize;
|
||||
|
||||
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(EnableRSIScalpingMSFT)
|
||||
{
|
||||
InitRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price, RS_MSFT_MagicNumber, RS_MSFT_Slippage);
|
||||
RegisterStrategy("RSIScalpingMSFT", RS_MSFT_MagicNumber, RS_MSFT_LotSize, RS_MSFT_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_MSFT_Symbol);
|
||||
g_RS_MSFT_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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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: ",
|
||||
|
||||
Print("United EA (self-evaluate build) initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
(EnableEMASlopeDistance ? "EMASlope " : ""),
|
||||
(EnableRSICrossOverReversal ? "RSICrossOver " : ""),
|
||||
@@ -543,10 +493,7 @@ int OnInit()
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""));
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print(GetPerformanceSummary());
|
||||
|
||||
|
||||
return initResult;
|
||||
}
|
||||
|
||||
@@ -593,41 +540,6 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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);
|
||||
|
||||
@@ -643,32 +555,50 @@ void OnTick()
|
||||
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);
|
||||
RS_APPL_BarsToWait, g_RS_APPL_LotSize, RS_APPL_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_APPL_UseTrailingStop, RS_APPL_TrailDistancePoints, RS_APPL_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_BTCUSD_BarsToWait, g_RS_BTCUSD_LotSize, RS_BTCUSD_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_BTCUSD_UseTrailingStop, RS_BTCUSD_TrailDistancePoints, RS_BTCUSD_TrailActivationPoints);
|
||||
|
||||
if(EnableRSIScalpingMSFT)
|
||||
ProcessRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price,
|
||||
RS_MSFT_RSI_Overbought, RS_MSFT_RSI_Oversold, RS_MSFT_RSI_Target_Buy, RS_MSFT_RSI_Target_Sell,
|
||||
RS_MSFT_BarsToWait, g_RS_MSFT_LotSize, RS_MSFT_MagicNumber);
|
||||
RS_MSFT_BarsToWait, g_RS_MSFT_LotSize, RS_MSFT_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_MSFT_UseTrailingStop, RS_MSFT_TrailDistancePoints, RS_MSFT_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_NVDA_BarsToWait, g_RS_NVDA_LotSize, RS_NVDA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_NVDA_UseTrailingStop, RS_NVDA_TrailDistancePoints, RS_NVDA_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_TSLA_BarsToWait, g_RS_TSLA_LotSize, RS_TSLA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_TSLA_UseTrailingStop, RS_TSLA_TrailDistancePoints, RS_TSLA_TrailActivationPoints);
|
||||
|
||||
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);
|
||||
RS_XAUUSD_BarsToWait, g_RS_XAUUSD_LotSize, RS_XAUUSD_MagicNumber,
|
||||
RS_UseReversalEscape, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult,
|
||||
RS_XAUUSD_UseTrailingStop, RS_XAUUSD_TrailDistancePoints, RS_XAUUSD_TrailActivationPoints);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -1,607 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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", "MSFT", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -1,6 +1,12 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| DarvasBoxStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#if defined(CLUSTER0_ORCHESTRATOR) || defined(UNITED_V2_DYNAMIC_LOTS)
|
||||
extern double g_DB_LotSize;
|
||||
#define DARVAS_TRADE_LOT (g_DB_LotSize)
|
||||
#else
|
||||
#define DARVAS_TRADE_LOT 0.01
|
||||
#endif
|
||||
|
||||
bool InitDarvasBox(string symbol)
|
||||
{
|
||||
@@ -195,9 +201,9 @@ bool PlaceOrder(ENUM_ORDER_TYPE orderType, double price, double sl, double tp)
|
||||
// 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(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
result = dbData.trade.Buy(DARVAS_TRADE_LOT, dbData.symbol, 0, sl, tp, "Darvas Box Breakout");
|
||||
else
|
||||
result = dbData.trade.Sell(0.01, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
result = dbData.trade.Sell(DARVAS_TRADE_LOT, dbData.symbol, 0, sl, tp, "Darvas Box Breakdown");
|
||||
|
||||
// Always log errors, success only if logging enabled
|
||||
if(result)
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SimpleTrendlineStrategy.mqh |
|
||||
//+------------------------------------------------------------------+
|
||||
#ifndef SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
#define SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
|
||||
struct SimpleTrendlineModel
|
||||
{
|
||||
datetime t1;
|
||||
datetime t2;
|
||||
datetime t3;
|
||||
double a;
|
||||
double b;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
struct SimpleTrendlineData
|
||||
{
|
||||
string symbol;
|
||||
bool isInitialized;
|
||||
CTrade trade;
|
||||
ENUM_TIMEFRAMES signalTF;
|
||||
ENUM_TIMEFRAMES higherTF;
|
||||
int maPeriod;
|
||||
ENUM_MA_METHOD maMethod;
|
||||
ENUM_APPLIED_PRICE appliedPrice;
|
||||
int htfBarsToScan;
|
||||
double touchTolerancePoints;
|
||||
double breakBufferPoints;
|
||||
ulong magic;
|
||||
bool drawTrendline;
|
||||
int maHandle;
|
||||
datetime lastSignalBarTime;
|
||||
string lineName;
|
||||
};
|
||||
|
||||
double ST_NormalizeVolume(const string sym, double vol)
|
||||
{
|
||||
double minLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
|
||||
double maxLot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
|
||||
double step = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
|
||||
if(step > 0.0)
|
||||
vol = MathFloor(vol / step) * step;
|
||||
if(vol < minLot)
|
||||
vol = minLot;
|
||||
if(vol > maxLot)
|
||||
vol = maxLot;
|
||||
return vol;
|
||||
}
|
||||
|
||||
bool ST_GetPosition(const string sym, const ulong magic, ENUM_POSITION_TYPE &type, double &volume)
|
||||
{
|
||||
if(!PositionSelectByMagic(sym, magic))
|
||||
return false;
|
||||
type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
volume = PositionGetDouble(POSITION_VOLUME);
|
||||
return true;
|
||||
}
|
||||
|
||||
int ST_FindRecentCrossPoints(SimpleTrendlineData &d, datetime ×[], double &prices[])
|
||||
{
|
||||
ArrayResize(times, 0);
|
||||
ArrayResize(prices, 0);
|
||||
if(d.maHandle == INVALID_HANDLE)
|
||||
return 0;
|
||||
|
||||
int needBars = MathMax(d.htfBarsToScan, d.maPeriod + 20);
|
||||
MqlRates rates[];
|
||||
double maBuf[];
|
||||
ArraySetAsSeries(rates, true);
|
||||
ArraySetAsSeries(maBuf, true);
|
||||
|
||||
int copiedRates = CopyRates(d.symbol, d.higherTF, 0, needBars, rates);
|
||||
int copiedMa = CopyBuffer(d.maHandle, 0, 0, needBars, maBuf);
|
||||
if(copiedRates <= 5 || copiedMa <= 5)
|
||||
return 0;
|
||||
|
||||
int bars = MathMin(copiedRates, copiedMa);
|
||||
for(int i = 2; i < bars - 1; i++)
|
||||
{
|
||||
double d0 = rates[i].close - maBuf[i];
|
||||
double d1 = rates[i + 1].close - maBuf[i + 1];
|
||||
if(d0 == 0.0 || d1 == 0.0 || (d0 * d1 < 0.0))
|
||||
{
|
||||
int n = ArraySize(times);
|
||||
ArrayResize(times, n + 1);
|
||||
ArrayResize(prices, n + 1);
|
||||
times[n] = rates[i].time;
|
||||
prices[n] = rates[i].close;
|
||||
if(ArraySize(times) >= 3)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ArraySize(times);
|
||||
}
|
||||
|
||||
bool ST_BuildTrendline(SimpleTrendlineData &d, SimpleTrendlineModel &m)
|
||||
{
|
||||
m.valid = false;
|
||||
datetime ts[];
|
||||
double ps[];
|
||||
if(ST_FindRecentCrossPoints(d, ts, ps) < 3)
|
||||
return false;
|
||||
|
||||
datetime tOld[3];
|
||||
double pOld[3];
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
tOld[i] = ts[2 - i];
|
||||
pOld[i] = ps[2 - i];
|
||||
}
|
||||
|
||||
long t0 = (long)tOld[0];
|
||||
double x1 = 0.0;
|
||||
double x2 = (double)((long)tOld[1] - t0);
|
||||
double x3 = (double)((long)tOld[2] - t0);
|
||||
double y1 = pOld[0];
|
||||
double y2 = pOld[1];
|
||||
double y3 = pOld[2];
|
||||
|
||||
double sx = x1 + x2 + x3;
|
||||
double sy = y1 + y2 + y3;
|
||||
double sxx = x1 * x1 + x2 * x2 + x3 * x3;
|
||||
double sxy = x1 * y1 + x2 * y2 + x3 * y3;
|
||||
double den = 3.0 * sxx - sx * sx;
|
||||
if(MathAbs(den) < 1e-10)
|
||||
return false;
|
||||
|
||||
m.a = (3.0 * sxy - sx * sy) / den;
|
||||
m.b = (sy - m.a * sx) / 3.0;
|
||||
m.t1 = tOld[0];
|
||||
m.t2 = tOld[1];
|
||||
m.t3 = tOld[2];
|
||||
m.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
double ST_LinePriceAt(const SimpleTrendlineModel &m, const datetime t)
|
||||
{
|
||||
if(!m.valid)
|
||||
return 0.0;
|
||||
double x = (double)((long)t - (long)m.t1);
|
||||
return m.a * x + m.b;
|
||||
}
|
||||
|
||||
void ST_DrawTrendline(SimpleTrendlineData &d, const SimpleTrendlineModel &m)
|
||||
{
|
||||
if(!d.drawTrendline || !m.valid || d.symbol != _Symbol)
|
||||
return;
|
||||
|
||||
datetime tStart = m.t1;
|
||||
datetime tEnd = iTime(d.symbol, d.signalTF, 0);
|
||||
if(tEnd <= tStart)
|
||||
tEnd = m.t3 + PeriodSeconds(d.signalTF) * 20;
|
||||
|
||||
double pStart = ST_LinePriceAt(m, tStart);
|
||||
double pEnd = ST_LinePriceAt(m, tEnd);
|
||||
|
||||
if(ObjectFind(0, d.lineName) < 0)
|
||||
ObjectCreate(0, d.lineName, OBJ_TREND, 0, tStart, pStart, tEnd, pEnd);
|
||||
else
|
||||
{
|
||||
ObjectMove(0, d.lineName, 0, tStart, pStart);
|
||||
ObjectMove(0, d.lineName, 1, tEnd, pEnd);
|
||||
}
|
||||
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_RAY_RIGHT, true);
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_COLOR, clrGold);
|
||||
ObjectSetInteger(0, d.lineName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
|
||||
void ST_TryExitOnBreak(SimpleTrendlineData &d, const SimpleTrendlineModel &m)
|
||||
{
|
||||
ENUM_POSITION_TYPE posType;
|
||||
double vol;
|
||||
if(!ST_GetPosition(d.symbol, d.magic, posType, vol))
|
||||
return;
|
||||
|
||||
double close1 = iClose(d.symbol, d.signalTF, 1);
|
||||
datetime t1 = iTime(d.symbol, d.signalTF, 1);
|
||||
double line1 = ST_LinePriceAt(m, t1);
|
||||
double buf = d.breakBufferPoints * SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
|
||||
bool closePos = false;
|
||||
if(posType == POSITION_TYPE_BUY && close1 < (line1 - buf))
|
||||
closePos = true;
|
||||
if(posType == POSITION_TYPE_SELL && close1 > (line1 + buf))
|
||||
closePos = true;
|
||||
|
||||
if(closePos)
|
||||
ClosePositionByMagic(d.trade, d.symbol, d.magic);
|
||||
}
|
||||
|
||||
void ST_TryPullbackEntry(SimpleTrendlineData &d, const SimpleTrendlineModel &m, const double lots)
|
||||
{
|
||||
if(PositionExistsByMagic(d.symbol, d.magic))
|
||||
return;
|
||||
|
||||
MqlRates b1[], b2[];
|
||||
ArraySetAsSeries(b1, true);
|
||||
ArraySetAsSeries(b2, true);
|
||||
if(CopyRates(d.symbol, d.signalTF, 1, 1, b1) != 1)
|
||||
return;
|
||||
if(CopyRates(d.symbol, d.signalTF, 2, 1, b2) != 1)
|
||||
return;
|
||||
if(ArraySize(b1) < 1 || ArraySize(b2) < 1)
|
||||
return;
|
||||
|
||||
double line1 = ST_LinePriceAt(m, b1[0].time);
|
||||
double tol = d.touchTolerancePoints * SymbolInfoDouble(d.symbol, SYMBOL_POINT);
|
||||
bool upTrend = (m.a > 0.0);
|
||||
bool downTrend = (m.a < 0.0);
|
||||
double vol = ST_NormalizeVolume(d.symbol, lots);
|
||||
|
||||
if(upTrend)
|
||||
{
|
||||
bool touched = (b1[0].low <= (line1 + tol));
|
||||
bool reclaim = (b1[0].close > line1);
|
||||
bool bullish = (b1[0].close > b1[0].open);
|
||||
bool stillHealthy = (b2[0].close >= ST_LinePriceAt(m, b2[0].time) - tol);
|
||||
if(touched && reclaim && bullish && stillHealthy)
|
||||
{
|
||||
if(!d.trade.Buy(vol, d.symbol, 0.0, 0.0, 0.0, "SimpleTrendline BUY"))
|
||||
Print("SimpleTrendline BUY failed [", d.symbol, "] retcode=", d.trade.ResultRetcode(), " ", d.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
else if(downTrend)
|
||||
{
|
||||
bool touched = (b1[0].high >= (line1 - tol));
|
||||
bool reject = (b1[0].close < line1);
|
||||
bool bearish = (b1[0].close < b1[0].open);
|
||||
bool stillWeak = (b2[0].close <= ST_LinePriceAt(m, b2[0].time) + tol);
|
||||
if(touched && reject && bearish && stillWeak)
|
||||
{
|
||||
if(!d.trade.Sell(vol, d.symbol, 0.0, 0.0, 0.0, "SimpleTrendline SELL"))
|
||||
Print("SimpleTrendline SELL failed [", d.symbol, "] retcode=", d.trade.ResultRetcode(), " ", d.trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool InitSimpleTrendline(SimpleTrendlineData &d,
|
||||
const string symbol,
|
||||
const ENUM_TIMEFRAMES signalTF,
|
||||
const ENUM_TIMEFRAMES higherTF,
|
||||
const int maPeriod,
|
||||
const ENUM_MA_METHOD maMethod,
|
||||
const ENUM_APPLIED_PRICE appliedPrice,
|
||||
const int htfBarsToScan,
|
||||
const double touchTolerancePoints,
|
||||
const double breakBufferPoints,
|
||||
const ulong magic,
|
||||
const bool drawTrendline)
|
||||
{
|
||||
d.isInitialized = false;
|
||||
d.symbol = symbol;
|
||||
StringTrimLeft(d.symbol);
|
||||
StringTrimRight(d.symbol);
|
||||
if(StringLen(d.symbol) == 0)
|
||||
d.symbol = _Symbol;
|
||||
|
||||
if(!SymbolSelect(d.symbol, true))
|
||||
return false;
|
||||
|
||||
d.signalTF = signalTF;
|
||||
d.higherTF = higherTF;
|
||||
d.maPeriod = maPeriod;
|
||||
d.maMethod = maMethod;
|
||||
d.appliedPrice = appliedPrice;
|
||||
d.htfBarsToScan = htfBarsToScan;
|
||||
d.touchTolerancePoints = touchTolerancePoints;
|
||||
d.breakBufferPoints = breakBufferPoints;
|
||||
d.magic = magic;
|
||||
d.drawTrendline = drawTrendline;
|
||||
d.lastSignalBarTime = 0;
|
||||
d.lineName = "SimpleTrendline_" + d.symbol + "_" + IntegerToString((int)d.magic);
|
||||
|
||||
d.trade.SetExpertMagicNumber((long)d.magic);
|
||||
d.trade.SetTypeFillingBySymbol(d.symbol);
|
||||
d.trade.SetDeviationInPoints(20);
|
||||
|
||||
d.maHandle = iMA(d.symbol, d.higherTF, d.maPeriod, 0, d.maMethod, d.appliedPrice);
|
||||
if(d.maHandle == INVALID_HANDLE)
|
||||
return false;
|
||||
|
||||
d.isInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeinitSimpleTrendline(SimpleTrendlineData &d)
|
||||
{
|
||||
if(d.maHandle != INVALID_HANDLE)
|
||||
IndicatorRelease(d.maHandle);
|
||||
d.maHandle = INVALID_HANDLE;
|
||||
if(ObjectFind(0, d.lineName) >= 0)
|
||||
ObjectDelete(0, d.lineName);
|
||||
d.isInitialized = false;
|
||||
}
|
||||
|
||||
void ProcessSimpleTrendline(SimpleTrendlineData &d, const double lots)
|
||||
{
|
||||
if(!d.isInitialized)
|
||||
return;
|
||||
|
||||
datetime bar0 = iTime(d.symbol, d.signalTF, 0);
|
||||
if(bar0 == 0 || bar0 == d.lastSignalBarTime)
|
||||
return;
|
||||
d.lastSignalBarTime = bar0;
|
||||
|
||||
SimpleTrendlineModel m;
|
||||
if(!ST_BuildTrendline(d, m))
|
||||
return;
|
||||
|
||||
ST_DrawTrendline(d, m);
|
||||
ST_TryExitOnBreak(d, m);
|
||||
ST_TryPullbackEntry(d, m, lots);
|
||||
}
|
||||
|
||||
#endif // SIMPLE_TRENDLINE_STRATEGY_MQH
|
||||
@@ -5,14 +5,17 @@
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#property version "1.10"
|
||||
#property strict
|
||||
#property description "LOT_* nominal at ORCH_ReferenceBalance; scale = balance/equity ÷ reference (clamped). No performance-evaluator ranking."
|
||||
|
||||
#include <Trade\Trade.mqh>
|
||||
#include <Trade\PositionInfo.mqh>
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#define UNITED_V2_DYNAMIC_LOTS
|
||||
double g_DB_LotSize;
|
||||
// Include strategy implementations early so structs are available
|
||||
#include "Strategies/DarvasBoxStrategy.mqh"
|
||||
#include "Strategies/EMASlopeDistanceStrategy.mqh"
|
||||
@@ -22,6 +25,7 @@
|
||||
#include "Strategies/SuperEMAStrategy.mqh"
|
||||
#include "Strategies/RSIReversalAsianStrategy.mqh"
|
||||
#include "Strategies/RSIConsolidationStrategy.mqh"
|
||||
#include "Strategies/SimpleTrendlineStrategy.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Lot Size Variables (for dynamic lot sizing) |
|
||||
@@ -30,6 +34,18 @@ double g_ES_LotSize; // EMA Slope Distance lot size
|
||||
double g_RC_LotSize; // RSI CrossOver Reversal lot size
|
||||
double g_RM_LotSize; // RSI MidPoint Hijack lot size
|
||||
|
||||
double g_Pos_RS_APPL;
|
||||
double g_Pos_RS_BTCUSD;
|
||||
double g_Pos_RS_NVDA;
|
||||
double g_Pos_RS_TSLA;
|
||||
double g_Pos_RS_XAUUSD;
|
||||
double g_Pos_RRA_EURUSD;
|
||||
double g_Pos_RRA_AUDUSD;
|
||||
double g_Pos_SE;
|
||||
double g_Pos_RCO;
|
||||
double g_Pos_ST_BTCUSD;
|
||||
double g_Pos_ST_XAUUSD;
|
||||
|
||||
bool United_MayOpenNewEntry(const string symbol, const ulong magic, const bool isBuy)
|
||||
{
|
||||
if(PositionExistsByMagic(symbol, magic))
|
||||
@@ -54,8 +70,11 @@ input bool EnableSuperEMA = true;
|
||||
input bool EnableRSIConsolidation = true;
|
||||
input bool EnableRSIReversalAsianEURUSD = true;
|
||||
input bool EnableRSIReversalAsianAUDUSD = true;
|
||||
input bool EnableSimpleTrendlineBTCUSD = true;
|
||||
input bool EnableSimpleTrendlineXAUUSD = true;
|
||||
|
||||
input group "=== Centralized Lot Size (Granular Per Robot) ==="
|
||||
input double LOT_DB_DarvasBox = 0.01;
|
||||
input double LOT_ES_EMASlopeDistance = 0.05;
|
||||
input double LOT_RC_RSICrossOver = 0.1;
|
||||
input double LOT_RM_RSIMidPointHijack = 0.01;
|
||||
@@ -68,6 +87,15 @@ input double LOT_RRA_EURUSD = 0.01;
|
||||
input double LOT_RRA_AUDUSD = 0.10;
|
||||
input double LOT_SE_SuperEMA = 0.01;
|
||||
input double LOT_RCO_RSIConsolidation = 0.04;
|
||||
input double LOT_ST_BTCUSD = 0.19;
|
||||
input double LOT_ST_XAUUSD = 0.02;
|
||||
|
||||
input group "=== Balance-based position sizing ==="
|
||||
input bool ORCH_ScaleLotsByBalance = true;
|
||||
input bool ORCH_UseEquityInsteadOfBalance = false;
|
||||
input double ORCH_ReferenceBalance = 10000.0;
|
||||
input double ORCH_MinBalanceScale = 0.1;
|
||||
input double ORCH_MaxBalanceScale = 10.0;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy 1: DarvasBoxXAUUSD |
|
||||
@@ -385,6 +413,75 @@ input ulong RCO_MagicNumber = 20250420;
|
||||
input int RCO_Slippage = 10;
|
||||
input int RCO_MaxSpreadPoints = 28;
|
||||
|
||||
input group "=== SimpleTrendline BTCUSD ==="
|
||||
input string ST_BTC_Symbol = "BTCUSD";
|
||||
input ENUM_TIMEFRAMES ST_BTC_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_BTC_HigherTF = PERIOD_H4;
|
||||
input int ST_BTC_MAPeriod = 150;
|
||||
input ENUM_MA_METHOD ST_BTC_MAMethod = MODE_SMMA;
|
||||
input ENUM_APPLIED_PRICE ST_BTC_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_BTC_HTFBarsToScan = 1200;
|
||||
input double ST_BTC_LineTouchTolerance = 170.0;
|
||||
input double ST_BTC_BreakBuffer = 90.0;
|
||||
input ulong ST_BTC_MagicNumber = 26042501;
|
||||
input bool ST_BTC_DrawTrendline = true;
|
||||
|
||||
input group "=== SimpleTrendline XAUUSD ==="
|
||||
input string ST_XAU_Symbol = "XAUUSD";
|
||||
input ENUM_TIMEFRAMES ST_XAU_SignalTF = PERIOD_H1;
|
||||
input ENUM_TIMEFRAMES ST_XAU_HigherTF = PERIOD_M10;
|
||||
input int ST_XAU_MAPeriod = 65;
|
||||
input ENUM_MA_METHOD ST_XAU_MAMethod = MODE_EMA;
|
||||
input ENUM_APPLIED_PRICE ST_XAU_AppliedPrice = PRICE_OPEN;
|
||||
input int ST_XAU_HTFBarsToScan = 500;
|
||||
input double ST_XAU_LineTouchTolerance = 220.0;
|
||||
input double ST_XAU_BreakBuffer = 110.0;
|
||||
input ulong ST_XAU_MagicNumber = 26042503;
|
||||
input bool ST_XAU_DrawTrendline = true;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Balance scaling: LOT_* = nominal size at ORCH_ReferenceBalance |
|
||||
//+------------------------------------------------------------------+
|
||||
double United_BalanceScaleFactor()
|
||||
{
|
||||
if(!ORCH_ScaleLotsByBalance || ORCH_ReferenceBalance <= 0.0)
|
||||
return 1.0;
|
||||
const double money = ORCH_UseEquityInsteadOfBalance
|
||||
? AccountInfoDouble(ACCOUNT_EQUITY)
|
||||
: AccountInfoDouble(ACCOUNT_BALANCE);
|
||||
double raw = money / ORCH_ReferenceBalance;
|
||||
if(raw < ORCH_MinBalanceScale)
|
||||
raw = ORCH_MinBalanceScale;
|
||||
if(raw > ORCH_MaxBalanceScale)
|
||||
raw = ORCH_MaxBalanceScale;
|
||||
return raw;
|
||||
}
|
||||
|
||||
double United_ScaledLot(const double baseLot)
|
||||
{
|
||||
const double lot = baseLot * United_BalanceScaleFactor();
|
||||
return (lot > 0.0 ? lot : 0.0);
|
||||
}
|
||||
|
||||
void United_RefreshScaledLots()
|
||||
{
|
||||
g_DB_LotSize = United_ScaledLot(LOT_DB_DarvasBox);
|
||||
g_ES_LotSize = United_ScaledLot(LOT_ES_EMASlopeDistance);
|
||||
g_RC_LotSize = United_ScaledLot(LOT_RC_RSICrossOver);
|
||||
g_RM_LotSize = United_ScaledLot(LOT_RM_RSIMidPointHijack);
|
||||
g_Pos_RS_APPL = United_ScaledLot(LOT_RS_APPL);
|
||||
g_Pos_RS_BTCUSD = United_ScaledLot(LOT_RS_BTCUSD);
|
||||
g_Pos_RS_NVDA = United_ScaledLot(LOT_RS_NVDA);
|
||||
g_Pos_RS_TSLA = United_ScaledLot(LOT_RS_TSLA);
|
||||
g_Pos_RS_XAUUSD = United_ScaledLot(LOT_RS_XAUUSD);
|
||||
g_Pos_RRA_EURUSD = United_ScaledLot(LOT_RRA_EURUSD);
|
||||
g_Pos_RRA_AUDUSD = United_ScaledLot(LOT_RRA_AUDUSD);
|
||||
g_Pos_SE = United_ScaledLot(LOT_SE_SuperEMA);
|
||||
g_Pos_RCO = United_ScaledLot(LOT_RCO_RSIConsolidation);
|
||||
g_Pos_ST_BTCUSD = United_ScaledLot(LOT_ST_BTCUSD);
|
||||
g_Pos_ST_XAUUSD = United_ScaledLot(LOT_ST_XAUUSD);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - DarvasBox |
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -483,6 +580,8 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
SuperEMAData seData;
|
||||
RSIConsolidationData rcoData;
|
||||
SimpleTrendlineData stBTCData;
|
||||
SimpleTrendlineData stXAUData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables - RSI Reversal Asian |
|
||||
@@ -497,10 +596,7 @@ int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize global lot size variables
|
||||
g_ES_LotSize = LOT_ES_EMASlopeDistance;
|
||||
g_RC_LotSize = LOT_RC_RSICrossOver;
|
||||
g_RM_LotSize = LOT_RM_RSIMidPointHijack;
|
||||
United_RefreshScaledLots();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
if(EnableDarvasBox)
|
||||
@@ -570,6 +666,18 @@ int OnInit()
|
||||
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, "'");
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
if(!InitSimpleTrendline(stBTCData, ST_BTC_Symbol, ST_BTC_SignalTF, ST_BTC_HigherTF, ST_BTC_MAPeriod,
|
||||
ST_BTC_MAMethod, ST_BTC_AppliedPrice, ST_BTC_HTFBarsToScan,
|
||||
ST_BTC_LineTouchTolerance, ST_BTC_BreakBuffer, ST_BTC_MagicNumber, ST_BTC_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineBTCUSD failed to initialize for symbol '", ST_BTC_Symbol, "'");
|
||||
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
if(!InitSimpleTrendline(stXAUData, ST_XAU_Symbol, ST_XAU_SignalTF, ST_XAU_HigherTF, ST_XAU_MAPeriod,
|
||||
ST_XAU_MAMethod, ST_XAU_AppliedPrice, ST_XAU_HTFBarsToScan,
|
||||
ST_XAU_LineTouchTolerance, ST_XAU_BreakBuffer, ST_XAU_MagicNumber, ST_XAU_DrawTrendline))
|
||||
Print("Warning: SimpleTrendlineXAUUSD failed to initialize for symbol '", ST_XAU_Symbol, "'");
|
||||
|
||||
Print("United EA initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
@@ -584,7 +692,9 @@ int OnInit()
|
||||
(EnableSuperEMA ? "SuperEMA " : ""),
|
||||
(EnableRSIConsolidation ? "RSIConsolidation " : ""),
|
||||
(EnableRSIReversalAsianEURUSD ? "RSIReversalAsianEURUSD " : ""),
|
||||
(EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : ""));
|
||||
(EnableRSIReversalAsianAUDUSD ? "RSIReversalAsianAUDUSD " : ""),
|
||||
(EnableSimpleTrendlineBTCUSD ? "SimpleTrendlineBTCUSD " : ""),
|
||||
(EnableSimpleTrendlineXAUUSD ? "SimpleTrendlineXAUUSD " : ""));
|
||||
|
||||
return initResult;
|
||||
}
|
||||
@@ -632,6 +742,11 @@ void OnDeinit(const int reason)
|
||||
|
||||
if(EnableRSIReversalAsianAUDUSD)
|
||||
DeinitRSIReversalAsian(rraAUDUSDData);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
DeinitSimpleTrendline(stBTCData);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
DeinitSimpleTrendline(stXAUData);
|
||||
|
||||
Print("United EA deinitialized. Reason: ", reason);
|
||||
}
|
||||
@@ -641,6 +756,8 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
United_RefreshScaledLots();
|
||||
|
||||
if(EnableDarvasBox)
|
||||
ProcessDarvasBox(DB_Symbol);
|
||||
|
||||
@@ -656,49 +773,54 @@ void OnTick()
|
||||
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, LOT_RS_APPL, RS_APPL_MagicNumber,
|
||||
RS_APPL_BarsToWait, g_Pos_RS_APPL, RS_APPL_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
|
||||
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, LOT_RS_BTCUSD, RS_BTCUSD_MagicNumber,
|
||||
RS_BTCUSD_BarsToWait, g_Pos_RS_BTCUSD, RS_BTCUSD_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
|
||||
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, LOT_RS_NVDA, RS_NVDA_MagicNumber,
|
||||
RS_NVDA_BarsToWait, g_Pos_RS_NVDA, RS_NVDA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
|
||||
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, LOT_RS_TSLA, RS_TSLA_MagicNumber,
|
||||
RS_TSLA_BarsToWait, g_Pos_RS_TSLA, RS_TSLA_MagicNumber,
|
||||
false, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
|
||||
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, LOT_RS_XAUUSD, RS_XAUUSD_MagicNumber,
|
||||
RS_XAUUSD_BarsToWait, g_Pos_RS_XAUUSD, RS_XAUUSD_MagicNumber,
|
||||
RS_UseReversalEscape, RS_ReversalATRPeriod, RS_ReversalAdverseAtrMult, RS_ReversalSignsRequired,
|
||||
RS_ReversalRsiVelocity, RS_ReversalBodyAtrMult);
|
||||
|
||||
if(EnableRSIReversalAsianEURUSD)
|
||||
ProcessRSIReversalAsian(rraEURUSDData, LOT_RRA_EURUSD);
|
||||
ProcessRSIReversalAsian(rraEURUSDData, g_Pos_RRA_EURUSD);
|
||||
|
||||
if(EnableRSIReversalAsianAUDUSD)
|
||||
ProcessRSIReversalAsian(rraAUDUSDData, LOT_RRA_AUDUSD);
|
||||
ProcessRSIReversalAsian(rraAUDUSDData, g_Pos_RRA_AUDUSD);
|
||||
|
||||
if(EnableSuperEMA)
|
||||
ProcessSuperEMA(seData, LOT_SE_SuperEMA);
|
||||
ProcessSuperEMA(seData, g_Pos_SE);
|
||||
|
||||
if(EnableRSIConsolidation)
|
||||
ProcessRSIConsolidation(rcoData, LOT_RCO_RSIConsolidation);
|
||||
ProcessRSIConsolidation(rcoData, g_Pos_RCO);
|
||||
|
||||
if(EnableSimpleTrendlineBTCUSD)
|
||||
ProcessSimpleTrendline(stBTCData, g_Pos_ST_BTCUSD);
|
||||
if(EnableSimpleTrendlineXAUUSD)
|
||||
ProcessSimpleTrendline(stXAUData, g_Pos_ST_XAUUSD);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#include "PerformanceEvaluator.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy Enable/Disable Switches |
|
||||
@@ -371,19 +370,18 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables for Dynamic Lot Sizes |
|
||||
//| Global Variables for lot sizes (from inputs below) |
|
||||
//+------------------------------------------------------------------+
|
||||
// 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_MSFT_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
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)
|
||||
double g_DB_LotSize = 0.01;
|
||||
double g_ES_LotSize;
|
||||
double g_RC_LotSize;
|
||||
double g_RM_LotSize;
|
||||
double g_RS_APPL_LotSize;
|
||||
double g_RS_BTCUSD_LotSize;
|
||||
double g_RS_MSFT_LotSize;
|
||||
double g_RS_NVDA_LotSize;
|
||||
double g_RS_TSLA_LotSize;
|
||||
double g_RS_XAUUSD_LotSize;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
@@ -391,148 +389,52 @@ double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01)
|
||||
int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize Performance Evaluator
|
||||
InitPerformanceTracking();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
|
||||
g_ES_LotSize = ES_LotGröße;
|
||||
g_RC_LotSize = RC_lotSize;
|
||||
g_RM_LotSize = RM_InpLotSize;
|
||||
g_RS_APPL_LotSize = RS_APPL_LotSize;
|
||||
g_RS_BTCUSD_LotSize = RS_BTCUSD_LotSize;
|
||||
g_RS_MSFT_LotSize = RS_MSFT_LotSize;
|
||||
g_RS_NVDA_LotSize = RS_NVDA_LotSize;
|
||||
g_RS_TSLA_LotSize = RS_TSLA_LotSize;
|
||||
g_RS_XAUUSD_LotSize = RS_XAUUSD_LotSize;
|
||||
|
||||
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(EnableRSIScalpingMSFT)
|
||||
{
|
||||
InitRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price, RS_MSFT_MagicNumber, RS_MSFT_Slippage);
|
||||
RegisterStrategy("RSIScalpingMSFT", RS_MSFT_MagicNumber, RS_MSFT_LotSize, RS_MSFT_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_MSFT_Symbol);
|
||||
g_RS_MSFT_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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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: ",
|
||||
|
||||
Print("United EA (self-evaluate build) initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
(EnableEMASlopeDistance ? "EMASlope " : ""),
|
||||
(EnableRSICrossOverReversal ? "RSICrossOver " : ""),
|
||||
@@ -543,10 +445,7 @@ int OnInit()
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""));
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print(GetPerformanceSummary());
|
||||
|
||||
|
||||
return initResult;
|
||||
}
|
||||
|
||||
@@ -593,41 +492,6 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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);
|
||||
|
||||
|
||||
@@ -1,607 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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", "MSFT", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -1,607 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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", "MSFT", "NVDA", "TSLA", "GOOGL", "AMZN", "META", "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;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <Indicators\Trend.mqh>
|
||||
#include <Indicators\Volumes.mqh>
|
||||
#include "MagicNumberHelpers.mqh"
|
||||
#include "PerformanceEvaluator.mqh"
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Strategy Enable/Disable Switches |
|
||||
@@ -371,19 +370,18 @@ RSIScalpingData rsTSLAData;
|
||||
RSIScalpingData rsXAUUSDData;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Global Variables for Dynamic Lot Sizes |
|
||||
//| Global Variables for lot sizes (from inputs below) |
|
||||
//+------------------------------------------------------------------+
|
||||
// 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_MSFT_LotSize = 5.0; // Stock - start with stock minimum (5.0)
|
||||
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)
|
||||
double g_DB_LotSize = 0.01;
|
||||
double g_ES_LotSize;
|
||||
double g_RC_LotSize;
|
||||
double g_RM_LotSize;
|
||||
double g_RS_APPL_LotSize;
|
||||
double g_RS_BTCUSD_LotSize;
|
||||
double g_RS_MSFT_LotSize;
|
||||
double g_RS_NVDA_LotSize;
|
||||
double g_RS_TSLA_LotSize;
|
||||
double g_RS_XAUUSD_LotSize;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
@@ -391,148 +389,52 @@ double g_RS_XAUUSD_LotSize = 0.01; // Forex - start with forex minimum (0.01)
|
||||
int OnInit()
|
||||
{
|
||||
int initResult = INIT_SUCCEEDED;
|
||||
|
||||
// Initialize Performance Evaluator
|
||||
InitPerformanceTracking();
|
||||
|
||||
// Initialize strategies - log warnings but don't fail entire EA if symbol unavailable
|
||||
|
||||
g_ES_LotSize = ES_LotGröße;
|
||||
g_RC_LotSize = RC_lotSize;
|
||||
g_RM_LotSize = RM_InpLotSize;
|
||||
g_RS_APPL_LotSize = RS_APPL_LotSize;
|
||||
g_RS_BTCUSD_LotSize = RS_BTCUSD_LotSize;
|
||||
g_RS_MSFT_LotSize = RS_MSFT_LotSize;
|
||||
g_RS_NVDA_LotSize = RS_NVDA_LotSize;
|
||||
g_RS_TSLA_LotSize = RS_TSLA_LotSize;
|
||||
g_RS_XAUUSD_LotSize = RS_XAUUSD_LotSize;
|
||||
|
||||
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(EnableRSIScalpingMSFT)
|
||||
{
|
||||
InitRSIScalping(rsMSFTData, RS_MSFT_Symbol, RS_MSFT_TimeFrame, RS_MSFT_RSI_Period, RS_MSFT_RSI_Applied_Price, RS_MSFT_MagicNumber, RS_MSFT_Slippage);
|
||||
RegisterStrategy("RSIScalpingMSFT", RS_MSFT_MagicNumber, RS_MSFT_LotSize, RS_MSFT_Symbol);
|
||||
// Start with minimum lot size (will be adjusted by performance evaluator)
|
||||
double minLot = GetMinLotSizeForSymbol(RS_MSFT_Symbol);
|
||||
g_RS_MSFT_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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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: ",
|
||||
|
||||
Print("United EA (self-evaluate build) initialized. Active strategies: ",
|
||||
(EnableDarvasBox ? "DarvasBox " : ""),
|
||||
(EnableEMASlopeDistance ? "EMASlope " : ""),
|
||||
(EnableRSICrossOverReversal ? "RSICrossOver " : ""),
|
||||
@@ -543,10 +445,7 @@ int OnInit()
|
||||
(EnableRSIScalpingNVDA ? "RSIScalpingNVDA " : ""),
|
||||
(EnableRSIScalpingTSLA ? "RSIScalpingTSLA " : ""),
|
||||
(EnableRSIScalpingXAUUSD ? "RSIScalpingXAUUSD " : ""));
|
||||
|
||||
if(PE_EnableLogging)
|
||||
Print(GetPerformanceSummary());
|
||||
|
||||
|
||||
return initResult;
|
||||
}
|
||||
|
||||
@@ -593,41 +492,6 @@ void OnDeinit(const int reason)
|
||||
//+------------------------------------------------------------------+
|
||||
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("RSIScalpingMSFT", RS_MSFT_MagicNumber);
|
||||
if(adjustedLot > 0) g_RS_MSFT_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);
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
; saved on 2026.05.01
|
||||
; Genetic optimization — EMASlopeDistanceCocktailXAUUSD (main.mq5)
|
||||
; Baseline aligned with Desktop 123.set; Strategy Tester -> Inputs -> Load
|
||||
;
|
||||
; Format: Parameter=Start||Step||Min||Max||Optimize(Y/N)
|
||||
;
|
||||
|
||||
; === Session / identity (usually fixed) ===
|
||||
MagicNumber=12350||0||12350||12350||N
|
||||
Timeframe=16385||0||16385||16385||N
|
||||
|
||||
; === Core signal thresholds (123.set starts) ===
|
||||
EMA_Periode=46||2||10||120||Y
|
||||
PreisSchwelle=600.0||25.0||200.0||1200.0||Y
|
||||
SteigungSchwelle=80.0||5.0||15.0||200.0||Y
|
||||
ÜberwachungTimeout=800||20||120||2400||Y
|
||||
|
||||
; === Trade management ===
|
||||
TrailingStop=260.0||50.0||5.0||500.0||Y
|
||||
LotGröße=0.03||0.01||0.01||0.30||N
|
||||
|
||||
; === Behaviour toggles (fixed unless you want regime tests) ===
|
||||
UseSpreadAdjustment=true||false||0||true||N
|
||||
UseBarData=true||false||0||true||N
|
||||
CloseUnprofitableTrades=true||false||0||true||N
|
||||
|
||||
; === Crossover / profit logic (123.set flags) ===
|
||||
MaxTradesPerCrossover=9||1||1||15||Y
|
||||
ProfitCheckBars=12||1||1||18||Y
|
||||
|
||||
; === Weekly ADX filter ===
|
||||
UseWeeklyADXFilter=true||false||0||true||N
|
||||
WeeklyADXPeriod=15||1||7||28||Y
|
||||
WeeklyADXMin=40.0||2.0||15.0||55.0||Y
|
||||
WeeklyADXBarShift=2||1||1||5||N
|
||||
WeeklyADXUseDirection=true||false||0||true||N
|
||||
@@ -0,0 +1,589 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMACrossOver.mq5 |
|
||||
//| Copyright 2025, MetaQuotes Ltd. |
|
||||
//| https://www.mql5.com |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Copyright 2025, MetaQuotes Ltd."
|
||||
#property link "https://www.mql5.com"
|
||||
#property version "1.00"
|
||||
#include <Trade\Trade.mqh>
|
||||
#include "../_united/MagicNumberHelpers.mqh"
|
||||
//--- Eingabeparameter (Input Parameters) - Optimized Profitable Parameters
|
||||
input int EMA_Periode = 50; // EMA Periode
|
||||
input double PreisSchwelle = 700.0; // Preisbewegung Schwelle in Pips
|
||||
input double SteigungSchwelle = 25.0; // EMA Steigung Schwelle in Pips
|
||||
input int ÜberwachungTimeout = 340; // Überwachungszeit in Sekunden
|
||||
input double TrailingStop = 370.0; // Gleitender Stop in Pips
|
||||
input double LotGröße = 0.07; // Handelsvolumen
|
||||
input int MagicNumber = 135790; // Magic Number für Trades
|
||||
input bool UseSpreadAdjustment = true; // Spread-Anpassung verwenden
|
||||
input ENUM_TIMEFRAMES Timeframe = PERIOD_H1; // Zeitraum für Analyse
|
||||
input bool UseBarData = true; // Bar-Daten statt Tick-Daten verwenden
|
||||
input int MaxTradesPerCrossover = 10; // Maximale Trades pro Crossover-Ereignis
|
||||
input int ProfitCheckBars = 15; // Bars bis zur Profit-Prüfung
|
||||
input bool CloseUnprofitableTrades = true; // Unprofitable Trades nach X Bars schließen
|
||||
input bool UseWeeklyADXFilter = true; // W1 ADX Trendfilter aktivieren
|
||||
input int WeeklyADXPeriod = 15; // ADX-Periode auf W1
|
||||
input double WeeklyADXMin = 40.0; // Minimaler ADX fuer Trendfreigabe
|
||||
input int WeeklyADXBarShift = 2; // 1=letzte geschlossene W1-Kerze
|
||||
input bool WeeklyADXUseDirection = true; // +DI/-DI Richtung mitpruefen
|
||||
|
||||
//--- Globale Variablen (Global Variables)
|
||||
int ema_handle; // EMA Indicator Handle
|
||||
double ema_array[]; // Array für EMA
|
||||
datetime letzte_überwachung_zeit; // Zeit der letzten Überwachung
|
||||
bool überwachung_aktiv = false; // Überwachungsstatus
|
||||
bool preis_trigger_aktiv = false; // Preis-Trigger Status
|
||||
bool steigung_trigger_aktiv = false; // Steigungs-Trigger Status
|
||||
int ticket = 0; // Trade Ticket
|
||||
CTrade trade; // CTrade Objekt
|
||||
int trades_in_current_crossover = 0; // Anzahl Trades im aktuellen Crossover
|
||||
bool crossover_detected = false; // Crossover erkannt
|
||||
datetime trade_open_time = 0; // Zeitpunkt des Trade-Öffnens
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Weekly ADX trend filter |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsWeeklyADXTrendFavorable(ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
if(!UseWeeklyADXFilter)
|
||||
return true;
|
||||
|
||||
int adxShift = WeeklyADXBarShift;
|
||||
if(adxShift < 0)
|
||||
adxShift = 0;
|
||||
|
||||
int adx_handle = iADX(_Symbol, PERIOD_W1, WeeklyADXPeriod);
|
||||
if(adx_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("TRACE: Weekly ADX Handle ungültig - Filter blockiert Entry");
|
||||
return false;
|
||||
}
|
||||
|
||||
double adx_buf[], plus_di_buf[], minus_di_buf[];
|
||||
ArraySetAsSeries(adx_buf, true);
|
||||
ArraySetAsSeries(plus_di_buf, true);
|
||||
ArraySetAsSeries(minus_di_buf, true);
|
||||
|
||||
bool ok_adx = (CopyBuffer(adx_handle, 0, adxShift, 1, adx_buf) > 0);
|
||||
bool ok_plus = (CopyBuffer(adx_handle, 1, adxShift, 1, plus_di_buf) > 0);
|
||||
bool ok_minus = (CopyBuffer(adx_handle, 2, adxShift, 1, minus_di_buf) > 0);
|
||||
IndicatorRelease(adx_handle);
|
||||
|
||||
if(!ok_adx || !ok_plus || !ok_minus)
|
||||
{
|
||||
Print("TRACE: Weekly ADX Daten nicht verfügbar - Filter blockiert Entry");
|
||||
return false;
|
||||
}
|
||||
|
||||
double adx_value = adx_buf[0];
|
||||
double plus_di = plus_di_buf[0];
|
||||
double minus_di = minus_di_buf[0];
|
||||
|
||||
bool strength_ok = (adx_value >= WeeklyADXMin);
|
||||
bool direction_ok = true;
|
||||
if(WeeklyADXUseDirection)
|
||||
{
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
direction_ok = (plus_di > minus_di);
|
||||
else
|
||||
direction_ok = (minus_di > plus_di);
|
||||
}
|
||||
|
||||
Print("TRACE: Weekly ADX Filter | ADX=", DoubleToString(adx_value, 2),
|
||||
" +DI=", DoubleToString(plus_di, 2),
|
||||
" -DI=", DoubleToString(minus_di, 2),
|
||||
" strength_ok=", strength_ok,
|
||||
" direction_ok=", direction_ok);
|
||||
|
||||
return (strength_ok && direction_ok);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
//--- CTrade konfigurieren (Configure CTrade)
|
||||
trade.SetExpertMagicNumber(MagicNumber);
|
||||
trade.SetDeviationInPoints(10);
|
||||
trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||||
|
||||
//--- EMA Indicator Handle erstellen (Create EMA indicator handle)
|
||||
ema_handle = iMA(_Symbol, Timeframe, EMA_Periode, 0, MODE_EMA, PRICE_CLOSE);
|
||||
|
||||
if(ema_handle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Fehler beim Erstellen des EMA Indicators");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
//--- Arrays initialisieren (Initialize arrays)
|
||||
ArraySetAsSeries(ema_array, true);
|
||||
|
||||
//--- Arrays mit aktuellen Werten füllen (Fill arrays with current values)
|
||||
BerechneEMA();
|
||||
|
||||
Print("EMA EA initialisiert - Periode: ", EMA_Periode, " Timeframe: ", EnumToString(Timeframe), " Handle: ", ema_handle);
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
//--- Indicator Handle freigeben (Release indicator handle)
|
||||
if(ema_handle != INVALID_HANDLE)
|
||||
{
|
||||
IndicatorRelease(ema_handle);
|
||||
}
|
||||
|
||||
Print("EA beendet - Grund: ", reason);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
//--- Bar-Daten oder Tick-Daten verwenden (Use bar data or tick data)
|
||||
if(UseBarData)
|
||||
{
|
||||
//--- Nur bei neuen Bars ausführen (Only execute on new bars)
|
||||
static datetime last_bar_time = 0;
|
||||
datetime current_bar_time = iTime(_Symbol, Timeframe, 0);
|
||||
|
||||
if(current_bar_time == last_bar_time)
|
||||
{
|
||||
return; // Kein neuer Bar, nichts tun
|
||||
}
|
||||
|
||||
last_bar_time = current_bar_time;
|
||||
}
|
||||
|
||||
//--- EMA Werte berechnen (Calculate EMA values)
|
||||
BerechneEMA();
|
||||
|
||||
//--- Debug: Aktuelle Werte ausgeben (Debug: Output current values)
|
||||
if(ArraySize(ema_array) > 0)
|
||||
{
|
||||
double aktueller_close = iClose(_Symbol, Timeframe, 0);
|
||||
double ema_aktuell = ema_array[0];
|
||||
double ema_vorher = ema_array[1];
|
||||
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / _Point;
|
||||
double steigung = (ema_aktuell - ema_vorher) / _Point;
|
||||
|
||||
if(UseBarData)
|
||||
{
|
||||
Print("=== DEBUG INFO (Neuer Bar) ===");
|
||||
Print("Bar Zeit: ", TimeToString(iTime(_Symbol, Timeframe, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("=== DEBUG INFO (Tick) ===");
|
||||
}
|
||||
|
||||
Print("Aktueller Close: ", aktueller_close);
|
||||
Print("EMA: ", ema_aktuell);
|
||||
Print("Preis-Abstand: ", preis_abstand, " Pips");
|
||||
Print("EMA Steigung: ", steigung, " Pips");
|
||||
Print("Differenz Close-EMA: ", aktueller_close - ema_aktuell);
|
||||
Print("Preis-Trigger: ", preis_trigger_aktiv, " Steigungs-Trigger: ", steigung_trigger_aktiv);
|
||||
Print("Überwachung aktiv: ", überwachung_aktiv);
|
||||
Print("Position offen: ", PositionExistsByMagic(_Symbol, MagicNumber));
|
||||
Print("Trades im aktuellen Crossover: ", trades_in_current_crossover, "/", MaxTradesPerCrossover);
|
||||
Print("==================");
|
||||
}
|
||||
|
||||
//--- Überwachung prüfen (Check monitoring)
|
||||
if(überwachung_aktiv)
|
||||
{
|
||||
if(UseBarData)
|
||||
{
|
||||
// Bar-basierte Überwachungszeit
|
||||
int bars_since_monitoring = iBarShift(_Symbol, Timeframe, letzte_überwachung_zeit);
|
||||
int timeout_bars = (int)(ÜberwachungTimeout / PeriodSeconds(Timeframe));
|
||||
|
||||
if(bars_since_monitoring > timeout_bars)
|
||||
{
|
||||
überwachung_aktiv = false;
|
||||
preis_trigger_aktiv = false;
|
||||
steigung_trigger_aktiv = false;
|
||||
Print("Überwachung beendet - Bar-basierte Zeitüberschreitung (", bars_since_monitoring, " Bars)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Tick-basierte Überwachungszeit
|
||||
if(TimeCurrent() - letzte_überwachung_zeit > ÜberwachungTimeout)
|
||||
{
|
||||
überwachung_aktiv = false;
|
||||
preis_trigger_aktiv = false;
|
||||
steigung_trigger_aktiv = false;
|
||||
Print("Überwachung beendet - Tick-basierte Zeitüberschreitung");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--- Trigger-Bedingungen prüfen (Check trigger conditions)
|
||||
PrüfeTrigger();
|
||||
|
||||
//--- Trade Management (Trade management)
|
||||
VerwalteTrades();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| EMA Berechnung (EMA Calculation) |
|
||||
//+------------------------------------------------------------------+
|
||||
void BerechneEMA()
|
||||
{
|
||||
//--- EMA Werte vom Indicator kopieren (Copy EMA values from indicator)
|
||||
int copied = CopyBuffer(ema_handle, 0, 0, 3, ema_array);
|
||||
|
||||
if(copied <= 0)
|
||||
{
|
||||
Print("TRACE: Fehler beim Kopieren der EMA Werte - Copied: ", copied);
|
||||
return;
|
||||
}
|
||||
|
||||
Print("TRACE: EMA Werte kopiert: ", copied, " Bars");
|
||||
Print("TRACE: EMA [0]: ", ema_array[0], " [1]: ", ema_array[1], " [2]: ", ema_array[2]);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trigger-Bedingungen prüfen (Check trigger conditions) |
|
||||
//+------------------------------------------------------------------+
|
||||
void PrüfeTrigger()
|
||||
{
|
||||
if(ArraySize(ema_array) < 2)
|
||||
{
|
||||
Print("TRACE: Array zu klein - Größe: ", ArraySize(ema_array));
|
||||
return;
|
||||
}
|
||||
|
||||
//--- Aktuelle Werte (Current values)
|
||||
double aktueller_preis = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||||
double aktueller_ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||||
double aktueller_close = iClose(_Symbol, Timeframe, 0);
|
||||
double pips_multiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
||||
|
||||
//--- EMA Werte in Variablen (EMA values in variables)
|
||||
double ema_aktuell = ema_array[0];
|
||||
double ema_vorher = ema_array[1];
|
||||
|
||||
//--- EMA Crossover Erkennung (EMA Crossover Detection)
|
||||
// Prüfe ob Preis die EMA kreuzt (Check if price crosses EMA)
|
||||
static double last_close = 0;
|
||||
static double last_ema = 0;
|
||||
|
||||
if(last_close != 0 && last_ema != 0)
|
||||
{
|
||||
bool crossover_bullish = (last_close <= last_ema) && (aktueller_close > ema_aktuell);
|
||||
bool crossover_bearish = (last_close >= last_ema) && (aktueller_close < ema_aktuell);
|
||||
|
||||
//--- Neues Crossover-Ereignis erkannt (New crossover event detected)
|
||||
if(crossover_bullish || crossover_bearish)
|
||||
{
|
||||
trades_in_current_crossover = 0; // Reset trade counter
|
||||
Print("TRACE: EMA Crossover erkannt - ", (crossover_bullish ? "BULLISH" : "BEARISH"), " - Trade-Counter zurückgesetzt");
|
||||
Print("TRACE: Vorher: Close=", last_close, " EMA=", last_ema, " Jetzt: Close=", aktueller_close, " EMA=", ema_aktuell);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Aktuelle Werte für nächsten Vergleich speichern (Save current values for next comparison)
|
||||
last_close = aktueller_close;
|
||||
last_ema = ema_aktuell;
|
||||
|
||||
//--- Preisbewegung zur EMA prüfen (Check price action to EMA)
|
||||
double preis_abstand = MathAbs(aktueller_close - ema_aktuell) / _Point / pips_multiplier;
|
||||
|
||||
Print("TRACE: Preis-Abstand: ", preis_abstand, " Pips (Schwelle: ", PreisSchwelle, ")");
|
||||
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
Print("TRACE: Trades im aktuellen Crossover: ", trades_in_current_crossover, "/", MaxTradesPerCrossover);
|
||||
|
||||
if(preis_abstand > PreisSchwelle && !preis_trigger_aktiv)
|
||||
{
|
||||
preis_trigger_aktiv = true;
|
||||
Print("TRACE: Preis-Trigger aktiviert: ", preis_abstand, " Pips");
|
||||
}
|
||||
|
||||
//--- EMA Steigung prüfen (Check EMA slope)
|
||||
double steigung = (ema_aktuell - ema_vorher) / _Point / pips_multiplier;
|
||||
|
||||
Print("TRACE: EMA Steigung: ", steigung, " Pips (Schwelle: ", SteigungSchwelle, ")");
|
||||
|
||||
if(MathAbs(steigung) > SteigungSchwelle && !steigung_trigger_aktiv)
|
||||
{
|
||||
steigung_trigger_aktiv = true;
|
||||
Print("TRACE: Steigungs-Trigger aktiviert: ", steigung, " Pips");
|
||||
}
|
||||
|
||||
//--- Überwachung starten wenn beide Trigger aktiv sind (Start monitoring when both triggers are active)
|
||||
if(preis_trigger_aktiv && steigung_trigger_aktiv && !überwachung_aktiv)
|
||||
{
|
||||
überwachung_aktiv = true;
|
||||
|
||||
if(UseBarData)
|
||||
{
|
||||
letzte_überwachung_zeit = iTime(_Symbol, Timeframe, 0); // Aktuelle Bar-Zeit
|
||||
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Bar: ", TimeToString(letzte_überwachung_zeit), ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
letzte_überwachung_zeit = TimeCurrent(); // Aktuelle Tick-Zeit
|
||||
Print("TRACE: Überwachung gestartet - Beide Trigger aktiv (Tick)");
|
||||
}
|
||||
}
|
||||
|
||||
//--- Trade platzieren wenn Überwachung aktiv und Preis über/unter EMA (Place trade when monitoring active and price above/below EMA)
|
||||
if(überwachung_aktiv)
|
||||
{
|
||||
bool bullish_signal = aktueller_close > ema_aktuell;
|
||||
bool bearish_signal = aktueller_close < ema_aktuell;
|
||||
|
||||
Print("TRACE: Signal Check - Bullish: ", bullish_signal, " Bearish: ", bearish_signal);
|
||||
Print("TRACE: Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
Print("TRACE: Differenz: ", aktueller_close - ema_aktuell);
|
||||
|
||||
//--- Trade-Limit prüfen (Check trade limit)
|
||||
if(trades_in_current_crossover >= MaxTradesPerCrossover)
|
||||
{
|
||||
Print("TRACE: Trade-Limit erreicht (", MaxTradesPerCrossover, ") - Kein neuer Trade");
|
||||
return;
|
||||
}
|
||||
|
||||
if(bullish_signal && !PositionExistsByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
if(!IsWeeklyADXTrendFavorable(ORDER_TYPE_BUY))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert BUY-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche KAUF-Trade zu platzieren (Trade #", trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_BUY))
|
||||
{
|
||||
trades_in_current_crossover++;
|
||||
}
|
||||
}
|
||||
else if(bearish_signal && !PositionExistsByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
if(!IsWeeklyADXTrendFavorable(ORDER_TYPE_SELL))
|
||||
{
|
||||
Print("TRACE: Weekly ADX blockiert SELL-Entry");
|
||||
return;
|
||||
}
|
||||
Print("TRACE: Versuche VERKAUF-Trade zu platzieren (Trade #", trades_in_current_crossover + 1, ")");
|
||||
if(PlatziereTrade(ORDER_TYPE_SELL))
|
||||
{
|
||||
trades_in_current_crossover++;
|
||||
}
|
||||
}
|
||||
else if(PositionExistsByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
Print("TRACE: Position bereits offen - kein neuer Trade");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trade platzieren (Place trade) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool PlatziereTrade(ENUM_ORDER_TYPE order_type)
|
||||
{
|
||||
Print("TRACE: Versuche Trade zu platzieren - Typ: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF");
|
||||
Print("TRACE: Lot: ", LotGröße);
|
||||
|
||||
bool success = false;
|
||||
|
||||
if(order_type == ORDER_TYPE_BUY)
|
||||
{
|
||||
success = trade.Buy(LotGröße, _Symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
else
|
||||
{
|
||||
success = trade.Sell(LotGröße, _Symbol, 0, 0, 0, "EMA Crossover Trade");
|
||||
}
|
||||
|
||||
if(success)
|
||||
{
|
||||
ticket = (int)trade.ResultOrder();
|
||||
Print("TRACE: Trade erfolgreich platziert: ", (order_type == ORDER_TYPE_BUY) ? "KAUF" : "VERKAUF", " Ticket: ", ticket);
|
||||
|
||||
//--- Trade-Öffnungszeit speichern (Save trade opening time)
|
||||
trade_open_time = iTime(_Symbol, Timeframe, 0);
|
||||
Print("TRACE: Trade-Öffnungszeit: ", TimeToString(trade_open_time));
|
||||
|
||||
//--- Überwachung zurücksetzen (Reset monitoring)
|
||||
überwachung_aktiv = false;
|
||||
preis_trigger_aktiv = false;
|
||||
steigung_trigger_aktiv = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Platzieren des Trades - Retcode: ", trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Trades verwalten (Manage trades) |
|
||||
//+------------------------------------------------------------------+
|
||||
void VerwalteTrades()
|
||||
{
|
||||
if(!PositionSelectByMagic(_Symbol, MagicNumber))
|
||||
return;
|
||||
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
||||
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
||||
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
double pips_multiplier = (_Digits == 3 || _Digits == 5) ? 10.0 : 1.0;
|
||||
double trailing_stop_pips = TrailingStop;
|
||||
|
||||
//--- Gleitender Stop (Trailing Stop) - nur wenn Position im Profit ist
|
||||
if(position_profit > 0) // Only apply trailing stop when in profit
|
||||
{
|
||||
if(position_type == POSITION_TYPE_BUY)
|
||||
{
|
||||
double new_stop_loss = current_price - (trailing_stop_pips * _Point * pips_multiplier);
|
||||
double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
|
||||
// Only move stop loss if new stop is higher than current stop
|
||||
if(new_stop_loss > current_stop_loss)
|
||||
{
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
else if(position_type == POSITION_TYPE_SELL)
|
||||
{
|
||||
double new_stop_loss = current_price + (trailing_stop_pips * _Point * pips_multiplier);
|
||||
double current_stop_loss = PositionGetDouble(POSITION_SL);
|
||||
|
||||
// Only move stop loss if new stop is lower than current stop
|
||||
if(new_stop_loss < current_stop_loss || current_stop_loss == 0)
|
||||
{
|
||||
ÄndereStopLoss(new_stop_loss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--- Ausstieg bei Preis unter/über EMA (Exit when price below/above EMA)
|
||||
if(ArraySize(ema_array) >= 1)
|
||||
{
|
||||
double aktueller_close = iClose(_Symbol, Timeframe, 0);
|
||||
double ema_aktuell = ema_array[0];
|
||||
bool exit_bullish = (position_type == POSITION_TYPE_SELL && aktueller_close > ema_aktuell);
|
||||
bool exit_bearish = (position_type == POSITION_TYPE_BUY && aktueller_close < ema_aktuell);
|
||||
|
||||
if(exit_bullish || exit_bearish)
|
||||
{
|
||||
Print("TRACE: Ausstiegssignal - Close: ", aktueller_close, " EMA: ", ema_aktuell);
|
||||
SchließePosition("EMA Crossover Exit");
|
||||
|
||||
Print("TRACE: Position geschlossen - Trade-Counter bleibt bei ", trades_in_current_crossover);
|
||||
}
|
||||
}
|
||||
|
||||
//--- Profit-Prüfung nach X Bars (Profit check after X bars)
|
||||
if(CloseUnprofitableTrades && trade_open_time != 0 && PositionExistsByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
Print("TRACE: Profit-Prüfung aktiviert - CloseUnprofitableTrades: ", CloseUnprofitableTrades);
|
||||
PrüfeProfitNachBars();
|
||||
}
|
||||
else if(!CloseUnprofitableTrades)
|
||||
{
|
||||
Print("TRACE: Profit-Prüfung deaktiviert - CloseUnprofitableTrades: ", CloseUnprofitableTrades);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Profit-Prüfung nach X Bars (Profit check after X bars) |
|
||||
//+------------------------------------------------------------------+
|
||||
void PrüfeProfitNachBars()
|
||||
{
|
||||
if(!PositionSelectByMagic(_Symbol, MagicNumber))
|
||||
{
|
||||
return; // Keine Position offen
|
||||
}
|
||||
|
||||
datetime current_bar_time = iTime(_Symbol, Timeframe, 0);
|
||||
int bars_since_trade_open = iBarShift(_Symbol, Timeframe, trade_open_time);
|
||||
|
||||
Print("TRACE: Bars seit Trade-Öffnung: ", bars_since_trade_open, "/", ProfitCheckBars);
|
||||
|
||||
//--- Prüfe ob genügend Bars vergangen sind (Check if enough bars have passed)
|
||||
if(bars_since_trade_open >= ProfitCheckBars)
|
||||
{
|
||||
double position_profit = PositionGetDouble(POSITION_PROFIT);
|
||||
double position_volume = PositionGetDouble(POSITION_VOLUME);
|
||||
ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
||||
|
||||
Print("TRACE: Profit-Prüfung nach ", ProfitCheckBars, " Bars");
|
||||
Print("TRACE: Position Profit: ", position_profit, " USD");
|
||||
|
||||
//--- Schließe Position wenn nicht im Profit (Close position if not in profit)
|
||||
if(position_profit <= 0)
|
||||
{
|
||||
Print("TRACE: Position nicht im Profit - Schließe Position");
|
||||
SchließePosition("Profit Check - Unprofitable");
|
||||
|
||||
//--- Trade-Öffnungszeit zurücksetzen (Reset trade opening time)
|
||||
trade_open_time = 0;
|
||||
Print("TRACE: Trade-Öffnungszeit zurückgesetzt");
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Position im Profit - Behalte Position");
|
||||
//--- Trade-Öffnungszeit zurücksetzen um weitere Prüfungen zu vermeiden (Reset to avoid further checks)
|
||||
trade_open_time = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Stop Loss ändern (Modify Stop Loss) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ÄndereStopLoss(double new_stop_loss)
|
||||
{
|
||||
Print("TRACE: Versuche Stop Loss zu ändern auf: ", new_stop_loss);
|
||||
|
||||
bool success = ModifyPositionByMagic(trade, _Symbol, MagicNumber, new_stop_loss, PositionGetDouble(POSITION_TP));
|
||||
|
||||
if(success)
|
||||
{
|
||||
Print("TRACE: Stop Loss erfolgreich geändert auf: ", new_stop_loss);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Ändern des Stop Loss - Retcode: ", trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Position schließen (Close position) |
|
||||
//+------------------------------------------------------------------+
|
||||
void SchließePosition(string reason = "Unbekannt")
|
||||
{
|
||||
Print("TRACE: Versuche Position zu schließen - Grund: ", reason);
|
||||
|
||||
bool success = ClosePositionByMagic(trade, _Symbol, MagicNumber);
|
||||
|
||||
if(success)
|
||||
{
|
||||
Print("TRACE: Position erfolgreich geschlossen - Grund: ", reason);
|
||||
}
|
||||
else
|
||||
{
|
||||
Print("TRACE: Fehler beim Schließen der Position - Retcode: ", trade.ResultRetcode());
|
||||
Print("TRACE: Fehlerbeschreibung: ", trade.ResultRetcodeDescription());
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
|
After Width: | Height: | Size: 30 KiB |