diff --git a/ai/eurusd1h/EURUSD_H1_ActionEA.mq5 b/ai/eurusd1h/EURUSD_H1_ActionEA.mq5 new file mode 100644 index 0000000..e0ee1c3 --- /dev/null +++ b/ai/eurusd1h/EURUSD_H1_ActionEA.mq5 @@ -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 + +#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); +} diff --git a/ai/eurusd1h/__pycache__/main.cpython-312.pyc b/ai/eurusd1h/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..7fc29ce Binary files /dev/null and b/ai/eurusd1h/__pycache__/main.cpython-312.pyc differ diff --git a/ai/eurusd1h/here.md b/ai/eurusd1h/here.md new file mode 100644 index 0000000..eaa1e3f --- /dev/null +++ b/ai/eurusd1h/here.md @@ -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. diff --git a/ai/eurusd1h/main.py b/ai/eurusd1h/main.py new file mode 100644 index 0000000..ab933c8 --- /dev/null +++ b/ai/eurusd1h/main.py @@ -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()) diff --git a/ai/eurusd1h/models/EURUSD_H1_action.onnx b/ai/eurusd1h/models/EURUSD_H1_action.onnx new file mode 100644 index 0000000..82798e7 Binary files /dev/null and b/ai/eurusd1h/models/EURUSD_H1_action.onnx differ diff --git a/ai/eurusd1h/models/EURUSD_H1_action_meta.json b/ai/eurusd1h/models/EURUSD_H1_action_meta.json new file mode 100644 index 0000000..d833371 --- /dev/null +++ b/ai/eurusd1h/models/EURUSD_H1_action_meta.json @@ -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." +} \ No newline at end of file diff --git a/ai/eurusd1h/models/EURUSD_H1_action_scaler.pkl b/ai/eurusd1h/models/EURUSD_H1_action_scaler.pkl new file mode 100644 index 0000000..1dccb6e Binary files /dev/null and b/ai/eurusd1h/models/EURUSD_H1_action_scaler.pkl differ diff --git a/ai/eurusd1h/requirements.txt b/ai/eurusd1h/requirements.txt new file mode 100644 index 0000000..b072808 --- /dev/null +++ b/ai/eurusd1h/requirements.txt @@ -0,0 +1 @@ +-r ../xauusd_h1/requirements.txt diff --git a/ai/yt/US500_H1_ArticleEA.mq5 b/ai/yt/US500_H1_ArticleEA.mq5 new file mode 100644 index 0000000..739e974 --- /dev/null +++ b/ai/yt/US500_H1_ArticleEA.mq5 @@ -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 + +#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); + } + } +} diff --git a/ai/yt/US500_H1_ArticleEA.set b/ai/yt/US500_H1_ArticleEA.set new file mode 100644 index 0000000..acfed63 --- /dev/null +++ b/ai/yt/US500_H1_ArticleEA.set @@ -0,0 +1,30 @@ +; US500_H1_ArticleEA — Strategy Tester preset (fixed inputs, no optimization) +; Copy to: MetaQuotes\Terminal\\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 diff --git a/ai/yt/US500_H1_ArticleEA_optimize.set b/ai/yt/US500_H1_ArticleEA_optimize.set new file mode 100644 index 0000000..e1f3107 --- /dev/null +++ b/ai/yt/US500_H1_ArticleEA_optimize.set @@ -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 diff --git a/ai/yt/__pycache__/train_article_split.cpython-312.pyc b/ai/yt/__pycache__/train_article_split.cpython-312.pyc new file mode 100644 index 0000000..ee003e9 Binary files /dev/null and b/ai/yt/__pycache__/train_article_split.cpython-312.pyc differ diff --git a/ai/yt/here.md b/ai/yt/here.md new file mode 100644 index 0000000..9b300c5 --- /dev/null +++ b/ai/yt/here.md @@ -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/_H1_article_split.onnx`, scaler `.pkl`, `*_meta.json`. diff --git a/ai/yt/models/US500_H1_article_split.onnx b/ai/yt/models/US500_H1_article_split.onnx new file mode 100644 index 0000000..343410d Binary files /dev/null and b/ai/yt/models/US500_H1_article_split.onnx differ diff --git a/ai/yt/models/US500_H1_article_split_meta.json b/ai/yt/models/US500_H1_article_split_meta.json new file mode 100644 index 0000000..78942ca --- /dev/null +++ b/ai/yt/models/US500_H1_article_split_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." +} \ No newline at end of file diff --git a/ai/yt/models/US500_H1_article_split_scaler.pkl b/ai/yt/models/US500_H1_article_split_scaler.pkl new file mode 100644 index 0000000..268dc70 Binary files /dev/null and b/ai/yt/models/US500_H1_article_split_scaler.pkl differ diff --git a/ai/yt/requirements.txt b/ai/yt/requirements.txt new file mode 100644 index 0000000..b072808 --- /dev/null +++ b/ai/yt/requirements.txt @@ -0,0 +1 @@ +-r ../xauusd_h1/requirements.txt diff --git a/ai/yt/train_article_split.py b/ai/yt/train_article_split.py new file mode 100644 index 0000000..947dc26 --- /dev/null +++ b/ai/yt/train_article_split.py @@ -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()) diff --git a/frontline/cluster-0/README.md b/frontline/cluster-0/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/frontline/cluster-0/_united-V2/PerformanceEvaluator.mqh b/frontline/cluster-0/_united-V2/PerformanceEvaluator.mqh deleted file mode 100644 index 991f603..0000000 --- a/frontline/cluster-0/_united-V2/PerformanceEvaluator.mqh +++ /dev/null @@ -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; -} - -//+------------------------------------------------------------------+ diff --git a/frontline/cluster-fuck/DarvasBoxXAUUSD/DarvasBoxXAUUSD_Genetic_Optimization.set b/frontline/cluster-fuck/DarvasBoxXAUUSD/DarvasBoxXAUUSD_Genetic_Optimization.set new file mode 100644 index 0000000..ca12828 --- /dev/null +++ b/frontline/cluster-fuck/DarvasBoxXAUUSD/DarvasBoxXAUUSD_Genetic_Optimization.set @@ -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 diff --git a/frontline/cluster-fuck/DarvasBoxXAUUSD/main.mq5 b/frontline/cluster-fuck/DarvasBoxXAUUSD/main.mq5 new file mode 100644 index 0000000..6da93f9 --- /dev/null +++ b/frontline/cluster-fuck/DarvasBoxXAUUSD/main.mq5 @@ -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 +#include +#include +#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); +} diff --git a/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/main.mq5 b/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/main.mq5 new file mode 100644 index 0000000..6d849c0 --- /dev/null +++ b/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/main.mq5 @@ -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 +#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()); + } +} + +//+------------------------------------------------------------------+ diff --git a/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/report.html b/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/report.html new file mode 100644 index 0000000..8d9931f Binary files /dev/null and b/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/report.html differ diff --git a/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/report.png b/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/report.png new file mode 100644 index 0000000..c0bf45b Binary files /dev/null and b/frontline/cluster-fuck/EMASlopeDistanceCocktailXAUUSD-trailing/report.png differ diff --git a/frontline/cluster-fuck/RSIConsolidationXAUUSD/RSIConsolidation.mq5 b/frontline/cluster-fuck/RSIConsolidationXAUUSD/RSIConsolidation.mq5 new file mode 100644 index 0000000..09c4d7d --- /dev/null +++ b/frontline/cluster-fuck/RSIConsolidationXAUUSD/RSIConsolidation.mq5 @@ -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 + +//--- 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"); + } +} + +//+------------------------------------------------------------------+ diff --git a/frontline/cluster-fuck/RSIConsolidationXAUUSD/RSIConsolidation_optimization.set b/frontline/cluster-fuck/RSIConsolidationXAUUSD/RSIConsolidation_optimization.set new file mode 100644 index 0000000..d6a75ed --- /dev/null +++ b/frontline/cluster-fuck/RSIConsolidationXAUUSD/RSIConsolidation_optimization.set @@ -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 diff --git a/frontline/cluster-fuck/RSICrossOverReversalXAUUSD/main.mq5 b/frontline/cluster-fuck/RSICrossOverReversalXAUUSD/main.mq5 new file mode 100644 index 0000000..8412fa6 --- /dev/null +++ b/frontline/cluster-fuck/RSICrossOverReversalXAUUSD/main.mq5 @@ -0,0 +1,295 @@ +// Input Parameters +#include +#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; +} \ No newline at end of file diff --git a/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/main.mq5 b/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/main.mq5 new file mode 100644 index 0000000..de9ddb3 --- /dev/null +++ b/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/main.mq5 @@ -0,0 +1,604 @@ +//+------------------------------------------------------------------+ +//| RSIFollowReverseEMACrossOver.mq5 | +//| Copyright 2024, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2024, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.00" + +#include +#include +#include "../_united/MagicNumberHelpers.mqh" + +// Input Parameters +input group "General Settings" +input ENUM_TIMEFRAMES InpTimeframe = PERIOD_H1; // Trading Timeframe +input double InpLotSize = 0.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); +} diff --git a/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/report.html b/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/report.html new file mode 100644 index 0000000..a731713 Binary files /dev/null and b/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/report.html differ diff --git a/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/report.png b/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/report.png new file mode 100644 index 0000000..71b2983 Binary files /dev/null and b/frontline/cluster-fuck/RSIMidPointHijackXAUUSD/report.png differ diff --git a/frontline/cluster-fuck/RSIReversalAsianAUDUSD/main.mq5 b/frontline/cluster-fuck/RSIReversalAsianAUDUSD/main.mq5 new file mode 100644 index 0000000..92c7968 --- /dev/null +++ b/frontline/cluster-fuck/RSIReversalAsianAUDUSD/main.mq5 @@ -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 + +// 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; + } + } + } +} \ No newline at end of file diff --git a/frontline/cluster-fuck/RSIReversalAsianAUDUSD/test-balance.png b/frontline/cluster-fuck/RSIReversalAsianAUDUSD/test-balance.png new file mode 100644 index 0000000..47d7c5d Binary files /dev/null and b/frontline/cluster-fuck/RSIReversalAsianAUDUSD/test-balance.png differ diff --git a/frontline/cluster-fuck/RSIReversalAsianEURUSD/main.mq5 b/frontline/cluster-fuck/RSIReversalAsianEURUSD/main.mq5 new file mode 100644 index 0000000..c89b331 --- /dev/null +++ b/frontline/cluster-fuck/RSIReversalAsianEURUSD/main.mq5 @@ -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 + +// 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; + } + } + } +} \ No newline at end of file diff --git a/frontline/cluster-fuck/RSIReversalAsianEURUSD/test-balance.jpg b/frontline/cluster-fuck/RSIReversalAsianEURUSD/test-balance.jpg new file mode 100644 index 0000000..2901f4b Binary files /dev/null and b/frontline/cluster-fuck/RSIReversalAsianEURUSD/test-balance.jpg differ diff --git a/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/main.mq5 b/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/main.mq5 new file mode 100644 index 0000000..b34e0a2 --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/main.mq5 @@ -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 +#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(); + } + } +} diff --git a/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/report.html b/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/report.html new file mode 100644 index 0000000..b512b3d Binary files /dev/null and b/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/report.html differ diff --git a/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/report.png b/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/report.png new file mode 100644 index 0000000..52287c6 Binary files /dev/null and b/frontline/cluster-fuck/RSIScalpingBTCUSD-trailing/report.png differ diff --git a/frontline/cluster-fuck/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization.set b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization.set new file mode 100644 index 0000000..81b89e6 --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization.set @@ -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 diff --git a/frontline/cluster-fuck/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization_Alternative.set b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization_Alternative.set new file mode 100644 index 0000000..3fca2be --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization_Alternative.set @@ -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 diff --git a/frontline/cluster-fuck/RSIScalpingNVDA-trailing/OPTIMIZATION_GUIDE.md b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/OPTIMIZATION_GUIDE.md new file mode 100644 index 0000000..d7d5ba6 --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/OPTIMIZATION_GUIDE.md @@ -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 +``` diff --git a/frontline/cluster-fuck/RSIScalpingNVDA-trailing/main.mq5 b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/main.mq5 new file mode 100644 index 0000000..5950bce --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingNVDA-trailing/main.mq5 @@ -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 +#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; + } +} diff --git a/frontline/cluster-fuck/RSIScalpingTSLA-trailing/main.mq5 b/frontline/cluster-fuck/RSIScalpingTSLA-trailing/main.mq5 new file mode 100644 index 0000000..1648b9a --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingTSLA-trailing/main.mq5 @@ -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 +#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; + } +} diff --git a/frontline/cluster-fuck/RSIScalpingTSLA-trailing/report.html b/frontline/cluster-fuck/RSIScalpingTSLA-trailing/report.html new file mode 100644 index 0000000..950f95b Binary files /dev/null and b/frontline/cluster-fuck/RSIScalpingTSLA-trailing/report.html differ diff --git a/frontline/cluster-fuck/RSIScalpingTSLA-trailing/report.png b/frontline/cluster-fuck/RSIScalpingTSLA-trailing/report.png new file mode 100644 index 0000000..5a26c1d Binary files /dev/null and b/frontline/cluster-fuck/RSIScalpingTSLA-trailing/report.png differ diff --git a/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/123.set b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/123.set new file mode 100644 index 0000000..0c7f7c1 --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/123.set @@ -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 diff --git a/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/main.mq5 b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/main.mq5 new file mode 100644 index 0000000..96b6f2f --- /dev/null +++ b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/main.mq5 @@ -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 +#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()); +} diff --git a/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/report.html b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/report.html new file mode 100644 index 0000000..203dcf4 Binary files /dev/null and b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/report.html differ diff --git a/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/report.png b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/report.png new file mode 100644 index 0000000..c2c91f2 Binary files /dev/null and b/frontline/cluster-fuck/RSIScalpingXAUUSD-trailing/report.png differ diff --git a/frontline/cluster-fuck/RSI_secret_sauce_XAUUSD/main.mq5 b/frontline/cluster-fuck/RSI_secret_sauce_XAUUSD/main.mq5 new file mode 100644 index 0000000..425b7f7 --- /dev/null +++ b/frontline/cluster-fuck/RSI_secret_sauce_XAUUSD/main.mq5 @@ -0,0 +1,508 @@ +//+------------------------------------------------------------------+ +//| RSI_SecretSauce_XAUUSD.mq5 | +//| Copyright 2025, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2025, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.01" +#property description "RSI Secret Sauce Strategy: Wait for RSI to leave 70/30 zone, then enter when it comes back in" +#property description "Based on momentum flip concept - not traditional overbought/oversold" + +#include +#include + +//--- Input Parameters +input group "=== Trading Settings ===" +input string InpSymbol = "XAUUSD"; // 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 +} + +//+------------------------------------------------------------------+ diff --git a/frontline/cluster-fuck/SimpleTrendlineBTCUSD/SimpleTrendline.mq5 b/frontline/cluster-fuck/SimpleTrendlineBTCUSD/SimpleTrendline.mq5 new file mode 100644 index 0000000..6476f77 --- /dev/null +++ b/frontline/cluster-fuck/SimpleTrendlineBTCUSD/SimpleTrendline.mq5 @@ -0,0 +1,284 @@ +#property strict +#property version "1.00" + +#include + +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); +} diff --git a/frontline/cluster-fuck/SimpleTrendlineBTCUSD/SimpleTrendline_optimization.set b/frontline/cluster-fuck/SimpleTrendlineBTCUSD/SimpleTrendline_optimization.set new file mode 100644 index 0000000..10ea2ee --- /dev/null +++ b/frontline/cluster-fuck/SimpleTrendlineBTCUSD/SimpleTrendline_optimization.set @@ -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 diff --git a/frontline/cluster-fuck/SimpleTrendlineGER40/SimpleTrendline.mq5 b/frontline/cluster-fuck/SimpleTrendlineGER40/SimpleTrendline.mq5 new file mode 100644 index 0000000..c87c3ef --- /dev/null +++ b/frontline/cluster-fuck/SimpleTrendlineGER40/SimpleTrendline.mq5 @@ -0,0 +1,284 @@ +#property strict +#property version "1.00" + +#include + +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); +} diff --git a/frontline/cluster-fuck/SimpleTrendlineGER40/SimpleTrendline_optimization.set b/frontline/cluster-fuck/SimpleTrendlineGER40/SimpleTrendline_optimization.set new file mode 100644 index 0000000..10ea2ee --- /dev/null +++ b/frontline/cluster-fuck/SimpleTrendlineGER40/SimpleTrendline_optimization.set @@ -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 diff --git a/frontline/cluster-fuck/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 b/frontline/cluster-fuck/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 new file mode 100644 index 0000000..77ed295 --- /dev/null +++ b/frontline/cluster-fuck/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 @@ -0,0 +1,376 @@ +#property strict +#property version "1.00" + +#include + +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); +} diff --git a/frontline/cluster-fuck/SimpleTrendlineXAUUSD/SimpleTrendline_optimization.set b/frontline/cluster-fuck/SimpleTrendlineXAUUSD/SimpleTrendline_optimization.set new file mode 100644 index 0000000..10ea2ee --- /dev/null +++ b/frontline/cluster-fuck/SimpleTrendlineXAUUSD/SimpleTrendline_optimization.set @@ -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 diff --git a/frontline/cluster-fuck/SuperEMAXAUUSD/SuperEMA.set b/frontline/cluster-fuck/SuperEMAXAUUSD/SuperEMA.set new file mode 100644 index 0000000..f52ec17 --- /dev/null +++ b/frontline/cluster-fuck/SuperEMAXAUUSD/SuperEMA.set @@ -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 diff --git a/frontline/cluster-fuck/SuperEMAXAUUSD/main.mq5 b/frontline/cluster-fuck/SuperEMAXAUUSD/main.mq5 new file mode 100644 index 0000000..07831c3 --- /dev/null +++ b/frontline/cluster-fuck/SuperEMAXAUUSD/main.mq5 @@ -0,0 +1,448 @@ +//+------------------------------------------------------------------+ +//| SuperEMA.mq5 | +//| EMA + CCI + MACD histogram — trend filter, momentum confirmation | +//+------------------------------------------------------------------+ +#property strict +#property version "1.01" + +#include + +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)); + } +} diff --git a/frontline/cluster-0/_united-V2/MagicNumberHelpers.mqh b/frontline/cluster-fuck/_united-V2/MagicNumberHelpers.mqh similarity index 73% rename from frontline/cluster-0/_united-V2/MagicNumberHelpers.mqh rename to frontline/cluster-fuck/_united-V2/MagicNumberHelpers.mqh index dc1fa31..0423cc4 100644 --- a/frontline/cluster-0/_united-V2/MagicNumberHelpers.mqh +++ b/frontline/cluster-fuck/_united-V2/MagicNumberHelpers.mqh @@ -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); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/cluster-0/_united-V2/STRATEGY_CONFIGURATION.md b/frontline/cluster-fuck/_united-V2/STRATEGY_CONFIGURATION.md similarity index 100% rename from frontline/cluster-0/_united-V2/STRATEGY_CONFIGURATION.md rename to frontline/cluster-fuck/_united-V2/STRATEGY_CONFIGURATION.md diff --git a/frontline/united_template/_united-V2_exponential/Strategies/DarvasBoxStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/DarvasBoxStrategy.mqh similarity index 89% rename from frontline/united_template/_united-V2_exponential/Strategies/DarvasBoxStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/DarvasBoxStrategy.mqh index 6c2d7d5..989dc21 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/DarvasBoxStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/DarvasBoxStrategy.mqh @@ -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) diff --git a/frontline/cluster-0/_united-V2/Strategies/EMASlopeDistanceStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/EMASlopeDistanceStrategy.mqh similarity index 73% rename from frontline/cluster-0/_united-V2/Strategies/EMASlopeDistanceStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/EMASlopeDistanceStrategy.mqh index 70e4502..2ddbeeb 100644 --- a/frontline/cluster-0/_united-V2/Strategies/EMASlopeDistanceStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/EMASlopeDistanceStrategy.mqh @@ -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(); } diff --git a/frontline/cluster-0/_united-V2/Strategies/RSIConsolidationStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/RSIConsolidationStrategy.mqh similarity index 100% rename from frontline/cluster-0/_united-V2/Strategies/RSIConsolidationStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/RSIConsolidationStrategy.mqh diff --git a/frontline/cluster-0/_united-V2/Strategies/RSICrossOverReversalStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/RSICrossOverReversalStrategy.mqh similarity index 84% rename from frontline/cluster-0/_united-V2/Strategies/RSICrossOverReversalStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/RSICrossOverReversalStrategy.mqh index 266292a..ddc5f5a 100644 --- a/frontline/cluster-0/_united-V2/Strategies/RSICrossOverReversalStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/RSICrossOverReversalStrategy.mqh @@ -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; } } diff --git a/frontline/united_template/_united-V2_exponential/Strategies/RSIMidPointHijackStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/RSIMidPointHijackStrategy.mqh similarity index 92% rename from frontline/united_template/_united-V2_exponential/Strategies/RSIMidPointHijackStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/RSIMidPointHijackStrategy.mqh index 3db8db5..120996b 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/RSIMidPointHijackStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/RSIMidPointHijackStrategy.mqh @@ -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"); } } } diff --git a/frontline/united_template/_united-V2_exponential/Strategies/RSIReversalAsianStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/RSIReversalAsianStrategy.mqh similarity index 94% rename from frontline/united_template/_united-V2_exponential/Strategies/RSIReversalAsianStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/RSIReversalAsianStrategy.mqh index 6762248..e204fb0 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/RSIReversalAsianStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/RSIReversalAsianStrategy.mqh @@ -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; diff --git a/frontline/united_template/_united-V2_exponential/Strategies/RSIScalpingStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/RSIScalpingStrategy.mqh similarity index 86% rename from frontline/united_template/_united-V2_exponential/Strategies/RSIScalpingStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/RSIScalpingStrategy.mqh index 5daa1c1..630955d 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/RSIScalpingStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/RSIScalpingStrategy.mqh @@ -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; diff --git a/frontline/cluster-fuck/_united-V2/Strategies/RSISecretSauceStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/RSISecretSauceStrategy.mqh new file mode 100644 index 0000000..a8c284e --- /dev/null +++ b/frontline/cluster-fuck/_united-V2/Strategies/RSISecretSauceStrategy.mqh @@ -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 +#include + +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 diff --git a/frontline/cluster-0/_united-V2/Strategies/SimpleTrendlineStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/SimpleTrendlineStrategy.mqh similarity index 100% rename from frontline/cluster-0/_united-V2/Strategies/SimpleTrendlineStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/SimpleTrendlineStrategy.mqh diff --git a/frontline/cluster-0/_united-V2/Strategies/SuperEMAStrategy.mqh b/frontline/cluster-fuck/_united-V2/Strategies/SuperEMAStrategy.mqh similarity index 93% rename from frontline/cluster-0/_united-V2/Strategies/SuperEMAStrategy.mqh rename to frontline/cluster-fuck/_united-V2/Strategies/SuperEMAStrategy.mqh index 3dba90e..52400fe 100644 --- a/frontline/cluster-0/_united-V2/Strategies/SuperEMAStrategy.mqh +++ b/frontline/cluster-fuck/_united-V2/Strategies/SuperEMAStrategy.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)); } } diff --git a/frontline/united_template/_united-V2_exponential/main.mq5 b/frontline/cluster-fuck/_united-V2/main.mq5 similarity index 71% rename from frontline/united_template/_united-V2_exponential/main.mq5 rename to frontline/cluster-fuck/_united-V2/main.mq5 index 0fc64f4..0881584 100644 --- a/frontline/united_template/_united-V2_exponential/main.mq5 +++ b/frontline/cluster-fuck/_united-V2/main.mq5 @@ -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 #include #include #include #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); } //+------------------------------------------------------------------+ diff --git a/frontline/cluster-0/_united-V2/report.png b/frontline/cluster-fuck/_united-V2/report.png similarity index 100% rename from frontline/cluster-0/_united-V2/report.png rename to frontline/cluster-fuck/_united-V2/report.png diff --git a/frontline/united_template/_united-V2_exponential/self-evaluate.mq5 b/frontline/cluster-fuck/_united-V2/self-evaluate.mq5 similarity index 74% rename from frontline/united_template/_united-V2_exponential/self-evaluate.mq5 rename to frontline/cluster-fuck/_united-V2/self-evaluate.mq5 index b9d92e9..971c874 100644 --- a/frontline/united_template/_united-V2_exponential/self-evaluate.mq5 +++ b/frontline/cluster-fuck/_united-V2/self-evaluate.mq5 @@ -13,7 +13,6 @@ #include #include #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); } //+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united-V2_exponential/MagicNumberHelpers.mqh b/frontline/cluster-gold/MagicNumberHelpers.mqh similarity index 73% rename from frontline/united_template/_united-V2_exponential/MagicNumberHelpers.mqh rename to frontline/cluster-gold/MagicNumberHelpers.mqh index dc1fa31..0423cc4 100644 --- a/frontline/united_template/_united-V2_exponential/MagicNumberHelpers.mqh +++ b/frontline/cluster-gold/MagicNumberHelpers.mqh @@ -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); +} + +//+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united-V2_exponential/STRATEGY_CONFIGURATION.md b/frontline/cluster-gold/STRATEGY_CONFIGURATION.md similarity index 100% rename from frontline/united_template/_united-V2_exponential/STRATEGY_CONFIGURATION.md rename to frontline/cluster-gold/STRATEGY_CONFIGURATION.md diff --git a/frontline/cluster-0/_united-V2/Strategies/DarvasBoxStrategy.mqh b/frontline/cluster-gold/Strategies/DarvasBoxStrategy.mqh similarity index 89% rename from frontline/cluster-0/_united-V2/Strategies/DarvasBoxStrategy.mqh rename to frontline/cluster-gold/Strategies/DarvasBoxStrategy.mqh index 6c2d7d5..989dc21 100644 --- a/frontline/cluster-0/_united-V2/Strategies/DarvasBoxStrategy.mqh +++ b/frontline/cluster-gold/Strategies/DarvasBoxStrategy.mqh @@ -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) diff --git a/frontline/united_template/_united-V2_exponential/Strategies/EMASlopeDistanceStrategy.mqh b/frontline/cluster-gold/Strategies/EMASlopeDistanceStrategy.mqh similarity index 73% rename from frontline/united_template/_united-V2_exponential/Strategies/EMASlopeDistanceStrategy.mqh rename to frontline/cluster-gold/Strategies/EMASlopeDistanceStrategy.mqh index 70e4502..2ddbeeb 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/EMASlopeDistanceStrategy.mqh +++ b/frontline/cluster-gold/Strategies/EMASlopeDistanceStrategy.mqh @@ -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(); } diff --git a/frontline/united_template/_united-V2_exponential/Strategies/RSIConsolidationStrategy.mqh b/frontline/cluster-gold/Strategies/RSIConsolidationStrategy.mqh similarity index 100% rename from frontline/united_template/_united-V2_exponential/Strategies/RSIConsolidationStrategy.mqh rename to frontline/cluster-gold/Strategies/RSIConsolidationStrategy.mqh diff --git a/frontline/united_template/_united-V2_exponential/Strategies/RSICrossOverReversalStrategy.mqh b/frontline/cluster-gold/Strategies/RSICrossOverReversalStrategy.mqh similarity index 84% rename from frontline/united_template/_united-V2_exponential/Strategies/RSICrossOverReversalStrategy.mqh rename to frontline/cluster-gold/Strategies/RSICrossOverReversalStrategy.mqh index 266292a..ddc5f5a 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/RSICrossOverReversalStrategy.mqh +++ b/frontline/cluster-gold/Strategies/RSICrossOverReversalStrategy.mqh @@ -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; } } diff --git a/frontline/cluster-0/_united-V2/Strategies/RSIMidPointHijackStrategy.mqh b/frontline/cluster-gold/Strategies/RSIMidPointHijackStrategy.mqh similarity index 92% rename from frontline/cluster-0/_united-V2/Strategies/RSIMidPointHijackStrategy.mqh rename to frontline/cluster-gold/Strategies/RSIMidPointHijackStrategy.mqh index 3db8db5..120996b 100644 --- a/frontline/cluster-0/_united-V2/Strategies/RSIMidPointHijackStrategy.mqh +++ b/frontline/cluster-gold/Strategies/RSIMidPointHijackStrategy.mqh @@ -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"); } } } diff --git a/frontline/cluster-0/_united-V2/Strategies/RSIReversalAsianStrategy.mqh b/frontline/cluster-gold/Strategies/RSIReversalAsianStrategy.mqh similarity index 94% rename from frontline/cluster-0/_united-V2/Strategies/RSIReversalAsianStrategy.mqh rename to frontline/cluster-gold/Strategies/RSIReversalAsianStrategy.mqh index 6762248..e204fb0 100644 --- a/frontline/cluster-0/_united-V2/Strategies/RSIReversalAsianStrategy.mqh +++ b/frontline/cluster-gold/Strategies/RSIReversalAsianStrategy.mqh @@ -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; diff --git a/frontline/cluster-0/_united-V2/Strategies/RSIScalpingStrategy.mqh b/frontline/cluster-gold/Strategies/RSIScalpingStrategy.mqh similarity index 86% rename from frontline/cluster-0/_united-V2/Strategies/RSIScalpingStrategy.mqh rename to frontline/cluster-gold/Strategies/RSIScalpingStrategy.mqh index 5daa1c1..630955d 100644 --- a/frontline/cluster-0/_united-V2/Strategies/RSIScalpingStrategy.mqh +++ b/frontline/cluster-gold/Strategies/RSIScalpingStrategy.mqh @@ -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; diff --git a/frontline/cluster-gold/Strategies/RSISecretSauceStrategy.mqh b/frontline/cluster-gold/Strategies/RSISecretSauceStrategy.mqh new file mode 100644 index 0000000..a8c284e --- /dev/null +++ b/frontline/cluster-gold/Strategies/RSISecretSauceStrategy.mqh @@ -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 +#include + +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 diff --git a/frontline/cluster-gold/Strategies/SimpleTrendlineStrategy.mqh b/frontline/cluster-gold/Strategies/SimpleTrendlineStrategy.mqh new file mode 100644 index 0000000..6341b88 --- /dev/null +++ b/frontline/cluster-gold/Strategies/SimpleTrendlineStrategy.mqh @@ -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 diff --git a/frontline/united_template/_united-V2_exponential/Strategies/SuperEMAStrategy.mqh b/frontline/cluster-gold/Strategies/SuperEMAStrategy.mqh similarity index 93% rename from frontline/united_template/_united-V2_exponential/Strategies/SuperEMAStrategy.mqh rename to frontline/cluster-gold/Strategies/SuperEMAStrategy.mqh index 3dba90e..52400fe 100644 --- a/frontline/united_template/_united-V2_exponential/Strategies/SuperEMAStrategy.mqh +++ b/frontline/cluster-gold/Strategies/SuperEMAStrategy.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)); } } diff --git a/frontline/cluster-0/_united-V2/main.mq5 b/frontline/cluster-gold/main.mq5 similarity index 79% rename from frontline/cluster-0/_united-V2/main.mq5 rename to frontline/cluster-gold/main.mq5 index 564b03f..f77c907 100644 --- a/frontline/cluster-0/_united-V2/main.mq5 +++ b/frontline/cluster-gold/main.mq5 @@ -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 #include #include #include #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); } //+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united-V2_exponential/report.png b/frontline/cluster-gold/report.png similarity index 100% rename from frontline/united_template/_united-V2_exponential/report.png rename to frontline/cluster-gold/report.png diff --git a/frontline/cluster-0/_united-V2/self-evaluate.mq5 b/frontline/cluster-gold/self-evaluate.mq5 similarity index 74% rename from frontline/cluster-0/_united-V2/self-evaluate.mq5 rename to frontline/cluster-gold/self-evaluate.mq5 index b9d92e9..971c874 100644 --- a/frontline/cluster-0/_united-V2/self-evaluate.mq5 +++ b/frontline/cluster-gold/self-evaluate.mq5 @@ -13,7 +13,6 @@ #include #include #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); } //+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united-V2/PerformanceEvaluator.mqh b/frontline/united_template/_united-V2/PerformanceEvaluator.mqh deleted file mode 100644 index 991f603..0000000 --- a/frontline/united_template/_united-V2/PerformanceEvaluator.mqh +++ /dev/null @@ -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; -} - -//+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united-V2/Strategies/DarvasBoxStrategy.mqh b/frontline/united_template/_united-V2/Strategies/DarvasBoxStrategy.mqh index 6c2d7d5..62e6450 100644 --- a/frontline/united_template/_united-V2/Strategies/DarvasBoxStrategy.mqh +++ b/frontline/united_template/_united-V2/Strategies/DarvasBoxStrategy.mqh @@ -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) diff --git a/frontline/united_template/_united-V2/Strategies/SimpleTrendlineStrategy.mqh b/frontline/united_template/_united-V2/Strategies/SimpleTrendlineStrategy.mqh new file mode 100644 index 0000000..6341b88 --- /dev/null +++ b/frontline/united_template/_united-V2/Strategies/SimpleTrendlineStrategy.mqh @@ -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 diff --git a/frontline/united_template/_united-V2/main.mq5 b/frontline/united_template/_united-V2/main.mq5 index 35e7106..2cbee46 100644 --- a/frontline/united_template/_united-V2/main.mq5 +++ b/frontline/united_template/_united-V2/main.mq5 @@ -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 #include #include #include #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); } //+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united-V2/self-evaluate.mq5 b/frontline/united_template/_united-V2/self-evaluate.mq5 index b9d92e9..3b56a55 100644 --- a/frontline/united_template/_united-V2/self-evaluate.mq5 +++ b/frontline/united_template/_united-V2/self-evaluate.mq5 @@ -13,7 +13,6 @@ #include #include #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); diff --git a/frontline/united_template/_united-V2_exponential/PerformanceEvaluator.mqh b/frontline/united_template/_united-V2_exponential/PerformanceEvaluator.mqh deleted file mode 100644 index 991f603..0000000 --- a/frontline/united_template/_united-V2_exponential/PerformanceEvaluator.mqh +++ /dev/null @@ -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; -} - -//+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united/PerformanceEvaluator.mqh b/frontline/united_template/_united/PerformanceEvaluator.mqh deleted file mode 100644 index 991f603..0000000 --- a/frontline/united_template/_united/PerformanceEvaluator.mqh +++ /dev/null @@ -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; -} - -//+------------------------------------------------------------------+ diff --git a/frontline/united_template/_united/self-evaluate.mq5 b/frontline/united_template/_united/self-evaluate.mq5 index b9d92e9..3b56a55 100644 --- a/frontline/united_template/_united/self-evaluate.mq5 +++ b/frontline/united_template/_united/self-evaluate.mq5 @@ -13,7 +13,6 @@ #include #include #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); diff --git a/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/EMASlopeDistanceCocktailXAUUSD_Genetic_Optimization.set b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/EMASlopeDistanceCocktailXAUUSD_Genetic_Optimization.set new file mode 100644 index 0000000..b4dc394 --- /dev/null +++ b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/EMASlopeDistanceCocktailXAUUSD_Genetic_Optimization.set @@ -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 diff --git a/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/main.mq5 b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/main.mq5 new file mode 100644 index 0000000..f901630 --- /dev/null +++ b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/main.mq5 @@ -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 + #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()); + } + } + + //+------------------------------------------------------------------+ diff --git a/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/report.html b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/report.html new file mode 100644 index 0000000..8d9931f Binary files /dev/null and b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/report.html differ diff --git a/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/report.png b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/report.png new file mode 100644 index 0000000..c0bf45b Binary files /dev/null and b/frontline/units-sharpshooter/EMASlopeDistanceCocktailXAUUSD-sharpshooter/report.png differ diff --git a/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/123.set b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/123.set new file mode 100644 index 0000000..7db5038 --- /dev/null +++ b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/123.set @@ -0,0 +1,20 @@ +; saved on 2026.05.01 19:15:26 +; Expert Advisor inputs — synced with RSIScalpingNVDA-Escaper main.mq5 defaults +; Strategy Tester: Inputs tab -> Load +; +TimeFrame=15||15||15||16385||N +RSI_Period=8||1||5||21||N +RSI_Applied_Price=1||0||1||1||N +RSI_Overbought=36||1.0||20.0||45.0||N +RSI_Oversold=38||1.0||25.0||48.0||N +RSI_Target_Buy=90||1.0||70.0||95.0||N +RSI_Target_Sell=70||1.0||15.0||80.0||N +BarsToWait=11||1||2||12||Y +BarsToWait_Early=3||1||1||6||Y +RSI_Confirm_Buy=49||1.0||48.0||72.0||Y +RSI_Confirm_Sell=29||1.0||28.0||55.0||Y +MaxBars_Unconfirmed=1||1||4||40||Y +StopLoss_Points=0||5.0||0.500000||50.000000||N +LotSize=20||5.0||1.0||100.0||N +MagicNumber=12345||0||12345||12345||N +Slippage=3||0||3||3||N diff --git a/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/NVDA_Escaper_Genetic_Optimization.set b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/NVDA_Escaper_Genetic_Optimization.set new file mode 100644 index 0000000..82b6c09 --- /dev/null +++ b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/NVDA_Escaper_Genetic_Optimization.set @@ -0,0 +1,20 @@ +; saved on 2026.05.01 +; Same optimization ranges/flags as 123.set (Desktop); baseline matches main.mq5 defaults +; Strategy Tester: Inputs tab -> Load +; +TimeFrame=15||15||15||16385||N +RSI_Period=8||1||5||21||N +RSI_Applied_Price=1||0||1||1||N +RSI_Overbought=36||1.0||20.0||45.0||N +RSI_Oversold=38||1.0||25.0||48.0||N +RSI_Target_Buy=90||1.0||70.0||95.0||N +RSI_Target_Sell=70||1.0||15.0||80.0||N +BarsToWait=11||1||2||12||Y +BarsToWait_Early=3||1||1||6||Y +RSI_Confirm_Buy=49||1.0||48.0||72.0||Y +RSI_Confirm_Sell=29||1.0||28.0||55.0||Y +MaxBars_Unconfirmed=1||1||4||40||Y +StopLoss_Points=0||5.0||0.500000||50.000000||N +LotSize=20||5.0||1.0||100.0||N +MagicNumber=12345||0||12345||12345||N +Slippage=3||0||3||3||N diff --git a/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/NVDA_Genetic_Optimization.set b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/NVDA_Genetic_Optimization.set new file mode 100644 index 0000000..81b89e6 --- /dev/null +++ b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/NVDA_Genetic_Optimization.set @@ -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 diff --git a/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/OPTIMIZATION_GUIDE.md b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/OPTIMIZATION_GUIDE.md new file mode 100644 index 0000000..d7d5ba6 --- /dev/null +++ b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/OPTIMIZATION_GUIDE.md @@ -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 +``` diff --git a/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/main.mq5 b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/main.mq5 new file mode 100644 index 0000000..4c53ecd --- /dev/null +++ b/frontline/units-sharpshooter/RSIScalpingNVDA-Sharpshooter/main.mq5 @@ -0,0 +1,446 @@ +//+------------------------------------------------------------------+ +//| 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.02" + +#include +#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 = 11; // Bars RSI against (after momentum confirmed) +input int BarsToWait_Early = 3; // Bars RSI against before RSI confirms favorable move +input double RSI_Confirm_Buy = 49; // Buy: max RSI since entry must reach this once to confirm +input double RSI_Confirm_Sell = 29; // Sell: min RSI since entry must reach this once to confirm +input int MaxBars_Unconfirmed = 1; // If still unconfirmed after this many bars, use fastest adverse exit (0=off) +input double StopLoss_Points = 0; // Hard SL distance in points (0 = none) +input double LotSize = 20; // Lot Size +input int MagicNumber = 12345; // Magic Number +input int Slippage = 3; // Slippage in points + +//--- 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; +// Escaper: cut losers fast until trade proves RSI moved in our favor +int bars_in_trade = 0; +double rsi_extreme_since_entry = 0; +bool momentum_confirmed = false; +bool close_retry_pending = false; + +//+------------------------------------------------------------------+ +//| Reset escaper state (call when flat or after close) | +//+------------------------------------------------------------------+ +void ResetEscaperState() +{ + bars_in_trade = 0; + rsi_extreme_since_entry = 0; + momentum_confirmed = false; + rsi_against_position = false; + bars_against_count = 0; + close_retry_pending = false; +} + +//+------------------------------------------------------------------+ +//| EA lost sync vs broker: recover tracking / retry failed closes | +//| (must run every tick — new-bar-only logic skips session reopen) | +//+------------------------------------------------------------------+ +void TryCloseRetryAndResync() +{ + if(!position_open && PositionExistsByMagic(_Symbol, MagicNumber)) + { + ulong tix = GetPositionTicketByMagic(_Symbol, MagicNumber); + position_ticket = (long)tix; + position_open = true; + if(PositionSelectByTicket(tix)) + current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + ResetEscaperState(); + Print("RSIScalpingNVDA-Escaper: resynced open position from broker."); + } + + if(!close_retry_pending || !position_open) + return; + + ulong ticket_retry = GetPositionTicketByMagic(_Symbol, MagicNumber); + if(ticket_retry == 0) + { + ResetEscaperState(); + position_open = false; + position_ticket = 0; + return; + } + + if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)) + return; + + if(SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) + return; + + if(trade.PositionClose(ticket_retry)) + { + ResetEscaperState(); + position_open = false; + position_ticket = 0; + } +} + +//+------------------------------------------------------------------+ +//| Adverse bars required: tight until RSI shows favorable impulse | +//+------------------------------------------------------------------+ +int AdverseBarsRequired() +{ + int need = momentum_confirmed ? BarsToWait : BarsToWait_Early; + if(MaxBars_Unconfirmed > 0 && !momentum_confirmed && bars_in_trade >= MaxBars_Unconfirmed) + need = MathMin(need, 1); + return need; +} + +//+------------------------------------------------------------------+ +//| 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() +{ + TryCloseRetryAndResync(); + + // 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); + if(current_bar_time == last_bar_time) + { + return; // Still the same bar, don't process + } + + last_bar_time = current_bar_time; + + // Update RSI values + if(!UpdateRSI()) + { + return; + } + + // 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, 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; +} + +//+------------------------------------------------------------------+ +//| 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, MagicNumber)) + { + position_open = false; + position_ticket = 0; + ResetEscaperState(); + return; + } + + bars_in_trade++; + if(current_position_type == POSITION_TYPE_BUY) + { + rsi_extreme_since_entry = MathMax(rsi_extreme_since_entry, rsi_current); + momentum_confirmed = (rsi_extreme_since_entry >= RSI_Confirm_Buy); + } + else + { + rsi_extreme_since_entry = MathMin(rsi_extreme_since_entry, rsi_current); + momentum_confirmed = (rsi_extreme_since_entry <= RSI_Confirm_Sell); + } + + // Exit conditions based on RSI target + if(current_position_type == POSITION_TYPE_BUY) + { + int adverse_need = AdverseBarsRequired(); + // 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 faster before RSI confirms favorable impulse; full wait after confirmation + if(bars_against_count >= adverse_need) + { + 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) + { + int adverse_need = AdverseBarsRequired(); + // 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++; + } + + if(bars_against_count >= adverse_need) + { + 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, MagicNumber)) + { + return; // Position already exists for this EA + } + + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double sl = 0; + if(StopLoss_Points > 0) + { + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + sl = NormalizeDouble(ask - StopLoss_Points * pt, dig); + } + + if(trade.Buy(LotSize, _Symbol, ask, sl, 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, MagicNumber)) + { + position_ticket = new_ticket; + position_open = true; + current_position_type = POSITION_TYPE_BUY; + rsi_extreme_since_entry = rsi_current; + momentum_confirmed = (rsi_current >= RSI_Confirm_Buy); + rsi_against_position = false; + bars_against_count = 0; + bars_in_trade = 0; + } + 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, MagicNumber)) + { + return; // Position already exists for this EA + } + + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double sl = 0; + if(StopLoss_Points > 0) + { + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + int dig = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + sl = NormalizeDouble(bid + StopLoss_Points * pt, dig); + } + + if(trade.Sell(LotSize, _Symbol, bid, sl, 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, MagicNumber)) + { + position_ticket = new_ticket; + position_open = true; + current_position_type = POSITION_TYPE_SELL; + rsi_extreme_since_entry = rsi_current; + momentum_confirmed = (rsi_current <= RSI_Confirm_Sell); + rsi_against_position = false; + bars_against_count = 0; + bars_in_trade = 0; + } + else + { + Print("Error: Position opened but doesn't match EA magic number or symbol"); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Close current position | +//+------------------------------------------------------------------+ +void ClosePosition() +{ + ulong ticket = GetPositionTicketByMagic(_Symbol, MagicNumber); + if(ticket == 0) + { + position_open = false; + position_ticket = 0; + ResetEscaperState(); + return; + } + + if(!trade.PositionClose(ticket)) + { + uint rc = trade.ResultRetcode(); + close_retry_pending = true; + PrintFormat("RSIScalpingNVDA-Escaper: close failed retcode=%u — keeping position, will retry when session allows.", rc); + return; + } + + position_open = false; + position_ticket = 0; + ResetEscaperState(); +} diff --git a/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/main.mq5 b/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/main.mq5 new file mode 100644 index 0000000..6d849c0 --- /dev/null +++ b/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/main.mq5 @@ -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 +#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()); + } +} + +//+------------------------------------------------------------------+ diff --git a/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/report.html b/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/report.html new file mode 100644 index 0000000..8d9931f Binary files /dev/null and b/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/report.html differ diff --git a/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/report.png b/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/report.png new file mode 100644 index 0000000..c0bf45b Binary files /dev/null and b/frontline/units-trailing/EMASlopeDistanceCocktailXAUUSD-trailing/report.png differ diff --git a/frontline/units-trailing/RSIScalpingBTCUSD-trailing/main.mq5 b/frontline/units-trailing/RSIScalpingBTCUSD-trailing/main.mq5 new file mode 100644 index 0000000..b34e0a2 --- /dev/null +++ b/frontline/units-trailing/RSIScalpingBTCUSD-trailing/main.mq5 @@ -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 +#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(); + } + } +} diff --git a/frontline/units-trailing/RSIScalpingBTCUSD-trailing/report.html b/frontline/units-trailing/RSIScalpingBTCUSD-trailing/report.html new file mode 100644 index 0000000..b512b3d Binary files /dev/null and b/frontline/units-trailing/RSIScalpingBTCUSD-trailing/report.html differ diff --git a/frontline/units-trailing/RSIScalpingBTCUSD-trailing/report.png b/frontline/units-trailing/RSIScalpingBTCUSD-trailing/report.png new file mode 100644 index 0000000..52287c6 Binary files /dev/null and b/frontline/units-trailing/RSIScalpingBTCUSD-trailing/report.png differ diff --git a/frontline/units-trailing/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization.set b/frontline/units-trailing/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization.set new file mode 100644 index 0000000..81b89e6 --- /dev/null +++ b/frontline/units-trailing/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization.set @@ -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 diff --git a/frontline/units-trailing/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization_Alternative.set b/frontline/units-trailing/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization_Alternative.set new file mode 100644 index 0000000..3fca2be --- /dev/null +++ b/frontline/units-trailing/RSIScalpingNVDA-trailing/NVDA_Genetic_Optimization_Alternative.set @@ -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 diff --git a/frontline/units-trailing/RSIScalpingNVDA-trailing/OPTIMIZATION_GUIDE.md b/frontline/units-trailing/RSIScalpingNVDA-trailing/OPTIMIZATION_GUIDE.md new file mode 100644 index 0000000..d7d5ba6 --- /dev/null +++ b/frontline/units-trailing/RSIScalpingNVDA-trailing/OPTIMIZATION_GUIDE.md @@ -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 +``` diff --git a/frontline/units-trailing/RSIScalpingNVDA-trailing/main.mq5 b/frontline/units-trailing/RSIScalpingNVDA-trailing/main.mq5 new file mode 100644 index 0000000..5950bce --- /dev/null +++ b/frontline/units-trailing/RSIScalpingNVDA-trailing/main.mq5 @@ -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 +#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; + } +} diff --git a/frontline/units-trailing/RSIScalpingTSLA-trailing/main.mq5 b/frontline/units-trailing/RSIScalpingTSLA-trailing/main.mq5 new file mode 100644 index 0000000..1648b9a --- /dev/null +++ b/frontline/units-trailing/RSIScalpingTSLA-trailing/main.mq5 @@ -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 +#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; + } +} diff --git a/frontline/units-trailing/RSIScalpingTSLA-trailing/report.html b/frontline/units-trailing/RSIScalpingTSLA-trailing/report.html new file mode 100644 index 0000000..950f95b Binary files /dev/null and b/frontline/units-trailing/RSIScalpingTSLA-trailing/report.html differ diff --git a/frontline/units-trailing/RSIScalpingTSLA-trailing/report.png b/frontline/units-trailing/RSIScalpingTSLA-trailing/report.png new file mode 100644 index 0000000..5a26c1d Binary files /dev/null and b/frontline/units-trailing/RSIScalpingTSLA-trailing/report.png differ diff --git a/frontline/units-trailing/RSIScalpingXAUUSD-trailing/123.set b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/123.set new file mode 100644 index 0000000..0c7f7c1 --- /dev/null +++ b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/123.set @@ -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 diff --git a/frontline/units-trailing/RSIScalpingXAUUSD-trailing/main.mq5 b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/main.mq5 new file mode 100644 index 0000000..96b6f2f --- /dev/null +++ b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/main.mq5 @@ -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 +#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()); +} diff --git a/frontline/units-trailing/RSIScalpingXAUUSD-trailing/report.html b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/report.html new file mode 100644 index 0000000..203dcf4 Binary files /dev/null and b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/report.html differ diff --git a/frontline/units-trailing/RSIScalpingXAUUSD-trailing/report.png b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/report.png new file mode 100644 index 0000000..c2c91f2 Binary files /dev/null and b/frontline/units-trailing/RSIScalpingXAUUSD-trailing/report.png differ diff --git a/frontline/units/RSIScalpingBTCUSD/main.mq5 b/frontline/units/RSIScalpingBTCUSD/main.mq5 index 7866af1..ea69523 100644 --- a/frontline/units/RSIScalpingBTCUSD/main.mq5 +++ b/frontline/units/RSIScalpingBTCUSD/main.mq5 @@ -23,6 +23,14 @@ 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 + //--- Global variables CTrade trade; int rsi_handle; @@ -35,6 +43,12 @@ 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(); + //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ @@ -77,21 +91,38 @@ void OnTick() { return; } - + // Check if this is a new bar datetime current_bar_time = iTime(_Symbol, TimeFrame, 0); - if(current_bar_time == last_bar_time) + bool is_new_bar = (current_bar_time != last_bar_time); + bool in_position = position_open || PositionExistsByMagic(_Symbol, MagicNumber); + + // While flat, process only on new bars. While in position, allow intrabar reversal escape checks. + if(!in_position && !is_new_bar) { - return; // Still the same bar, don't process + return; } - - last_bar_time = current_bar_time; - + // Update RSI values if(!UpdateRSI()) { return; } + + if(in_position && UseReversalEscape) + { + TryReversalEscape(); + } + + 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(); @@ -120,6 +151,155 @@ bool UpdateRSI() 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, MagicNumber); + if(live_ticket == 0) + return; + if(!PositionSelectByTicketSymbolAndMagic(live_ticket, _Symbol, 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))); +} + +//+------------------------------------------------------------------+ +//| 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, 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, 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 | //+------------------------------------------------------------------+ @@ -133,10 +313,7 @@ void CheckExistingPosition() // Check if position still exists with correct magic number AND symbol for THIS EA if(!PositionSelectByTicketSymbolAndMagic(position_ticket, _Symbol, MagicNumber)) { - position_open = false; - position_ticket = 0; - rsi_against_position = false; - bars_against_count = 0; + ResetPositionTracking(); return; } @@ -308,20 +485,24 @@ void OpenSellPosition() //+------------------------------------------------------------------+ void ClosePosition() { + bool position_exists_before_close = PositionExistsByMagic(_Symbol, 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, MagicNumber)) { - position_open = false; - position_ticket = 0; - rsi_against_position = false; - bars_against_count = 0; + ResetPositionTracking(); } 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; + // Keep tracking when close fails (e.g. market closed); retry on next bar. + if(!PositionExistsByMagic(_Symbol, MagicNumber)) + { + ResetPositionTracking(); + } } } diff --git a/frontline/units/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 b/frontline/units/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 index c869eeb..77ed295 100644 --- a/frontline/units/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 +++ b/frontline/units/SimpleTrendlineXAUUSD/SimpleTrendline.mq5 @@ -13,12 +13,15 @@ input double InpBreakBuffer = 110; // Break confirmation 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 { @@ -180,6 +183,75 @@ bool GetCurrentPosition(long &type, double &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; @@ -234,6 +306,16 @@ void TryPullbackEntry(const TrendlineModel &m) 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"); } } @@ -245,6 +327,16 @@ void TryPullbackEntry(const TrendlineModel &m) 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"); } } diff --git a/lab/EAs/RSILadderXAUUSD/main.mq5 b/lab/EAs/RSILadderXAUUSD/main.mq5 new file mode 100644 index 0000000..bf8b093 --- /dev/null +++ b/lab/EAs/RSILadderXAUUSD/main.mq5 @@ -0,0 +1,437 @@ +//+------------------------------------------------------------------+ +//| RSIScalping.mq5 | +//| Copyright 2025, MetaQuotes Ltd. | +//| https://www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2025, MetaQuotes Ltd." +#property link "https://www.mql5.com" +#property version "1.03" + +#include +#include "../_united/MagicNumberHelpers.mqh" + +//--- Input parameters +input ENUM_TIMEFRAMES TimeFrame = PERIOD_H1; // Timeframe for Analysis +input int RSI_Period = 14; // RSI Period +input ENUM_APPLIED_PRICE RSI_Applied_Price = PRICE_CLOSE; // RSI Applied Price +input double RSI_Overbought = 71; // RSI Overbought Level +input double RSI_Oversold = 57; // RSI Oversold Level +input bool UseEntrySlopeFilter = false; // require RSI momentum on entry bars +input double EntryMinSlopePerBar = 1.0; // minimum RSI delta per bar for entry +input double RSI_Target_Buy = 80; // RSI Target for Buy Exit +input double RSI_Target_Sell = 57; // RSI Target for Sell Exit +input int BarsToWait = 4; // Bars to wait when RSI goes against position +input bool ExitOnAdverseRsiBarStep = true; // new bar: exit if last closed RSI vs prior closed is against trade +input double LotSize = 0.1; // Lot Size +input int MagicNumber = 129102315; // Magic Number +input int Slippage = 3; // Slippage in points + +input group "=== Reversal escape (intrabar, multi-signal) ===" +input bool UseReversalEscape = true; // run while in position every tick +input int ReversalATRPeriod = 14; // ATR lookback on signal timeframe +input double ReversalAdverseAtrMult = 5.25; // close if price vs entry >= this * ATR +input int ReversalSignsRequired = 2; // how many independent signs must align +input double ReversalRsiVelocity = 16.0; // RSI points drop (long) / rise (short) vs prior buffer +input double ReversalBodyAtrMult = 5.1; // last closed bar body >= this * ATR counts as one sign + +//--- Global variables +CTrade trade; +int rsi_handle; +double rsi_buffer[]; +double rsi_prev, rsi_current, rsi_two_bars_ago; +bool position_open = false; +int position_ticket = 0; +ENUM_POSITION_TYPE current_position_type = POSITION_TYPE_BUY; +datetime last_bar_time = 0; +bool rsi_against_position = false; +int bars_against_count = 0; + +//+------------------------------------------------------------------+ +//| Expert initialization function | +//+------------------------------------------------------------------+ +int OnInit() +{ + // Initialize RSI indicator + rsi_handle = iRSI(_Symbol, TimeFrame, RSI_Period, RSI_Applied_Price); + if(rsi_handle == INVALID_HANDLE) + { + return(INIT_FAILED); + } + + // Initialize trade object + trade.SetExpertMagicNumber(MagicNumber); + trade.SetDeviationInPoints(Slippage); + trade.SetTypeFilling(ORDER_FILLING_FOK); + + // Allocate arrays + ArraySetAsSeries(rsi_buffer, true); + + return(INIT_SUCCEEDED); +} + +//+------------------------------------------------------------------+ +//| Expert deinitialization function | +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + if(rsi_handle != INVALID_HANDLE) + IndicatorRelease(rsi_handle); +} + +//+------------------------------------------------------------------+ +//| Expert tick function | +//+------------------------------------------------------------------+ +void OnTick() +{ + if(Bars(_Symbol, TimeFrame) < RSI_Period + 2) + return; + + const datetime current_bar_time = iTime(_Symbol, TimeFrame, 0); + const bool new_bar = (current_bar_time != last_bar_time); + const bool in_pos = position_open || PositionExistsByMagic(_Symbol, (ulong)MagicNumber); + + if(!in_pos && !new_bar) + return; + + if(!UpdateRSI()) + return; + + if(in_pos && UseReversalEscape) + TryReversalEscape(); + + if(!new_bar) + return; + + last_bar_time = current_bar_time; + + ResyncPositionFromMarket(); + CheckExistingPosition(); + + if(!position_open && !PositionExistsByMagic(_Symbol, (ulong)MagicNumber)) + CheckEntrySignals(); +} + +//+------------------------------------------------------------------+ +//| Update RSI values | +//+------------------------------------------------------------------+ +bool UpdateRSI() +{ + if(CopyBuffer(rsi_handle, 0, 0, 3, rsi_buffer) < 3) + { + return false; + } + + rsi_current = rsi_buffer[0]; // Current bar + rsi_prev = rsi_buffer[1]; // Previous bar + rsi_two_bars_ago = rsi_buffer[2]; // Two bars ago + + return true; +} + +//+------------------------------------------------------------------+ +//| Wilder ATR in price units (signal timeframe) | +//+------------------------------------------------------------------+ +double ATRPriceOnTF(const int period) +{ + if(period < 1) + return 0.0; + MqlRates rates[]; + const int need = period + 2; + if(CopyRates(_Symbol, TimeFrame, 0, need, rates) < need) + return 0.0; + ArraySetAsSeries(rates, true); + double sum = 0.0; + for(int i = 1; i <= period; i++) + { + const double hl = rates[i].high - rates[i].low; + const double hc = MathAbs(rates[i].high - rates[i + 1].close); + const double lc = MathAbs(rates[i].low - rates[i + 1].close); + sum += MathMax(hl, MathMax(hc, lc)); + } + return sum / (double)period; +} + +//+------------------------------------------------------------------+ +//| Independent adverse signs (need ReversalSignsRequired to exit) | +//+------------------------------------------------------------------+ +int CountReversalEscapeSigns(const ENUM_POSITION_TYPE ptype, const double atr) +{ + if(atr <= 0.0) + return 0; + + const double entry = PositionGetDouble(POSITION_PRICE_OPEN); + const double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + const double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + int signs = 0; + + if(ptype == POSITION_TYPE_BUY) + { + if(entry - bid >= ReversalAdverseAtrMult * atr) + signs++; + if(rsi_prev - rsi_current >= ReversalRsiVelocity) + signs++; + } + else if(ptype == POSITION_TYPE_SELL) + { + if(ask - entry >= ReversalAdverseAtrMult * atr) + signs++; + if(rsi_current - rsi_prev >= ReversalRsiVelocity) + signs++; + } + else + return 0; + + MqlRates r[]; + if(CopyRates(_Symbol, TimeFrame, 0, 4, r) >= 4) + { + ArraySetAsSeries(r, true); + const double body = MathAbs(r[1].close - r[1].open); + if(body >= ReversalBodyAtrMult * atr) + { + if(ptype == POSITION_TYPE_BUY && r[1].close < r[1].open) + signs++; + else if(ptype == POSITION_TYPE_SELL && r[1].close > r[1].open) + signs++; + } + if(ptype == POSITION_TYPE_BUY) + { + if(r[1].close < r[2].close && r[2].close < r[3].close) + signs++; + } + else + { + if(r[1].close > r[2].close && r[2].close > r[3].close) + signs++; + } + } + + return signs; +} + +//+------------------------------------------------------------------+ +//| Cut losers fast on violent reversals (evaluated every tick) | +//+------------------------------------------------------------------+ +void TryReversalEscape() +{ + if(!PositionSelectByMagic(_Symbol, (ulong)MagicNumber)) + return; + + const ENUM_POSITION_TYPE ptype = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); + const double atr = ATRPriceOnTF(ReversalATRPeriod); + if(atr <= 0.0) + return; + + const int n = CountReversalEscapeSigns(ptype, atr); + if(n < ReversalSignsRequired) + return; + + ClosePosition(); + Print("RSIScalpingXAUUSD: reversal escape signs=", n, " need=", ReversalSignsRequired, + " ATR=", DoubleToString(atr, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS))); +} + +void ResyncPositionFromMarket() +{ + if(position_open) + return; + ulong t = GetPositionTicketByMagic(_Symbol, (ulong)MagicNumber); + if(t == 0 || !PositionSelectByTicket(t)) + return; + position_ticket = (int)t; + position_open = true; + current_position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); +} + +//+------------------------------------------------------------------+ +//| Check existing position for exit conditions | +//+------------------------------------------------------------------+ +void CheckExistingPosition() +{ + if(!position_open) + { + return; + } + + // Check if position still exists with correct magic number + if(!PositionSelectByTicketAndMagic(position_ticket, MagicNumber)) + { + position_open = false; + position_ticket = 0; + rsi_against_position = false; + bars_against_count = 0; + return; + } + + // On each new bar: last completed RSI vs the bar before — exit if that step is adverse to the position + if(ExitOnAdverseRsiBarStep) + { + if(current_position_type == POSITION_TYPE_BUY && rsi_prev < rsi_two_bars_ago) + { + ClosePosition(); + return; + } + if(current_position_type == POSITION_TYPE_SELL && rsi_prev > rsi_two_bars_ago) + { + ClosePosition(); + return; + } + } + + // Exit conditions based on RSI target + if(current_position_type == POSITION_TYPE_BUY) + { + // Check if RSI is against the position (below oversold) + if(rsi_current < RSI_Oversold) + { + if(!rsi_against_position) + { + rsi_against_position = true; + bars_against_count = 1; + } + else + { + bars_against_count++; + } + + // Close position if RSI has been against for Y bars + if(bars_against_count >= BarsToWait) + { + ClosePosition(); + return; + } + } + else + { + // RSI is no longer against the position, reset counter + if(rsi_against_position) + { + rsi_against_position = false; + bars_against_count = 0; + } + + // Exit long position when RSI reaches buy target + if(rsi_current >= RSI_Target_Buy) + { + ClosePosition(); + } + } + } + else if(current_position_type == POSITION_TYPE_SELL) + { + // Check if RSI is against the position (above overbought) + if(rsi_current > RSI_Overbought) + { + if(!rsi_against_position) + { + rsi_against_position = true; + bars_against_count = 1; + } + else + { + bars_against_count++; + } + + // Close position if RSI has been against for Y bars + if(bars_against_count >= BarsToWait) + { + ClosePosition(); + return; + } + } + else + { + // RSI is no longer against the position, reset counter + if(rsi_against_position) + { + rsi_against_position = false; + bars_against_count = 0; + } + + // Exit short position when RSI reaches sell target + if(rsi_current <= RSI_Target_Sell) + { + ClosePosition(); + } + } + } +} + +//+------------------------------------------------------------------+ +//| Check for entry signals | +//+------------------------------------------------------------------+ +void CheckEntrySignals() +{ + const double upSlope1 = rsi_prev - rsi_two_bars_ago; // older->prev + const double upSlope2 = rsi_current - rsi_prev; // prev->current + const double dnSlope1 = rsi_two_bars_ago - rsi_prev; // older->prev + const double dnSlope2 = rsi_prev - rsi_current; // prev->current + const bool buySlopeOk = (!UseEntrySlopeFilter) || (upSlope1 >= EntryMinSlopePerBar && upSlope2 >= EntryMinSlopePerBar); + const bool sellSlopeOk = (!UseEntrySlopeFilter) || (dnSlope1 >= EntryMinSlopePerBar && dnSlope2 >= EntryMinSlopePerBar); + + // Buy signal: RSI crosses from oversold to above oversold (checking the actual crossover) + if(rsi_two_bars_ago <= RSI_Oversold && rsi_prev > RSI_Oversold && buySlopeOk) + { + OpenBuyPosition(); + } + + // Sell signal: RSI crosses from overbought to below overbought (checking the actual crossover) + if(rsi_two_bars_ago >= RSI_Overbought && rsi_prev < RSI_Overbought && sellSlopeOk) + { + OpenSellPosition(); + } +} + +//+------------------------------------------------------------------+ +//| Open buy position | +//+------------------------------------------------------------------+ +void OpenBuyPosition() +{ + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + + if(trade.Buy(LotSize, _Symbol, ask, 0, 0, "RSI Scalping Buy")) + { + position_ticket = trade.ResultOrder(); + position_open = true; + current_position_type = POSITION_TYPE_BUY; + } +} + +//+------------------------------------------------------------------+ +//| Open sell position | +//+------------------------------------------------------------------+ +void OpenSellPosition() +{ + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + if(trade.Sell(LotSize, _Symbol, bid, 0, 0, "RSI Scalping Sell")) + { + position_ticket = trade.ResultOrder(); + position_open = true; + current_position_type = POSITION_TYPE_SELL; + } +} + +//+------------------------------------------------------------------+ +//| Close current position | +//+------------------------------------------------------------------+ +void ClosePosition() +{ + if(ClosePositionByMagic(trade, _Symbol, (ulong)MagicNumber)) + { + position_open = false; + position_ticket = 0; + rsi_against_position = false; + bars_against_count = 0; + return; + } + if(!PositionExistsByMagic(_Symbol, (ulong)MagicNumber)) + { + position_open = false; + position_ticket = 0; + rsi_against_position = false; + bars_against_count = 0; + return; + } + Print("RSIScalpingXAUUSD: close failed (will retry on next bar). retcode=", + trade.ResultRetcode(), " lastError=", GetLastError()); +} diff --git a/lab/EAs/RSILadderXAUUSD/report.html b/lab/EAs/RSILadderXAUUSD/report.html new file mode 100644 index 0000000..203dcf4 Binary files /dev/null and b/lab/EAs/RSILadderXAUUSD/report.html differ diff --git a/lab/EAs/RSILadderXAUUSD/report.png b/lab/EAs/RSILadderXAUUSD/report.png new file mode 100644 index 0000000..c2c91f2 Binary files /dev/null and b/lab/EAs/RSILadderXAUUSD/report.png differ diff --git a/lab/EAs/SimpleTrendlineTSLA/SimpleTrendline.mq5 b/lab/EAs/SimpleTrendlineTSLA/SimpleTrendline.mq5 new file mode 100644 index 0000000..c869eeb --- /dev/null +++ b/lab/EAs/SimpleTrendlineTSLA/SimpleTrendline.mq5 @@ -0,0 +1,284 @@ +#property strict +#property version "1.00" + +#include + +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 + +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); +} diff --git a/lab/EAs/SimpleTrendlineTSLA/SimpleTrendline_optimization.set b/lab/EAs/SimpleTrendlineTSLA/SimpleTrendline_optimization.set new file mode 100644 index 0000000..29aaeca --- /dev/null +++ b/lab/EAs/SimpleTrendlineTSLA/SimpleTrendline_optimization.set @@ -0,0 +1,14 @@ +; SimpleTrendline.mq5 optimization preset (TSLA-focused) +; Strategy Tester -> Inputs -> Load +; Focus: trendline pullback + break exits on TSLA volatility (no broker SL/TP) +; +InpHigherTF=16385||16385||1||16387||Y +InpMAPeriod=55||30||5||160||Y +InpMAMethod=1||0||1||3||Y +InpAppliedPrice=0||0||1||6||Y +InpHTFBarsToScan=500||250||50||1500||Y +InpLineTouchTolerance=65.0||20.0||5.0||180.0||Y +InpBreakBuffer=22.0||8.0||2.0||70.0||Y +InpLots=0.10||0.10||0.01||0.10||N +InpMagic=26042501||26042501||1||26042501||N +InpDrawTrendline=false||false||0||true||N diff --git a/lab/EAs/catcher-optimization.set b/lab/EAs/catcher-optimization.set new file mode 100644 index 0000000..f7d4f7e --- /dev/null +++ b/lab/EAs/catcher-optimization.set @@ -0,0 +1,21 @@ +; saved on 2026.04.25 +; optimization profile for double-top-bottom-catcher.mq5 +; load this in Strategy Tester > Inputs tab > Load +; +InpTf=1||0||0||49153||N +InpHtf=5||0||0||49153||N +InpUseHtfFilter=true||false||0||true||Y +InpEmaFast=9||7||1||14||Y +InpEmaSlow=21||18||1||34||Y +InpPivotLeft=2||1||1||4||Y +InpPivotRight=2||1||1||4||Y +InpPatternLookbackBars=180||100||20||300||Y +InpMinPatternSeparation=6||4||1||12||Y +InpMaxPatternSeparation=50||20||5||80||Y +InpTopBottomTolPts=120.0||50.0||10.0||250.0||Y +InpMinEmaPriceDistPts=80.0||20.0||10.0||180.0||Y +InpPrevLevelLookback=120||50||10||250||Y +InpLots=0.01||0.01||0.0||0.01||N +InpSlBufferPts=25||10||5||60||Y +InpMagic=930101||930101||1||9301010||N +InpSlippagePts=30||10||5||50||Y diff --git a/lab/EAs/double-top-bottom-catcher.mq5 b/lab/EAs/double-top-bottom-catcher.mq5 new file mode 100644 index 0000000..9286bed --- /dev/null +++ b/lab/EAs/double-top-bottom-catcher.mq5 @@ -0,0 +1,350 @@ +//+------------------------------------------------------------------+ +//| double-top-bottom-catcher.mq5 | +//| Lab EA: Double top/bottom catcher with EMA distance + HTF trend | +//+------------------------------------------------------------------+ +#property copyright "Lab" +#property version "1.00" + +#include + +input ENUM_TIMEFRAMES InpTf = PERIOD_M1; // Signal timeframe +input ENUM_TIMEFRAMES InpHtf = PERIOD_M5; // Higher timeframe +input bool InpUseHtfFilter = true; // Require HTF trend alignment +input int InpEmaFast = 9; // Fast EMA +input int InpEmaSlow = 21; // Slow EMA + +input int InpPivotLeft = 2; // Pivot bars left +input int InpPivotRight = 2; // Pivot bars right +input int InpPatternLookbackBars = 180; // Search range for patterns +input int InpMinPatternSeparation = 6; // Min bars between tops/bottoms +input int InpMaxPatternSeparation = 50; // Max bars between tops/bottoms +input double InpTopBottomTolPts = 120; // Max diff between top/top or bottom/bottom +input double InpMinEmaPriceDistPts = 80; // Min stretch from EMA at 2nd touch +input int InpPrevLevelLookback = 120; // Lookback to find previous support/resistance + +input double InpLots = 0.01; +input int InpSlBufferPts = 25; // SL buffer beyond pattern extreme +input ulong InpMagic = 20260425; +input int InpSlippagePts = 30; + +CTrade g_trade; + +int g_hEmaFast = INVALID_HANDLE; +int g_hEmaSlow = INVALID_HANDLE; +int g_hEmaFastHtf = INVALID_HANDLE; +int g_hEmaSlowHtf = INVALID_HANDLE; + +double g_emaFast[]; +double g_emaSlow[]; +double g_emaFastHtf[]; +double g_emaSlowHtf[]; + +int OnInit() +{ + g_trade.SetExpertMagicNumber(InpMagic); + g_trade.SetDeviationInPoints(InpSlippagePts); + SetTradeFillingBySymbol(); + + g_hEmaFast = iMA(_Symbol, InpTf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE); + g_hEmaSlow = iMA(_Symbol, InpTf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE); + g_hEmaFastHtf = iMA(_Symbol, InpHtf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE); + g_hEmaSlowHtf = iMA(_Symbol, InpHtf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE); + + if(g_hEmaFast == INVALID_HANDLE || g_hEmaSlow == INVALID_HANDLE || + g_hEmaFastHtf == INVALID_HANDLE || g_hEmaSlowHtf == INVALID_HANDLE) + return INIT_FAILED; + + ArraySetAsSeries(g_emaFast, true); + ArraySetAsSeries(g_emaSlow, true); + ArraySetAsSeries(g_emaFastHtf, true); + ArraySetAsSeries(g_emaSlowHtf, true); + + return INIT_SUCCEEDED; +} + +void OnDeinit(const int reason) +{ + if(g_hEmaFast != INVALID_HANDLE) IndicatorRelease(g_hEmaFast); + if(g_hEmaSlow != INVALID_HANDLE) IndicatorRelease(g_hEmaSlow); + if(g_hEmaFastHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaFastHtf); + if(g_hEmaSlowHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaSlowHtf); +} + +void OnTick() +{ + static datetime lastBar = 0; + datetime barTime = iTime(_Symbol, InpTf, 0); + if(barTime == lastBar) + return; + lastBar = barTime; + + const int need = MathMax(260, InpPatternLookbackBars + InpPrevLevelLookback + 20); + if(CopyBuffer(g_hEmaFast, 0, 0, need, g_emaFast) < need) return; + if(CopyBuffer(g_hEmaSlow, 0, 0, need, g_emaSlow) < need) return; + if(CopyBuffer(g_hEmaFastHtf, 0, 0, 5, g_emaFastHtf) < 5) return; + if(CopyBuffer(g_hEmaSlowHtf, 0, 0, 5, g_emaSlowHtf) < 5) return; + + if(PositionExistsForMagic()) + return; + + TryEnterLongDoubleBottom(); + if(!PositionExistsForMagic()) + TryEnterShortDoubleTop(); +} + +void TryEnterLongDoubleBottom() +{ + int firstBottom = -1; // older + int secondBottom = -1; // newer + double neckline = 0.0; + double lowA = 0.0; + double lowB = 0.0; + + if(!FindDoubleBottom(firstBottom, secondBottom, neckline, lowA, lowB)) + return; + + const int c = 1; + double close1 = iClose(_Symbol, InpTf, c); + if(close1 <= neckline) + return; // wait for neckline break confirmation + + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + double stretched = g_emaFast[secondBottom] - lowB; + if(stretched < InpMinEmaPriceDistPts * pt) + return; // no enough EMA/price displacement for reversal + + if(close1 <= g_emaFast[c]) + return; // keep confirmation strict: close above EMA fast + + if(InpUseHtfFilter && !(g_emaFastHtf[c] > g_emaSlowHtf[c])) + return; + + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + double sl = MathMin(lowA, lowB) - InpSlBufferPts * pt; + double tp = FindPreviousResistance(firstBottom); + if(tp <= ask + pt) + return; + if(sl >= ask - pt) + return; + + sl = NormalizeDouble(sl, digits); + tp = NormalizeDouble(tp, digits); + g_trade.Buy(InpLots, _Symbol, ask, sl, tp, "Double bottom"); +} + +void TryEnterShortDoubleTop() +{ + int firstTop = -1; // older + int secondTop = -1; // newer + double neckline = 0.0; + double hiA = 0.0; + double hiB = 0.0; + + if(!FindDoubleTop(firstTop, secondTop, neckline, hiA, hiB)) + return; + + const int c = 1; + double close1 = iClose(_Symbol, InpTf, c); + if(close1 >= neckline) + return; // wait for neckline break confirmation + + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + double stretched = hiB - g_emaFast[secondTop]; + if(stretched < InpMinEmaPriceDistPts * pt) + return; // no enough EMA/price displacement for reversal + + if(close1 >= g_emaFast[c]) + return; // keep confirmation strict: close below EMA fast + + if(InpUseHtfFilter && !(g_emaFastHtf[c] < g_emaSlowHtf[c])) + return; + + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + double sl = MathMax(hiA, hiB) + InpSlBufferPts * pt; + double tp = FindPreviousSupport(firstTop); + if(tp >= bid - pt) + return; + if(sl <= bid + pt) + return; + + sl = NormalizeDouble(sl, digits); + tp = NormalizeDouble(tp, digits); + g_trade.Sell(InpLots, _Symbol, bid, sl, tp, "Double top"); +} + +bool FindDoubleBottom(int &firstBottom, int &secondBottom, double &neckline, double &lowA, double &lowB) +{ + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + int minShift = InpPivotRight + 1; + int maxShift = MathMin(InpPatternLookbackBars, Bars(_Symbol, InpTf) - InpPivotLeft - 2); + if(maxShift <= minShift + InpPivotLeft + InpPivotRight + 2) + return false; + + for(int newer = minShift; newer <= maxShift; newer++) + { + if(!IsPivotLow(newer)) + continue; + for(int older = newer + InpMinPatternSeparation; older <= maxShift; older++) + { + int sep = older - newer; + if(sep > InpMaxPatternSeparation) + break; + if(!IsPivotLow(older)) + continue; + + double lNew = iLow(_Symbol, InpTf, newer); + double lOld = iLow(_Symbol, InpTf, older); + if(MathAbs(lNew - lOld) > InpTopBottomTolPts * pt) + continue; + + double neck = HighestHighBetween(newer, older); + if(neck <= 0.0) + continue; + + firstBottom = older; + secondBottom = newer; + lowA = lOld; + lowB = lNew; + neckline = neck; + return true; + } + } + return false; +} + +bool FindDoubleTop(int &firstTop, int &secondTop, double &neckline, double &hiA, double &hiB) +{ + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + int minShift = InpPivotRight + 1; + int maxShift = MathMin(InpPatternLookbackBars, Bars(_Symbol, InpTf) - InpPivotLeft - 2); + if(maxShift <= minShift + InpPivotLeft + InpPivotRight + 2) + return false; + + for(int newer = minShift; newer <= maxShift; newer++) + { + if(!IsPivotHigh(newer)) + continue; + for(int older = newer + InpMinPatternSeparation; older <= maxShift; older++) + { + int sep = older - newer; + if(sep > InpMaxPatternSeparation) + break; + if(!IsPivotHigh(older)) + continue; + + double hNew = iHigh(_Symbol, InpTf, newer); + double hOld = iHigh(_Symbol, InpTf, older); + if(MathAbs(hNew - hOld) > InpTopBottomTolPts * pt) + continue; + + double neck = LowestLowBetween(newer, older); + if(neck <= 0.0) + continue; + + firstTop = older; + secondTop = newer; + hiA = hOld; + hiB = hNew; + neckline = neck; + return true; + } + } + return false; +} + +bool IsPivotLow(const int shift) +{ + double v = iLow(_Symbol, InpTf, shift); + for(int i = 1; i <= InpPivotLeft; i++) + if(iLow(_Symbol, InpTf, shift + i) <= v) return false; + for(int i = 1; i <= InpPivotRight; i++) + if(iLow(_Symbol, InpTf, shift - i) < v) return false; + return true; +} + +bool IsPivotHigh(const int shift) +{ + double v = iHigh(_Symbol, InpTf, shift); + for(int i = 1; i <= InpPivotLeft; i++) + if(iHigh(_Symbol, InpTf, shift + i) >= v) return false; + for(int i = 1; i <= InpPivotRight; i++) + if(iHigh(_Symbol, InpTf, shift - i) > v) return false; + return true; +} + +double HighestHighBetween(const int shiftA, const int shiftB) +{ + int from = MathMin(shiftA, shiftB); + int to = MathMax(shiftA, shiftB); + double v = -DBL_MAX; + for(int i = from; i <= to; i++) + v = MathMax(v, iHigh(_Symbol, InpTf, i)); + return v; +} + +double LowestLowBetween(const int shiftA, const int shiftB) +{ + int from = MathMin(shiftA, shiftB); + int to = MathMax(shiftA, shiftB); + double v = DBL_MAX; + for(int i = from; i <= to; i++) + v = MathMin(v, iLow(_Symbol, InpTf, i)); + return v; +} + +double FindPreviousResistance(const int firstBottomShift) +{ + int start = firstBottomShift + 1; + int end = firstBottomShift + InpPrevLevelLookback; + int bars = Bars(_Symbol, InpTf); + end = MathMin(end, bars - 2); + if(start > end) + return 0.0; + + double r = -DBL_MAX; + for(int i = start; i <= end; i++) + r = MathMax(r, iHigh(_Symbol, InpTf, i)); + return r; +} + +double FindPreviousSupport(const int firstTopShift) +{ + int start = firstTopShift + 1; + int end = firstTopShift + InpPrevLevelLookback; + int bars = Bars(_Symbol, InpTf); + end = MathMin(end, bars - 2); + if(start > end) + return 0.0; + + double s = DBL_MAX; + for(int i = start; i <= end; i++) + s = MathMin(s, iLow(_Symbol, InpTf, i)); + return s; +} + +bool PositionExistsForMagic() +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic) + return true; + } + return false; +} + +void SetTradeFillingBySymbol() +{ + long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); + if((mask & SYMBOL_FILLING_IOC) != 0) + g_trade.SetTypeFilling(ORDER_FILLING_IOC); + else if((mask & SYMBOL_FILLING_FOK) != 0) + g_trade.SetTypeFilling(ORDER_FILLING_FOK); + else + g_trade.SetTypeFilling(ORDER_FILLING_RETURN); +} diff --git a/lab/EAs/rsi-dual-martingale-hybrid.mq5 b/lab/EAs/rsi-dual-martingale-hybrid.mq5 new file mode 100644 index 0000000..066a3d3 --- /dev/null +++ b/lab/EAs/rsi-dual-martingale-hybrid.mq5 @@ -0,0 +1,404 @@ +//+------------------------------------------------------------------+ +//| rsi-dual-martingale-hybrid.mq5 | +//| Two robots: RSI reversal martingale + RSI midpoint trend helper | +//+------------------------------------------------------------------+ +#property copyright "Lab" +#property version "1.00" + +#include + +//--- core +input ENUM_TIMEFRAMES InpTf = PERIOD_M5; +input int InpRsiLen = 14; +input double InpRsiOverbought = 70.0; +input double InpRsiOversold = 30.0; +input double InpRsiMid = 50.0; + +//--- money +input double InpBaseLot = 0.01; +input int InpSlippagePts = 30; +input ulong InpMagicBase = 2026042501; + +//--- robot A: RSI reversal martingale +input bool InpEnableReversalMartingale = true; +input double InpMartingaleMult = 1.7; +input int InpMartingaleStepPts = 300; +input int InpMartingaleMaxLevels = 6; + +//--- robot B: reverse martingale trend follow (RSI cross midpoint) +input bool InpEnableTrendReverseMartingale = true; +input double InpTrendPyramidMult = 1.5; +input int InpTrendPyramidStepPts = 250; +input int InpTrendMaxLevels = 5; + +//--- rescue / coordination +input bool InpEnableRescue = true; +input double InpTroubleLossMoney = -8.0; // martingale basket in trouble below this +input double InpRescueLotMult = 2.0; // base lot multiplier for rescue trade +input int InpRescueCooldownBars = 3; + +CTrade g_trade; +int g_hRsi = INVALID_HANDLE; +double g_rsi[]; + +datetime g_lastBar = 0; +int g_lastRescueBarIndex = -1000000; + +enum RobotDirection +{ + DIR_NONE = 0, + DIR_BUY = 1, + DIR_SELL = -1 +}; + +// Magic map: +// base + 1 : reversal martingale basket +// base + 2 : trend reverse-martingale basket +// base + 3 : rescue positions +ulong MagicRev() { return InpMagicBase + 1; } +ulong MagicTrend() { return InpMagicBase + 2; } +ulong MagicRescue() { return InpMagicBase + 3; } + +int OnInit() +{ + g_trade.SetDeviationInPoints(InpSlippagePts); + SetTradeFillingBySymbol(); + + g_hRsi = iRSI(_Symbol, InpTf, InpRsiLen, PRICE_CLOSE); + if(g_hRsi == INVALID_HANDLE) + return INIT_FAILED; + + ArraySetAsSeries(g_rsi, true); + return INIT_SUCCEEDED; +} + +void OnDeinit(const int reason) +{ + if(g_hRsi != INVALID_HANDLE) + IndicatorRelease(g_hRsi); +} + +void OnTick() +{ + if(CopyBuffer(g_hRsi, 0, 0, 10, g_rsi) < 10) + return; + + // Rescue management can run every tick. + ManageRescueCoordination(); + + datetime t = iTime(_Symbol, InpTf, 0); + if(t == g_lastBar) + return; + g_lastBar = t; + + if(InpEnableReversalMartingale) + RunReversalMartingale(); + + if(InpEnableTrendReverseMartingale) + RunTrendReverseMartingale(); +} + +void RunReversalMartingale() +{ + ulong magic = MagicRev(); + int count = BasketCountByMagic(magic); + double rsi1 = g_rsi[1]; + + // No fixed TP/SL: close reversal basket when mean-reversion reaches RSI midpoint. + RobotDirection dir = BasketDirectionByMagic(magic); + if(count > 0 && + ((dir == DIR_BUY && rsi1 >= InpRsiMid) || + (dir == DIR_SELL && rsi1 <= InpRsiMid))) + { + CloseBasketByMagic(magic); + return; + } + + double rsi2 = g_rsi[2]; + + if(count == 0) + { + if(rsi2 < InpRsiOversold && rsi1 > InpRsiOversold) + { + OpenMarketByDirection(magic, DIR_BUY, NormalizeVolume(InpBaseLot), "REV start"); + return; + } + if(rsi2 > InpRsiOverbought && rsi1 < InpRsiOverbought) + { + OpenMarketByDirection(magic, DIR_SELL, NormalizeVolume(InpBaseLot), "REV start"); + return; + } + return; + } + + if(dir == DIR_NONE || count >= InpMartingaleMaxLevels) + return; + + double lastEntry = LastEntryPriceByMagic(magic); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + bool adverseEnough = false; + if(dir == DIR_BUY) + adverseEnough = (lastEntry - bid) >= (InpMartingaleStepPts * pt); + else if(dir == DIR_SELL) + adverseEnough = (ask - lastEntry) >= (InpMartingaleStepPts * pt); + + if(!adverseEnough) + return; + + double lot = NormalizeVolume(InpBaseLot * MathPow(InpMartingaleMult, count)); + OpenMarketByDirection(magic, dir, lot, "REV scale"); +} + +void RunTrendReverseMartingale() +{ + ulong magic = MagicTrend(); + int count = BasketCountByMagic(magic); + RobotDirection dir = BasketDirectionByMagic(magic); + double basketProfit = BasketProfitByMagic(magic); + double rsi1 = g_rsi[1]; + double rsi2 = g_rsi[2]; + + // No fixed TP/SL: close trend basket when RSI crosses back through midpoint. + if(count > 0 && + ((dir == DIR_BUY && rsi2 > InpRsiMid && rsi1 < InpRsiMid) || + (dir == DIR_SELL && rsi2 < InpRsiMid && rsi1 > InpRsiMid))) + { + CloseBasketByMagic(magic); + return; + } + + if(count == 0) + { + if(rsi2 < InpRsiMid && rsi1 > InpRsiMid) + { + OpenMarketByDirection(magic, DIR_BUY, NormalizeVolume(InpBaseLot), "TREND cross"); + return; + } + if(rsi2 > InpRsiMid && rsi1 < InpRsiMid) + { + OpenMarketByDirection(magic, DIR_SELL, NormalizeVolume(InpBaseLot), "TREND cross"); + return; + } + return; + } + + if(dir == DIR_NONE || count >= InpTrendMaxLevels) + return; + if(basketProfit <= 0.0) + return; // reverse martingale: only add into winners + + double lastEntry = LastEntryPriceByMagic(magic); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + bool favorableEnough = false; + if(dir == DIR_BUY) + favorableEnough = (bid - lastEntry) >= (InpTrendPyramidStepPts * pt); + else if(dir == DIR_SELL) + favorableEnough = (lastEntry - ask) >= (InpTrendPyramidStepPts * pt); + + if(!favorableEnough) + return; + + double lot = NormalizeVolume(InpBaseLot * MathPow(InpTrendPyramidMult, count)); + OpenMarketByDirection(magic, dir, lot, "TREND add"); +} + +void ManageRescueCoordination() +{ + if(!InpEnableRescue) + return; + + ulong mRev = MagicRev(); + ulong mRes = MagicRescue(); + + double revProfit = BasketProfitByMagic(mRev); + int revCount = BasketCountByMagic(mRev); + + if(revCount == 0) + { + CloseBasketByMagic(mRes); + return; + } + + // Phase 1: no fixed rescue TP/SL; close rescue on RSI midpoint recross against rescue direction. + RobotDirection rescueDir = BasketDirectionByMagic(mRes); + if(BasketCountByMagic(mRes) > 0 && + ((rescueDir == DIR_BUY && g_rsi[2] > InpRsiMid && g_rsi[1] < InpRsiMid) || + (rescueDir == DIR_SELL && g_rsi[2] < InpRsiMid && g_rsi[1] > InpRsiMid))) + { + ulong worstTicket = WorstTicketByMagic(mRev); + CloseBasketByMagic(mRes); + if(worstTicket != 0) + g_trade.PositionClose(worstTicket); + return; + } + + // Phase 2: if martingale basket is in trouble, launch one trend-aligned rescue trade. + if(revProfit > InpTroubleLossMoney) + return; + + if(BasketCountByMagic(mRes) > 0) + return; + + int barsNow = iBars(_Symbol, InpTf); + if((barsNow - g_lastRescueBarIndex) < InpRescueCooldownBars) + return; + + RobotDirection helperDir = (g_rsi[1] >= InpRsiMid ? DIR_BUY : DIR_SELL); + + // avoid adding rescue in same direction as losing reversal basket when RSI trend disagrees + RobotDirection revDir = BasketDirectionByMagic(mRev); + if(revDir == helperDir) + helperDir = (helperDir == DIR_BUY ? DIR_SELL : DIR_BUY); + + double lot = NormalizeVolume(InpBaseLot * InpRescueLotMult); + if(OpenMarketByDirection(mRes, helperDir, lot, "RESCUE")) + g_lastRescueBarIndex = barsNow; +} + +bool OpenMarketByDirection(const ulong magic, const RobotDirection dir, const double lot, const string comment) +{ + if(dir == DIR_NONE || lot <= 0.0) + return false; + + g_trade.SetExpertMagicNumber(magic); + + double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); + double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); + + if(dir == DIR_BUY) + return g_trade.Buy(lot, _Symbol, ask, 0.0, 0.0, comment); + return g_trade.Sell(lot, _Symbol, bid, 0.0, 0.0, comment); +} + +int BasketCountByMagic(const ulong magic) +{ + int n = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue; + n++; + } + return n; +} + +double BasketProfitByMagic(const ulong magic) +{ + double sum = 0.0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue; + sum += PositionGetDouble(POSITION_PROFIT); + } + return sum; +} + +RobotDirection BasketDirectionByMagic(const ulong magic) +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue; + long type = PositionGetInteger(POSITION_TYPE); + return (type == POSITION_TYPE_BUY ? DIR_BUY : DIR_SELL); + } + return DIR_NONE; +} + +double LastEntryPriceByMagic(const ulong magic) +{ + datetime newest = 0; + double price = 0.0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue; + datetime t = (datetime)PositionGetInteger(POSITION_TIME); + if(t >= newest) + { + newest = t; + price = PositionGetDouble(POSITION_PRICE_OPEN); + } + } + return price; +} + +ulong WorstTicketByMagic(const ulong magic) +{ + double worstProfit = DBL_MAX; + ulong worstTicket = 0; + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue; + double p = PositionGetDouble(POSITION_PROFIT); + if(p < worstProfit) + { + worstProfit = p; + worstTicket = ticket; + } + } + return worstTicket; +} + +void CloseBasketByMagic(const ulong magic) +{ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + ulong ticket = PositionGetTicket(i); + if(ticket == 0) continue; + if(!PositionSelectByTicket(ticket)) continue; + if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; + if((ulong)PositionGetInteger(POSITION_MAGIC) != magic) continue; + g_trade.PositionClose(ticket); + } +} + +double NormalizeVolume(const double volRaw) +{ + double vMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + double vMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); + double vStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + if(vStep <= 0.0) + vStep = 0.01; + + double v = MathMax(vMin, MathMin(vMax, volRaw)); + v = MathFloor(v / vStep) * vStep; + int vd = 2; + if(vStep < 0.01) vd = 3; + if(vStep < 0.001) vd = 4; + return NormalizeDouble(v, vd); +} + +void SetTradeFillingBySymbol() +{ + long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE); + if((mask & SYMBOL_FILLING_IOC) != 0) + g_trade.SetTypeFilling(ORDER_FILLING_IOC); + else if((mask & SYMBOL_FILLING_FOK) != 0) + g_trade.SetTypeFilling(ORDER_FILLING_FOK); + else + g_trade.SetTypeFilling(ORDER_FILLING_RETURN); +} diff --git a/polymarket/__pycache__/__init__.cpython-312.pyc b/polymarket/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index eb2a460..0000000 Binary files a/polymarket/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/analytics/__init__.py b/polymarket/analytics/__init__.py deleted file mode 100644 index 53bfc92..0000000 --- a/polymarket/analytics/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Analytics Module""" - -from .metrics import PerformanceMetrics - -__all__ = ['PerformanceMetrics'] diff --git a/polymarket/analytics/metrics.py b/polymarket/analytics/metrics.py deleted file mode 100644 index 75a7e9c..0000000 --- a/polymarket/analytics/metrics.py +++ /dev/null @@ -1,216 +0,0 @@ -""" -Performance Metrics and Analytics - -Calculates various performance metrics for strategies. -""" - -from typing import Dict, List -import numpy as np -import pandas as pd - - -class PerformanceMetrics: - """Calculate performance metrics from backtest results""" - - @staticmethod - def calculate_sharpe_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float: - """ - Calculate Sharpe ratio. - - Args: - returns: List of daily returns - risk_free_rate: Annual risk-free rate - - Returns: - Sharpe ratio - """ - if not returns: - return 0.0 - - returns_array = np.array(returns) - excess_returns = returns_array - (risk_free_rate / 365) - - if returns_array.std() == 0: - return 0.0 - - sharpe = np.sqrt(365) * excess_returns.mean() / returns_array.std() - return sharpe - - @staticmethod - def calculate_sortino_ratio(returns: List[float], risk_free_rate: float = 0.0) -> float: - """ - Calculate Sortino ratio (downside deviation only). - - Args: - returns: List of daily returns - risk_free_rate: Annual risk-free rate - - Returns: - Sortino ratio - """ - if not returns: - return 0.0 - - returns_array = np.array(returns) - excess_returns = returns_array - (risk_free_rate / 365) - - # Calculate downside deviation - downside_returns = excess_returns[excess_returns < 0] - if len(downside_returns) == 0: - return 0.0 - - downside_std = np.std(downside_returns) - if downside_std == 0: - return 0.0 - - sortino = np.sqrt(365) * excess_returns.mean() / downside_std - return sortino - - @staticmethod - def calculate_max_drawdown(equity_curve: List[float]) -> Dict[str, float]: - """ - Calculate maximum drawdown. - - Args: - equity_curve: List of equity values over time - - Returns: - Dictionary with max_drawdown, max_drawdown_percent, and drawdown_duration - """ - if not equity_curve: - return {'max_drawdown': 0.0, 'max_drawdown_percent': 0.0, 'drawdown_duration': 0} - - equity_array = np.array(equity_curve) - peak = np.maximum.accumulate(equity_array) - drawdown = peak - equity_array - drawdown_percent = (drawdown / peak) * 100 - - max_dd = float(np.max(drawdown)) - max_dd_percent = float(np.max(drawdown_percent)) - - # Calculate drawdown duration - in_drawdown = drawdown > 0 - if np.any(in_drawdown): - # Count consecutive periods in drawdown - durations = [] - current_duration = 0 - for in_dd in in_drawdown: - if in_dd: - current_duration += 1 - else: - if current_duration > 0: - durations.append(current_duration) - current_duration = 0 - if current_duration > 0: - durations.append(current_duration) - - max_duration = max(durations) if durations else 0 - else: - max_duration = 0 - - return { - 'max_drawdown': max_dd, - 'max_drawdown_percent': max_dd_percent, - 'drawdown_duration': max_duration - } - - @staticmethod - def calculate_calmar_ratio(total_return: float, max_drawdown_percent: float) -> float: - """ - Calculate Calmar ratio (return / max drawdown). - - Args: - total_return: Total return percentage - max_drawdown_percent: Maximum drawdown percentage - - Returns: - Calmar ratio - """ - if max_drawdown_percent == 0: - return 0.0 - return total_return / max_drawdown_percent - - @staticmethod - def calculate_profit_factor(total_profit: float, total_loss: float) -> float: - """ - Calculate profit factor. - - Args: - total_profit: Total profit - total_loss: Total loss (absolute value) - - Returns: - Profit factor - """ - if total_loss == 0: - return 0.0 if total_profit == 0 else float('inf') - return abs(total_profit / total_loss) - - @staticmethod - def calculate_expectancy(win_rate: float, avg_win: float, avg_loss: float) -> float: - """ - Calculate expectancy per trade. - - Args: - win_rate: Win rate (0-1) - avg_win: Average winning trade - avg_loss: Average losing trade (absolute value) - - Returns: - Expectancy - """ - return (win_rate * avg_win) - ((1 - win_rate) * avg_loss) - - @staticmethod - def generate_report(backtest_results: Dict) -> str: - """ - Generate formatted performance report. - - Args: - backtest_results: Results dictionary from backtest - - Returns: - Formatted report string - """ - equity_curve = [point['equity'] for point in backtest_results.get('equity_curve', [])] - daily_returns = backtest_results.get('daily_returns', []) - - # Calculate additional metrics - sharpe = PerformanceMetrics.calculate_sharpe_ratio(daily_returns) - sortino = PerformanceMetrics.calculate_sortino_ratio(daily_returns) - dd_metrics = PerformanceMetrics.calculate_max_drawdown(equity_curve) - - report = f""" -{'='*70} -POLYMARKET BACKTEST REPORT -{'='*70} - -Strategy: {backtest_results.get('strategy', 'Unknown')} -Period: {backtest_results.get('start_date')} to {backtest_results.get('end_date')} - -INITIAL METRICS: - Initial Balance: ${backtest_results.get('initial_balance', 0):,.2f} - Final Equity: ${backtest_results.get('final_equity', 0):,.2f} - Total Return: {backtest_results.get('total_return', 0):.2f}% - -TRADE STATISTICS: - Total Trades: {backtest_results.get('total_trades', 0)} - Winning Trades: {backtest_results.get('winning_trades', 0)} - Losing Trades: {backtest_results.get('losing_trades', 0)} - Win Rate: {backtest_results.get('win_rate', 0):.2f}% - -PROFITABILITY: - Total Profit: ${backtest_results.get('total_profit', 0):,.2f} - Total Loss: ${backtest_results.get('total_loss', 0):,.2f} - Net Profit: ${backtest_results.get('net_profit', 0):,.2f} - Profit Factor: {backtest_results.get('profit_factor', 0):.2f} - -RISK METRICS: - Maximum Drawdown: {dd_metrics['max_drawdown_percent']:.2f}% - Drawdown Duration: {dd_metrics['drawdown_duration']} periods - Sharpe Ratio: {sharpe:.2f} - Sortino Ratio: {sortino:.2f} - -{'='*70} -""" - return report diff --git a/polymarket/api/__init__.py b/polymarket/api/__init__.py deleted file mode 100644 index 7c0526e..0000000 --- a/polymarket/api/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Polymarket API Clients""" - -from .gamma_client import GammaClient -from .clob_client import ClobClient -from .data_client import DataClient - -__all__ = ['GammaClient', 'ClobClient', 'DataClient'] diff --git a/polymarket/api/__pycache__/__init__.cpython-312.pyc b/polymarket/api/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 4699bf4..0000000 Binary files a/polymarket/api/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/api/__pycache__/clob_client.cpython-312.pyc b/polymarket/api/__pycache__/clob_client.cpython-312.pyc deleted file mode 100644 index b96d782..0000000 Binary files a/polymarket/api/__pycache__/clob_client.cpython-312.pyc and /dev/null differ diff --git a/polymarket/api/__pycache__/data_client.cpython-312.pyc b/polymarket/api/__pycache__/data_client.cpython-312.pyc deleted file mode 100644 index 8d58587..0000000 Binary files a/polymarket/api/__pycache__/data_client.cpython-312.pyc and /dev/null differ diff --git a/polymarket/api/__pycache__/gamma_client.cpython-312.pyc b/polymarket/api/__pycache__/gamma_client.cpython-312.pyc deleted file mode 100644 index 9925edd..0000000 Binary files a/polymarket/api/__pycache__/gamma_client.cpython-312.pyc and /dev/null differ diff --git a/polymarket/api/clob_client.py b/polymarket/api/clob_client.py deleted file mode 100644 index 542276b..0000000 --- a/polymarket/api/clob_client.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -Polymarket CLOB API Client - -Provides orderbook data, price quotes, and order placement. -API Documentation: https://docs.polymarket.com/developers/CLOB/introduction -""" - -import requests -from typing import Dict, Optional, List -import time - - -class ClobClient: - """Client for Polymarket CLOB API - Trading and orderbook data""" - - BASE_URL = "https://clob.polymarket.com" - - def __init__(self, timeout: int = 30): - """ - Initialize CLOB API client. - - Args: - timeout: Request timeout in seconds - """ - self.timeout = timeout - self.session = requests.Session() - self.session.headers.update({ - 'Accept': 'application/json', - 'User-Agent': 'Polymarket-Trading-Framework/1.0' - }) - - def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: - """Make GET request with error handling""" - url = f"{self.BASE_URL}{endpoint}" - try: - response = self.session.get(url, params=params, timeout=self.timeout) - response.raise_for_status() - return response.json() - except requests.exceptions.RequestException as e: - raise Exception(f"CLOB API error: {e}") - - def get_price(self, token_id: str, side: str = 'buy') -> float: - """ - Get current price for a token. - - Args: - token_id: CLOB token ID - side: 'buy' or 'sell' - - Returns: - Current price as float - """ - params = { - 'token_id': token_id, - 'side': side - } - response = self._get('/price', params=params) - return float(response.get('price', 0.0)) - - def get_orderbook(self, token_id: str) -> Dict: - """ - Get orderbook depth for a token. - - Per docs: https://docs.polymarket.com/quickstart/fetching-data - Endpoint: /book?token_id=YOUR_TOKEN_ID - - Args: - token_id: CLOB token ID - - Returns: - Dictionary with 'bids' and 'asks' arrays - """ - params = {'token_id': token_id} - return self._get('/book', params=params) - - def get_best_bid_ask(self, token_id: str) -> Dict[str, float]: - """ - Get best bid and ask prices. - - Args: - token_id: CLOB token ID - - Returns: - Dictionary with 'bid' and 'ask' prices - """ - book = self.get_orderbook(token_id) - - best_bid = float(book['bids'][0]['price']) if book.get('bids') else 0.0 - best_ask = float(book['asks'][0]['price']) if book.get('asks') else 1.0 - - return { - 'bid': best_bid, - 'ask': best_ask, - 'spread': best_ask - best_bid, - 'mid': (best_bid + best_ask) / 2 - } - - def get_market_depth(self, token_id: str, levels: int = 10) -> Dict: - """ - Get market depth up to specified levels. - - Args: - token_id: CLOB token ID - levels: Number of levels to retrieve - - Returns: - Dictionary with bid/ask depth - """ - book = self.get_orderbook(token_id) - - bids = book.get('bids', [])[:levels] - asks = book.get('asks', [])[:levels] - - # Calculate cumulative depth - bid_depth = sum(float(bid['size']) for bid in bids) - ask_depth = sum(float(ask['size']) for ask in asks) - - return { - 'bids': bids, - 'asks': asks, - 'bid_depth': bid_depth, - 'ask_depth': ask_depth, - 'total_depth': bid_depth + ask_depth - } - - def calculate_impact(self, token_id: str, size: float, side: str) -> Dict: - """ - Calculate estimated price impact for a trade size. - - Args: - token_id: CLOB token ID - size: Trade size - side: 'buy' or 'sell' - - Returns: - Dictionary with impact metrics - """ - book = self.get_orderbook(token_id) - - if side == 'buy': - levels = book.get('asks', []) - else: - levels = book.get('bids', []) - - remaining = size - total_cost = 0.0 - levels_consumed = [] - - for level in levels: - level_price = float(level['price']) - level_size = float(level['size']) - - if remaining <= 0: - break - - consumed = min(remaining, level_size) - total_cost += consumed * level_price - remaining -= consumed - - levels_consumed.append({ - 'price': level_price, - 'size': consumed - }) - - avg_price = total_cost / size if size > 0 else 0.0 - best_price = float(levels[0]['price']) if levels else 0.0 - impact = abs(avg_price - best_price) / best_price if best_price > 0 else 0.0 - - return { - 'average_price': avg_price, - 'best_price': best_price, - 'price_impact': impact, - 'levels_consumed': len(levels_consumed), - 'slippage': avg_price - best_price if side == 'buy' else best_price - avg_price - } diff --git a/polymarket/api/data_client.py b/polymarket/api/data_client.py deleted file mode 100644 index 42cbaf1..0000000 --- a/polymarket/api/data_client.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Polymarket Data API Client - -Provides positions, trade history, and portfolio data. -API Documentation: https://docs.polymarket.com/developers/misc-endpoints/data-api-get-positions -""" - -import requests -from typing import Dict, Optional, List -from datetime import datetime - - -class DataClient: - """Client for Polymarket Data API - Positions and history""" - - BASE_URL = "https://data-api.polymarket.com" - - def __init__(self, api_key: Optional[str] = None, timeout: int = 30): - """ - Initialize Data API client. - - Args: - api_key: Optional API key for authenticated requests - timeout: Request timeout in seconds - """ - self.timeout = timeout - self.api_key = api_key - self.session = requests.Session() - self.session.headers.update({ - 'Accept': 'application/json', - 'User-Agent': 'Polymarket-Trading-Framework/1.0' - }) - - if api_key: - self.session.headers['Authorization'] = f'Bearer {api_key}' - - def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: - """Make GET request with error handling""" - url = f"{self.BASE_URL}{endpoint}" - try: - response = self.session.get(url, params=params, timeout=self.timeout) - response.raise_for_status() - return response.json() - except requests.exceptions.RequestException as e: - raise Exception(f"Data API error: {e}") - - def get_positions(self, user_address: str) -> List[Dict]: - """ - Get user positions. - - Args: - user_address: User wallet address - - Returns: - List of position dictionaries - """ - params = {'user': user_address} - return self._get('/positions', params=params) - - def get_trades(self, user_address: str, limit: int = 100) -> List[Dict]: - """ - Get user trade history. - - Args: - user_address: User wallet address - limit: Maximum number of trades to return - - Returns: - List of trade dictionaries - """ - params = { - 'user': user_address, - 'limit': limit - } - return self._get('/trades', params=params) - - def get_portfolio(self, user_address: str) -> Dict: - """ - Get user portfolio summary. - - Args: - user_address: User wallet address - - Returns: - Portfolio dictionary with balances, positions, etc. - """ - params = {'user': user_address} - return self._get('/portfolio', params=params) diff --git a/polymarket/api/gamma_client.py b/polymarket/api/gamma_client.py deleted file mode 100644 index 0d8e411..0000000 --- a/polymarket/api/gamma_client.py +++ /dev/null @@ -1,177 +0,0 @@ -""" -Polymarket Gamma API Client - -Provides market discovery, metadata, and event data. -API Documentation: https://docs.polymarket.com/developers/gamma-markets-api/overview -""" - -import requests -from typing import List, Dict, Optional, Any -from datetime import datetime -import time - - -class GammaClient: - """Client for Polymarket Gamma API - Market discovery and metadata""" - - BASE_URL = "https://gamma-api.polymarket.com" - - def __init__(self, timeout: int = 30): - """ - Initialize Gamma API client. - - Args: - timeout: Request timeout in seconds - """ - self.timeout = timeout - self.session = requests.Session() - self.session.headers.update({ - 'Accept': 'application/json', - 'User-Agent': 'Polymarket-Trading-Framework/1.0' - }) - - def _get(self, endpoint: str, params: Optional[Dict] = None) -> Dict: - """Make GET request with error handling""" - url = f"{self.BASE_URL}{endpoint}" - try: - response = self.session.get(url, params=params, timeout=self.timeout) - response.raise_for_status() - data = response.json() - # According to docs, /events returns an array directly - # But handle both array and dict responses - return data - except requests.exceptions.RequestException as e: - raise Exception(f"Gamma API error: {e}") - - def get_events(self, - active: bool = True, - closed: bool = False, - limit: int = 100, - tag_id: Optional[int] = None, - series_id: Optional[int] = None, - order: Optional[str] = None, - ascending: bool = True) -> List[Dict]: - """ - Fetch active events/markets. - - Args: - active: Filter for active events - closed: Filter for closed events - limit: Maximum number of results - tag_id: Filter by tag/category ID - series_id: Filter by series ID (for sports) - order: Sort order (e.g., 'startTime') - ascending: Sort ascending or descending - - Returns: - List of event dictionaries - """ - params = { - 'active': str(active).lower(), - 'closed': str(closed).lower(), - 'limit': limit - } - - if tag_id: - params['tag_id'] = tag_id - if series_id: - params['series_id'] = series_id - if order: - params['order'] = order - params['ascending'] = str(ascending).lower() - - return self._get('/events', params=params) - - def get_event_by_slug(self, slug: str) -> Optional[Dict]: - """ - Get event details by slug. - - Args: - slug: Event slug (e.g., 'will-bitcoin-reach-100k-by-2025') - - Returns: - Event dictionary or None if not found - """ - events = self.get_events(limit=1) - for event in events: - if event.get('slug') == slug: - return event - return None - - def get_market_by_slug(self, slug: str) -> Optional[Dict]: - """ - Get market details by slug. - - Args: - slug: Market slug - - Returns: - Market dictionary with clobTokenIds, outcomes, prices - """ - params = {'slug': slug} - markets = self._get('/markets', params=params) - return markets[0] if markets else None - - def get_tags(self, limit: int = 100) -> List[Dict]: - """ - Get all available tags/categories. - - Args: - limit: Maximum number of tags to return - - Returns: - List of tag dictionaries - """ - params = {'limit': limit} - return self._get('/tags', params=params) - - def get_sports(self) -> List[Dict]: - """ - Get all supported sports leagues. - - Returns: - List of sports league dictionaries - """ - return self._get('/sports') - - def get_market_prices(self, market: Dict) -> Dict[str, float]: - """ - Extract current prices from market data. - - Args: - market: Market dictionary with outcomes and outcomePrices - - Returns: - Dictionary mapping outcome to price (probability) - """ - import json - - outcomes = json.loads(market.get('outcomes', '[]')) - prices = json.loads(market.get('outcomePrices', '[]')) - - return {outcome: float(price) for outcome, price in zip(outcomes, prices)} - - def search_events(self, query: str, limit: int = 20) -> List[Dict]: - """ - Search events by title/keywords. - - Args: - query: Search query - limit: Maximum results - - Returns: - List of matching events - """ - # Note: This is a simplified search - actual API may have different endpoint - all_events = self.get_events(limit=1000) - query_lower = query.lower() - - matches = [] - for event in all_events: - title = event.get('title', '').lower() - if query_lower in title: - matches.append(event) - if len(matches) >= limit: - break - - return matches diff --git a/polymarket/backtesting/__init__.py b/polymarket/backtesting/__init__.py deleted file mode 100644 index 2c0eada..0000000 --- a/polymarket/backtesting/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Backtesting Module""" - -from .engine import BacktestEngine - -__all__ = ['BacktestEngine'] diff --git a/polymarket/backtesting/__pycache__/__init__.cpython-312.pyc b/polymarket/backtesting/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 4da5a73..0000000 Binary files a/polymarket/backtesting/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/backtesting/__pycache__/engine.cpython-312.pyc b/polymarket/backtesting/__pycache__/engine.cpython-312.pyc deleted file mode 100644 index 48283b7..0000000 Binary files a/polymarket/backtesting/__pycache__/engine.cpython-312.pyc and /dev/null differ diff --git a/polymarket/backtesting/engine.py b/polymarket/backtesting/engine.py deleted file mode 100644 index 9cf6476..0000000 --- a/polymarket/backtesting/engine.py +++ /dev/null @@ -1,499 +0,0 @@ -""" -Backtesting Engine for Polymarket - -Simulates trading on historical market data. -""" - -from typing import Dict, List, Optional, Any -from datetime import datetime, timedelta -import pandas as pd -import numpy as np - -# Configure numpy to handle division by zero gracefully -np.seterr(divide='ignore', invalid='ignore') -from ..strategies.base_strategy import BaseStrategy, MarketSignal, Position -from ..api.gamma_client import GammaClient -from ..api.clob_client import ClobClient -import time - - -class BacktestEngine: - """ - Main backtesting engine for Polymarket strategies. - - Simulates trading on historical data with realistic execution. - """ - - def __init__(self, - strategy: BaseStrategy, - start_date: datetime, - end_date: datetime, - initial_balance: float = 1000.0): - """ - Initialize backtesting engine. - - Args: - strategy: Strategy instance to backtest - start_date: Start date for backtesting - end_date: End date for backtesting - initial_balance: Starting USDC balance - """ - self.strategy = strategy - self.start_date = start_date - self.end_date = end_date - self.initial_balance = initial_balance - - # Initialize API clients (for data fetching) - self.gamma_client = GammaClient() - self.clob_client = ClobClient() - - # Backtest state - self.current_date = start_date - self.market_snapshots: List[Dict] = [] - self.trades: List[Dict] = [] - - # Performance tracking - self.equity_curve: List[Dict] = [] - self.daily_returns: List[float] = [] - - def fetch_historical_markets(self, tag_id: Optional[int] = None) -> List[Dict]: - """ - Fetch markets that were active during backtest period. - - Note: Polymarket API may not provide full historical data. - This is a simplified implementation. - - Args: - tag_id: Optional tag ID to filter markets - - Returns: - List of market dictionaries - """ - # Get current active markets (as proxy for historical) - # In production, you'd need to store historical snapshots - events = self.gamma_client.get_events( - active=True, - closed=False, - limit=100, - tag_id=tag_id - ) - - markets = [] - for event in events: - for market in event.get('markets', []): - markets.append({ - 'event': event, - 'market': market, - 'timestamp': datetime.now() # Would be historical in real implementation - }) - - return markets - - def simulate_price_evolution(self, - initial_price: float, - days: int, - volatility: float = 0.05) -> List[float]: - """ - Simulate price evolution for backtesting. - - In production, use actual historical price data. - - Args: - initial_price: Starting price - days: Number of days to simulate - volatility: Daily volatility - - Returns: - List of prices over time - """ - prices = [initial_price] - for _ in range(days): - # Random walk with mean reversion - change = np.random.normal(0, volatility) - new_price = prices[-1] + change - new_price = max(0.01, min(0.99, new_price)) # Bound between 0 and 1 - prices.append(new_price) - - return prices - - def execute_signal(self, - signal: MarketSignal, - market_data: Dict, - timestamp: datetime) -> Optional[Dict]: - """ - Execute a trading signal. - - Args: - signal: Trading signal from strategy - market_data: Current market data - timestamp: Current timestamp - - Returns: - Trade dictionary or None if execution failed - """ - # Get market from market_data first (needed for prices) - market = market_data.get('market', {}) - - # Use token_id from signal if available, otherwise get from market - if signal.token_id: - token_id = signal.token_id - else: - token_ids = market.get('clobTokenIds', []) - if not token_ids: - return None - token_id = token_ids[0] - - outcome = 'Yes' # Default outcome - - # Get current price from market data - import json - prices = json.loads(market.get('outcomePrices', '[0.5, 0.5]')) - if signal.action == 'BUY': - current_price = float(prices[0]) # Yes price - else: - current_price = float(prices[0]) # Use Yes price for exit too - - # Validate price - if current_price <= 0 or current_price >= 1: - return None # Invalid price - - # Calculate position_size based on action - if signal.action == 'SELL': - if token_id not in self.strategy.positions: - return None # No position to close - # For SELL, position_size represents the value we'll get back - pos = self.strategy.positions[token_id] - # Validate position data - if not (pos.size > 0 and np.isfinite(pos.size) and - current_price > 0 and current_price < 1 and - np.isfinite(current_price)): - return None # Invalid position or price data - position_size = pos.size * current_price * signal.size # signal.size = 1.0 for full close - if not np.isfinite(position_size) or position_size <= 0: - return None - else: - # For BUY, calculate position size and check limits - # Validate balance - if not (np.isfinite(self.strategy.current_balance) and self.strategy.current_balance > 0): - return None - - position_size = min( - signal.size * self.strategy.current_balance, - self.strategy.current_balance * self.strategy.max_position_size - ) - - # Validate position_size - if not (np.isfinite(position_size) and position_size > 0): - return None - - # Check if can open position - if not self.strategy.can_open_position(position_size, token_id): - return None - - # Ensure we have enough balance - if position_size > self.strategy.current_balance: - return None - - # Execute trade - if signal.action == 'BUY': - # Buy tokens - safe division - if current_price > 0 and current_price < 1 and np.isfinite(current_price): - tokens_bought = position_size / current_price - # Validate tokens_bought - if not (np.isfinite(tokens_bought) and tokens_bought > 0): - return None - else: - return None # Invalid price, skip trade - - # Validate balance before subtraction - if not (np.isfinite(self.strategy.current_balance) and - self.strategy.current_balance >= position_size): - return None - - self.strategy.current_balance -= position_size - - # Ensure balance is still finite - if not np.isfinite(self.strategy.current_balance): - self.strategy.current_balance = 0.0 - return None - - # Create position - position = Position( - token_id=token_id, - outcome=outcome, - size=tokens_bought, - entry_price=current_price, - entry_time=timestamp, - current_price=current_price, - unrealized_pnl=0.0 - ) - self.strategy.positions[token_id] = position - - elif signal.action == 'SELL': - # Close existing position - if token_id in self.strategy.positions: - pos = self.strategy.positions[token_id] - # Validate position data - if not (np.isfinite(pos.size) and pos.size > 0 and - np.isfinite(pos.entry_price) and pos.entry_price > 0): - return None - - # Close fraction of position (signal.size = 1.0 means close all) - close_size = pos.size * signal.size - if not (np.isfinite(close_size) and close_size > 0): - return None - - exit_value = close_size * current_price - entry_cost = close_size * pos.entry_price - - # Validate calculations - if not (np.isfinite(exit_value) and np.isfinite(entry_cost)): - return None - - pnl = exit_value - entry_cost - if not np.isfinite(pnl): - pnl = 0.0 - - # Validate balance before addition - if not np.isfinite(self.strategy.current_balance): - self.strategy.current_balance = 0.0 - - self.strategy.current_balance += exit_value - - # Ensure balance is still finite - if not np.isfinite(self.strategy.current_balance): - self.strategy.current_balance = 0.0 - return None - - self.strategy.total_trades += 1 - - if pnl > 0: - self.strategy.winning_trades += 1 - self.strategy.total_profit += pnl if np.isfinite(pnl) else 0.0 - else: - self.strategy.losing_trades += 1 - self.strategy.total_loss += abs(pnl) if np.isfinite(pnl) else 0.0 - - # Update or remove position - if signal.size >= 1.0: - # Close entire position - pos.realized_pnl = pnl if np.isfinite(pnl) else 0.0 - self.strategy.closed_positions.append(pos) - del self.strategy.positions[token_id] - else: - # Partial close - pos.size -= close_size - if not (np.isfinite(pos.size) and pos.size >= 0): - pos.size = 0.0 - pos.realized_pnl += pnl if np.isfinite(pnl) else 0.0 - if not np.isfinite(pos.realized_pnl): - pos.realized_pnl = 0.0 - - trade = { - 'timestamp': timestamp, - 'action': signal.action, - 'token_id': token_id, - 'outcome': outcome, - 'price': current_price, - 'size': position_size, - 'reason': signal.reason, - 'confidence': signal.confidence - } - - self.trades.append(trade) - return trade - - def run(self, markets: Optional[List[Dict]] = None) -> Dict[str, Any]: - """ - Run the backtest. - - Args: - markets: Optional list of markets to backtest. If None, fetches markets. - - Returns: - Dictionary with backtest results - """ - print(f"Starting backtest from {self.start_date} to {self.end_date}") - - # Fetch markets if not provided - if markets is None: - markets = self.fetch_historical_markets() - - if not markets: - raise ValueError("No markets found for backtesting") - - print(f"Found {len(markets)} markets to backtest") - - # Simulate time progression - current_date = self.start_date - day_count = 0 - - while current_date <= self.end_date: - # Update positions with current prices - for token_id, position in self.strategy.positions.items(): - # Simulate price movement - # In production, use actual historical prices - price_change = np.random.normal(0, 0.02) - new_price = max(0.01, min(0.99, position.current_price + price_change)) - self.strategy.update_position(token_id, new_price) - - # Process each market - for market_snapshot in markets: - market_data = { - 'event': market_snapshot['event'], - 'market': market_snapshot['market'], - 'timestamp': current_date - } - - # Get current prices - market = market_snapshot['market'] - import json - outcomes = json.loads(market.get('outcomes', '["Yes", "No"]')) - prices = json.loads(market.get('outcomePrices', '[0.5, 0.5]')) - - market_data['prices'] = { - outcome: float(price) - for outcome, price in zip(outcomes, prices) - } - - # Get strategy signal - signal = self.strategy.analyze_market(market_data) - - if signal and signal.confidence >= self.strategy.min_confidence: - self.execute_signal(signal, market_data, current_date) - - # Update equity curve - self.strategy.update_drawdown() - equity = self.strategy.calculate_equity() - - self.equity_curve.append({ - 'date': current_date, - 'equity': equity, - 'balance': self.strategy.current_balance, - 'unrealized_pnl': sum(pos.unrealized_pnl for pos in self.strategy.positions.values()) - }) - - # Calculate daily return - if len(self.equity_curve) > 1: - prev_equity = self.equity_curve[-2]['equity'] - daily_return = (equity - prev_equity) / prev_equity if prev_equity > 0 else 0.0 - self.daily_returns.append(daily_return) - - # Advance to next day - current_date += timedelta(days=1) - day_count += 1 - - if day_count % 10 == 0: - print(f"Progress: {day_count} days, Equity: ${equity:.2f}") - - # Close all open positions at end - final_equity = self.strategy.calculate_equity() - for token_id, position in list(self.strategy.positions.items()): - # Assume final price is entry price (or use last known price) - exit_value = position.size * position.current_price - pnl = exit_value - (position.size * position.entry_price) - - self.strategy.current_balance += exit_value - self.strategy.total_trades += 1 - - if pnl > 0: - self.strategy.winning_trades += 1 - self.strategy.total_profit += pnl - else: - self.strategy.losing_trades += 1 - self.strategy.total_loss += abs(pnl) - - del self.strategy.positions[token_id] - - # Calculate final metrics with safe division - if self.initial_balance > 0: - total_return = (final_equity - self.initial_balance) / self.initial_balance * 100 - else: - total_return = 0.0 - - sharpe_ratio = self._calculate_sharpe_ratio() - - # Safe win rate calculation - if self.strategy.total_trades > 0: - win_rate = (self.strategy.winning_trades / self.strategy.total_trades * 100) - else: - win_rate = 0.0 - - # Safe profit factor calculation - if abs(self.strategy.total_loss) > 1e-10: - profit_factor = abs(self.strategy.total_profit / self.strategy.total_loss) - else: - profit_factor = 0.0 if abs(self.strategy.total_profit) < 1e-10 else float('inf') - - # Ensure all values are finite - total_return = total_return if np.isfinite(total_return) else 0.0 - win_rate = win_rate if np.isfinite(win_rate) else 0.0 - profit_factor = profit_factor if (np.isfinite(profit_factor) and profit_factor != float('inf')) else 0.0 - sharpe_ratio = sharpe_ratio if np.isfinite(sharpe_ratio) else 0.0 - max_dd = self.strategy.max_drawdown * 100 if np.isfinite(self.strategy.max_drawdown) else 0.0 - - results = { - 'strategy': self.strategy.name, - 'start_date': self.start_date, - 'end_date': self.end_date, - 'initial_balance': self.initial_balance, - 'final_balance': self.strategy.current_balance, - 'final_equity': final_equity if np.isfinite(final_equity) else self.initial_balance, - 'total_return': total_return, - 'total_trades': self.strategy.total_trades, - 'winning_trades': self.strategy.winning_trades, - 'losing_trades': self.strategy.losing_trades, - 'win_rate': win_rate, - 'total_profit': self.strategy.total_profit if np.isfinite(self.strategy.total_profit) else 0.0, - 'total_loss': self.strategy.total_loss if np.isfinite(self.strategy.total_loss) else 0.0, - 'net_profit': (self.strategy.total_profit + self.strategy.total_loss) if np.isfinite(self.strategy.total_profit + self.strategy.total_loss) else 0.0, - 'profit_factor': profit_factor, - 'max_drawdown': max_dd, - 'sharpe_ratio': sharpe_ratio, - 'trades': self.trades, - 'equity_curve': self.equity_curve - } - - return results - - def _calculate_sharpe_ratio(self, risk_free_rate: float = 0.0) -> float: - """Calculate Sharpe ratio from daily returns""" - if not self.daily_returns: - return 0.0 - - returns = np.array(self.daily_returns) - if len(returns) == 0: - return 0.0 - - excess_returns = returns - (risk_free_rate / 365) # Daily risk-free rate - - std_dev = returns.std() - if std_dev == 0 or np.isnan(std_dev) or not np.isfinite(std_dev): - return 0.0 - - mean_return = excess_returns.mean() - if not np.isfinite(mean_return): - return 0.0 - - sharpe = np.sqrt(365) * mean_return / std_dev - return sharpe if np.isfinite(sharpe) else 0.0 - - def generate_report(self, output_file: Optional[str] = None) -> None: - """Generate backtest report""" - results = { - 'strategy': self.strategy.name, - 'performance': self.strategy.get_performance_metrics() - } - - print("\n" + "="*60) - print("BACKTEST RESULTS") - print("="*60) - print(f"Strategy: {results['strategy']}") - print(f"Period: {self.start_date.date()} to {self.end_date.date()}") - print(f"Initial Balance: ${self.initial_balance:.2f}") - print(f"Final Equity: ${self.strategy.equity:.2f}") - print(f"Total Return: {((self.strategy.equity - self.initial_balance) / self.initial_balance * 100):.2f}%") - print(f"Total Trades: {self.strategy.total_trades}") - print(f"Win Rate: {(self.strategy.winning_trades / self.strategy.total_trades * 100) if self.strategy.total_trades > 0 else 0:.2f}%") - print(f"Max Drawdown: {self.strategy.max_drawdown * 100:.2f}%") - print("="*60) diff --git a/polymarket/docs/API_REFERENCE.md b/polymarket/docs/API_REFERENCE.md deleted file mode 100644 index d2c15e5..0000000 --- a/polymarket/docs/API_REFERENCE.md +++ /dev/null @@ -1,627 +0,0 @@ -# Polymarket API Reference - -Complete API reference for the Polymarket Trading Framework. - -## Table of Contents - -- [Rate Limits](#rate-limits) -- [Gamma API Client](#gamma-api-client) -- [CLOB API Client](#clob-api-client) -- [Data API Client](#data-api-client) -- [Base Strategy](#base-strategy) -- [Backtesting Engine](#backtesting-engine) -- [Live Trading Engine](#live-trading-engine) -- [Error Handling](#error-handling) - -## Rate Limits - -### Overview - -Polymarket APIs implement rate limiting to ensure fair usage. The framework includes built-in rate limit handling. - -### Rate Limit Specifications - -**Gamma API (Market Discovery)** -- **Rate Limit**: 60 requests per minute per IP -- **Burst**: Up to 10 requests in a single second -- **Headers**: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` - -**CLOB API (Trading & Orderbook)** -- **Rate Limit**: 120 requests per minute per authenticated user -- **Burst**: Up to 20 requests in a single second -- **Headers**: Same as Gamma API - -**Data API (Positions & History)** -- **Rate Limit**: 30 requests per minute per authenticated user -- **Burst**: Up to 5 requests in a single second - -### Handling Rate Limits - -The framework automatically handles rate limits with: -- Automatic request queuing -- Exponential backoff on 429 (Too Many Requests) errors -- Configurable delays between requests (default: 100ms) - -```python -from polymarket.utils.config import Config - -# Configure request delay -Config.REQUEST_DELAY = 0.2 # 200ms between requests -Config.MAX_REQUESTS_PER_MINUTE = 60 -``` - -### Rate Limit Errors - -When rate limited, the API returns: -- **Status Code**: 429 Too Many Requests -- **Response Body**: `{"error": "Rate limit exceeded", "retry_after": 60}` -- **Headers**: `Retry-After: 60` (seconds to wait) - -The framework will automatically retry after the specified delay. - -## Gamma API Client - -### Class: `GammaClient` - -Client for Polymarket Gamma API - Market discovery and metadata. - -#### Constructor - -```python -GammaClient(timeout: int = 30) -``` - -**Parameters:** -- `timeout` (int): Request timeout in seconds (default: 30) - -#### Methods - -##### `get_events()` - -Fetch active events/markets. - -```python -get_events( - active: bool = True, - closed: bool = False, - limit: int = 100, - tag_id: Optional[int] = None, - series_id: Optional[int] = None, - order: Optional[str] = None, - ascending: bool = True -) -> List[Dict] -``` - -**Parameters:** -- `active` (bool): Filter for active events (default: True) -- `closed` (bool): Filter for closed events (default: False) -- `limit` (int): Maximum number of results (default: 100, max: 1000) -- `tag_id` (Optional[int]): Filter by tag/category ID -- `series_id` (Optional[int]): Filter by series ID (for sports) -- `order` (Optional[str]): Sort order (e.g., 'startTime', 'volume') -- `ascending` (bool): Sort ascending or descending (default: True) - -**Returns:** -- `List[Dict]`: List of event dictionaries with fields: - - `id`: Event ID - - `title`: Event title - - `slug`: Event slug (URL-friendly identifier) - - `description`: Event description - - `startDate`: Start date (ISO 8601) - - `endDate`: End date (ISO 8601) - - `markets`: List of markets in this event - - `tags`: List of tag IDs - -**Example:** -```python -from polymarket import GammaClient - -gamma = GammaClient() -events = gamma.get_events(active=True, limit=10, tag_id=21) # Get 10 active crypto events -``` - -##### `get_event_by_slug()` - -Get event details by slug. - -```python -get_event_by_slug(slug: str) -> Optional[Dict] -``` - -**Parameters:** -- `slug` (str): Event slug (e.g., 'will-bitcoin-reach-100k-by-2025') - -**Returns:** -- `Optional[Dict]`: Event dictionary or None if not found - -**Example:** -```python -event = gamma.get_event_by_slug('will-bitcoin-reach-100k-by-2025') -``` - -##### `get_market_by_slug()` - -Get market details by slug. - -```python -get_market_by_slug(slug: str) -> Optional[Dict] -``` - -**Parameters:** -- `slug` (str): Market slug - -**Returns:** -- `Optional[Dict]`: Market dictionary with: - - `clobTokenIds`: List of CLOB token IDs for Yes/No outcomes - - `outcomes`: JSON string of outcome names (e.g., '["Yes", "No"]') - - `outcomePrices`: JSON string of current prices (e.g., '[0.65, 0.35]') - - `question`: Market question - - `endDate`: Market end date - -**Example:** -```python -market = gamma.get_market_by_slug('bitcoin-100k-2025') -prices = gamma.get_market_prices(market) -print(f"Yes: {prices['Yes']:.2%}, No: {prices['No']:.2%}") -``` - -##### `get_tags()` - -Get all available tags/categories. - -```python -get_tags(limit: int = 100) -> List[Dict] -``` - -**Returns:** -- `List[Dict]`: List of tag dictionaries with `id` and `name` fields - -**Example:** -```python -tags = gamma.get_tags() -for tag in tags: - print(f"{tag['id']}: {tag['name']}") -``` - -##### `get_sports()` - -Get all supported sports leagues. - -```python -get_sports() -> List[Dict] -``` - -**Returns:** -- `List[Dict]`: List of sports league dictionaries - -##### `get_market_prices()` - -Extract current prices from market data. - -```python -get_market_prices(market: Dict) -> Dict[str, float] -``` - -**Parameters:** -- `market` (Dict): Market dictionary with outcomes and outcomePrices - -**Returns:** -- `Dict[str, float]`: Dictionary mapping outcome to price (probability) - -**Example:** -```python -prices = gamma.get_market_prices(market) -yes_prob = prices['Yes'] # 0.65 = 65% probability -``` - -## CLOB API Client - -### Class: `ClobClient` - -Client for Polymarket CLOB API - Trading and orderbook data. - -#### Methods - -##### `get_price()` - -Get current price for a token. - -```python -get_price(token_id: str, side: str = 'buy') -> float -``` - -**Parameters:** -- `token_id` (str): CLOB token ID -- `side` (str): 'buy' or 'sell' (default: 'buy') - -**Returns:** -- `float`: Current price (0.0 to 1.0) - -**Example:** -```python -from polymarket import ClobClient - -clob = ClobClient() -token_id = market['clobTokenIds'][0] -price = clob.get_price(token_id, side='buy') -``` - -##### `get_orderbook()` - -Get orderbook depth for a token. - -```python -get_orderbook(token_id: str) -> Dict -``` - -**Returns:** -- `Dict`: Dictionary with: - - `bids`: List of bid orders `[{"price": float, "size": float}, ...]` - - `asks`: List of ask orders `[{"price": float, "size": float}, ...]` - -**Example:** -```python -book = clob.get_orderbook(token_id) -best_bid = book['bids'][0]['price'] -best_ask = book['asks'][0]['price'] -``` - -##### `get_best_bid_ask()` - -Get best bid and ask prices. - -```python -get_best_bid_ask(token_id: str) -> Dict[str, float] -``` - -**Returns:** -- `Dict[str, float]`: Dictionary with: - - `bid`: Best bid price - - `ask`: Best ask price - - `spread`: Bid-ask spread - - `mid`: Mid price ((bid + ask) / 2) - -##### `get_market_depth()` - -Get market depth up to specified levels. - -```python -get_market_depth(token_id: str, levels: int = 10) -> Dict -``` - -**Returns:** -- `Dict`: Dictionary with: - - `bids`: Top N bid levels - - `asks`: Top N ask levels - - `bid_depth`: Cumulative bid depth - - `ask_depth`: Cumulative ask depth - - `total_depth`: Total market depth - -##### `calculate_impact()` - -Calculate estimated price impact for a trade size. - -```python -calculate_impact(token_id: str, size: float, side: str) -> Dict -``` - -**Parameters:** -- `token_id` (str): CLOB token ID -- `size` (float): Trade size in tokens -- `side` (str): 'buy' or 'sell' - -**Returns:** -- `Dict`: Dictionary with: - - `average_price`: Average execution price - - `best_price`: Best available price - - `price_impact`: Price impact percentage - - `levels_consumed`: Number of orderbook levels consumed - - `slippage`: Price slippage - -**Example:** -```python -impact = clob.calculate_impact(token_id, size=100, side='buy') -print(f"Price impact: {impact['price_impact']:.2%}") -print(f"Average price: {impact['average_price']:.4f}") -``` - -## Data API Client - -### Class: `DataClient` - -Client for Polymarket Data API - Positions and history. - -#### Constructor - -```python -DataClient(api_key: Optional[str] = None, timeout: int = 30) -``` - -**Parameters:** -- `api_key` (Optional[str]): API key for authenticated requests -- `timeout` (int): Request timeout in seconds - -#### Methods - -##### `get_positions()` - -Get user positions. - -```python -get_positions(user_address: str) -> List[Dict] -``` - -**Parameters:** -- `user_address` (str): User wallet address (0x...) - -**Returns:** -- `List[Dict]`: List of position dictionaries - -##### `get_trades()` - -Get user trade history. - -```python -get_trades(user_address: str, limit: int = 100) -> List[Dict] -``` - -**Parameters:** -- `user_address` (str): User wallet address -- `limit` (int): Maximum number of trades (default: 100) - -**Returns:** -- `List[Dict]`: List of trade dictionaries - -##### `get_portfolio()` - -Get user portfolio summary. - -```python -get_portfolio(user_address: str) -> Dict -``` - -**Returns:** -- `Dict`: Portfolio dictionary with balances, positions, etc. - -## Base Strategy - -### Class: `BaseStrategy` - -Base class for all Polymarket trading strategies. - -#### Constructor - -```python -BaseStrategy(name: str, initial_balance: float = 1000.0) -``` - -#### Abstract Methods - -##### `analyze_market()` - -Analyze market and generate trading signal. - -```python -@abstractmethod -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - """ - Args: - market_data: Dictionary containing: - - 'event': Event information - - 'market': Market information - - 'prices': Current outcome prices - - 'orderbook': Orderbook data - - 'history': Historical price data (if available) - - Returns: - MarketSignal or None if no trade - """ -``` - -##### `get_parameters()` - -Return strategy parameters. - -```python -@abstractmethod -def get_parameters(self) -> Dict[str, Any]: - """Returns: Dictionary of parameter names and values""" -``` - -#### Properties - -- `name`: Strategy name -- `initial_balance`: Starting USDC balance -- `current_balance`: Current USDC balance -- `equity`: Current equity (balance + unrealized PnL) -- `positions`: Dictionary of open positions (token_id -> Position) -- `total_trades`: Total number of trades executed -- `winning_trades`: Number of winning trades -- `losing_trades`: Number of losing trades -- `max_drawdown`: Maximum drawdown (0.0 to 1.0) - -#### Methods - -##### `update_position()` - -Update position with current price. - -```python -update_position(token_id: str, current_price: float) -> None -``` - -##### `calculate_equity()` - -Calculate current equity (balance + unrealized PnL). - -```python -calculate_equity(self) -> float -``` - -##### `can_open_position()` - -Check if strategy can open a new position. - -```python -can_open_position(self, size: float, token_id: str) -> bool -``` - -##### `get_performance_metrics()` - -Get current performance metrics. - -```python -get_performance_metrics(self) -> Dict[str, Any] -``` - -**Returns:** -- Dictionary with: `total_trades`, `winning_trades`, `losing_trades`, `win_rate`, `total_profit`, `total_loss`, `net_profit`, `profit_factor`, `max_drawdown`, `current_balance`, `equity`, `unrealized_pnl`, `open_positions` - -## Backtesting Engine - -### Class: `BacktestEngine` - -Main backtesting engine for Polymarket strategies. - -#### Constructor - -```python -BacktestEngine( - strategy: BaseStrategy, - start_date: datetime, - end_date: datetime, - initial_balance: float = 1000.0 -) -``` - -#### Methods - -##### `run()` - -Run the backtest. - -```python -run(self, markets: Optional[List[Dict]] = None) -> Dict[str, Any] -``` - -**Returns:** -- Dictionary with backtest results including: - - `total_return`: Total return percentage - - `total_trades`: Number of trades - - `win_rate`: Win rate percentage - - `sharpe_ratio`: Sharpe ratio - - `max_drawdown`: Maximum drawdown percentage - - `equity_curve`: List of equity values over time - - `trades`: List of all trades executed - -##### `generate_report()` - -Generate backtest report. - -```python -generate_report(self, output_file: Optional[str] = None) -> None -``` - -## Live Trading Engine - -### Class: `LiveTradingEngine` - -Live trading engine for Polymarket. - -#### Constructor - -```python -LiveTradingEngine( - strategy: BaseStrategy, - poll_interval: int = 60 -) -``` - -**Parameters:** -- `strategy`: Strategy instance to trade -- `poll_interval`: Seconds between market checks (default: 60) - -#### Methods - -##### `add_market()` - -Add a market to monitor. - -```python -add_market( - event_slug: Optional[str] = None, - market_slug: Optional[str] = None -) -> None -``` - -##### `monitor_tag()` - -Monitor all active markets in a tag/category. - -```python -monitor_tag(self, tag_id: int, limit: int = 20) -> None -``` - -##### `start()` - -Start the live trading engine. - -```python -start(self) -> None -``` - -##### `stop()` - -Stop the trading engine. - -```python -stop(self) -> None -``` - -## Error Handling - -### Common Errors - -#### `ConnectionError` -- **Cause**: Network connectivity issues -- **Solution**: Check internet connection, retry with exponential backoff - -#### `TimeoutError` -- **Cause**: Request timeout exceeded -- **Solution**: Increase timeout value or check API status - -#### `RateLimitError` -- **Cause**: Rate limit exceeded -- **Solution**: Framework automatically handles with retry logic - -#### `AuthenticationError` -- **Cause**: Invalid API credentials -- **Solution**: Verify API keys and wallet address in `.env` file - -#### `MarketNotFoundError` -- **Cause**: Market slug or ID not found -- **Solution**: Verify market exists and is active - -### Error Response Format - -All API errors return JSON: - -```json -{ - "error": "Error message", - "code": "ERROR_CODE", - "details": {} -} -``` - -### Retry Logic - -The framework implements automatic retry for: -- Network errors (up to 3 retries) -- Rate limit errors (with exponential backoff) -- 5xx server errors (up to 3 retries) - -No retry for: -- 4xx client errors (except 429) -- Authentication errors -- Validation errors diff --git a/polymarket/docs/DOCUMENTATION_SUMMARY.md b/polymarket/docs/DOCUMENTATION_SUMMARY.md deleted file mode 100644 index df9b227..0000000 --- a/polymarket/docs/DOCUMENTATION_SUMMARY.md +++ /dev/null @@ -1,166 +0,0 @@ -# Documentation Summary & Evaluation - -## Documentation Completeness Assessment - -### ✅ Complete Documentation - -1. **API Reference** (`API_REFERENCE.md`) - - ✅ Rate limits for all APIs (Gamma, CLOB, Data) - - ✅ Complete endpoint documentation - - ✅ Request/response formats - - ✅ Error handling and codes - - ✅ Code examples for all methods - - ✅ Parameter descriptions - - ✅ Return type specifications - -2. **Glossary** (`GLOSSARY.md`) - - ✅ Core concepts defined - - ✅ Trading terminology - - ✅ Position management terms - - ✅ Performance metrics explained - - ✅ API terms documented - - ✅ Common abbreviations - - ✅ Price notation explained - -3. **Strategy Development Guide** (`STRATEGY_GUIDE.md`) - - ✅ Step-by-step strategy creation - - ✅ Complete examples - - ✅ Advanced patterns - - ✅ Best practices - - ✅ Common strategy types - - ✅ Testing guidelines - - ✅ Strategy checklist - -4. **Quick Start Guide** (`QUICKSTART.md`) - - ✅ Installation instructions - - ✅ Configuration setup - - ✅ Basic examples - - ✅ Common use cases - -5. **Implementation Notes** (`IMPLEMENTATION_NOTES.md`) - - ✅ Framework status - - ✅ Known limitations - - ✅ Next steps - - ✅ Security notes - -### Documentation Quality Metrics - -#### Coverage: 95% -- All major components documented -- All public APIs documented -- Examples provided for common use cases -- Edge cases and error handling covered - -#### Clarity: Excellent -- Clear explanations -- Code examples for every concept -- Step-by-step guides -- Terminology consistently defined - -#### Completeness: Very Good -- API methods fully documented -- Parameters and return types specified -- Error handling explained -- Best practices included - -#### Usability: Excellent -- Quick start guide for beginners -- Advanced guides for experienced users -- Examples for all major features -- Troubleshooting information - -## Documentation Structure - -``` -polymarket/ -├── README.md # Main entry point -├── IMPLEMENTATION_NOTES.md # Framework status -├── example_usage.py # Code examples -└── docs/ - ├── QUICKSTART.md # Getting started - ├── API_REFERENCE.md # Complete API docs - ├── STRATEGY_GUIDE.md # Strategy development - ├── GLOSSARY.md # Terminology - └── DOCUMENTATION_SUMMARY.md # This file -``` - -## Key Documentation Features - -### 1. Rate Limits -- **Documented**: ✅ Complete -- **Details**: Limits for all APIs, handling strategies, error responses -- **Location**: `API_REFERENCE.md` → Rate Limits section - -### 2. Endpoints Reference -- **Documented**: ✅ Complete -- **Details**: All methods, parameters, return types, examples -- **Location**: `API_REFERENCE.md` → API Client sections - -### 3. Glossary -- **Documented**: ✅ Complete -- **Details**: 50+ terms defined, abbreviations, notation -- **Location**: `GLOSSARY.md` - -### 4. Strategy Development -- **Documented**: ✅ Complete -- **Details**: Creation guide, patterns, best practices, testing -- **Location**: `STRATEGY_GUIDE.md` - -## What's Well Documented - -1. **API Usage**: Every method has examples and clear parameter descriptions -2. **Error Handling**: Comprehensive error documentation with solutions -3. **Rate Limiting**: Detailed rate limit specifications and handling -4. **Strategy Creation**: Step-by-step guide with complete examples -5. **Terminology**: Extensive glossary covering all key concepts -6. **Configuration**: Clear setup instructions and examples - -## Minor Gaps (Non-Critical) - -1. **Market Makers**: Not documented (optional feature) - - Would require additional Polymarket docs - - Not essential for basic trading - -2. **WebSocket Integration**: Mentioned but not detailed - - Framework doesn't implement WebSockets yet - - Documented in implementation notes - -3. **Advanced Order Types**: Basic orders documented, advanced types not - - Market orders, limit orders covered - - Stop orders, conditional orders not detailed - -## Documentation Best Practices Followed - -✅ **Clear Structure**: Logical organization with table of contents -✅ **Code Examples**: Every concept has working code examples -✅ **Cross-References**: Links between related documentation -✅ **Progressive Disclosure**: Basic → Advanced content flow -✅ **Searchability**: Well-organized sections and headings -✅ **Completeness**: All public APIs documented -✅ **Accuracy**: Documentation matches code implementation - -## Recommendations - -### For Users -1. Start with `QUICKSTART.md` for basic setup -2. Read `STRATEGY_GUIDE.md` before creating strategies -3. Reference `API_REFERENCE.md` for specific method details -4. Check `GLOSSARY.md` for terminology questions - -### For Developers -1. Review `IMPLEMENTATION_NOTES.md` for framework status -2. Check `API_REFERENCE.md` for integration details -3. Follow patterns in `STRATEGY_GUIDE.md` for new strategies - -## Conclusion - -The Polymarket framework documentation is **comprehensive and production-ready**. All critical components are documented with examples, and the documentation structure supports both beginners and advanced users. - -**Overall Grade: A (95/100)** - -- Coverage: 95/100 -- Clarity: 98/100 -- Completeness: 95/100 -- Usability: 97/100 - -The framework is well-documented and ready for use. Minor gaps exist only in optional features (market makers) that aren't essential for core functionality. diff --git a/polymarket/docs/GLOSSARY.md b/polymarket/docs/GLOSSARY.md deleted file mode 100644 index 2b1b4d2..0000000 --- a/polymarket/docs/GLOSSARY.md +++ /dev/null @@ -1,200 +0,0 @@ -# Polymarket Glossary - -Complete terminology reference for Polymarket prediction markets. - -## Core Concepts - -### Prediction Market -A market where participants trade contracts based on the outcome of future events. Prices represent the market's collective probability assessment. - -### Market -A specific question with binary or multiple outcomes. Each market resolves to one outcome based on real-world events. - -### Event -A collection of related markets. For example, an election event may contain multiple markets for different races. - -### Outcome -A possible result of a market. Binary markets have two outcomes: "Yes" and "No". - -### Token -A conditional token representing a position in a specific outcome. Each outcome has its own token (e.g., "Yes Token", "No Token"). - -### CLOB Token ID -A unique identifier for a conditional token in the CLOB (Central Limit Order Book) system. Used for trading and order placement. - -## Trading Terms - -### Bid -An offer to buy tokens at a specified price. The highest bid is the best bid. - -### Ask -An offer to sell tokens at a specified price. The lowest ask is the best ask. - -### Spread -The difference between the best ask and best bid prices. Represents the cost of immediate execution. - -### Mid Price -The average of the best bid and best ask: `(bid + ask) / 2`. Often used as a fair value estimate. - -### Order Book -A list of all open buy (bids) and sell (asks) orders for a token, sorted by price and time. - -### Market Depth -The total volume available at each price level in the order book. Indicates liquidity. - -### Price Impact -The change in price caused by executing a trade of a given size. Larger trades typically have higher price impact. - -### Slippage -The difference between expected execution price and actual execution price. Caused by consuming multiple order book levels. - -### Liquidity -The ease with which tokens can be bought or sold without significantly affecting the price. High liquidity = tight spreads and deep order books. - -## Position Management - -### Position -An open trade holding tokens in a specific outcome. Can be long (holding Yes tokens) or short (holding No tokens). - -### Entry Price -The average price at which a position was opened. - -### Exit Price -The price at which a position is closed. - -### Unrealized PnL -Profit or loss on an open position, calculated from current market price. - -### Realized PnL -Profit or loss from a closed position. - -### Equity -Total account value: `balance + unrealized_pnl` - -### Balance -Available USDC (USD Coin) for trading. - -## Market Resolution - -### Resolution Date -The date and time when a market resolves based on the outcome of the event. - -### Resolution Source -The authoritative source used to determine the outcome (e.g., official election results, sports league data). - -### Disputed Resolution -A market resolution that is challenged by participants. May require manual review. - -### Settlement -The process of distributing payouts to winning positions after market resolution. - -## Performance Metrics - -### Win Rate -Percentage of profitable trades: `(winning_trades / total_trades) * 100` - -### Profit Factor -Ratio of total profit to total loss: `abs(total_profit / total_loss)`. Values > 1 indicate profitability. - -### Sharpe Ratio -Risk-adjusted return metric. Higher values indicate better risk-adjusted performance. - -### Sortino Ratio -Similar to Sharpe ratio but only considers downside volatility. - -### Maximum Drawdown -The largest peak-to-trough decline in equity during a trading period. Expressed as a percentage. - -### Calmar Ratio -Return divided by maximum drawdown. Higher values indicate better risk-adjusted returns. - -### Expectancy -Expected profit per trade: `(win_rate * avg_win) - ((1 - win_rate) * avg_loss)` - -## API Terms - -### Rate Limit -Maximum number of API requests allowed per time period. Exceeding limits results in 429 errors. - -### API Key -Authentication credential for accessing authenticated endpoints. - -### Signature Type -Method of authentication: -- `0`: EOA (Externally Owned Account) - standard wallet -- `1`: POLY_PROXY - proxy contract -- `2`: GNOSIS_SAFE - Gnosis Safe multisig - -### Funder Address -Wallet address used to fund trades and pay gas fees. - -### Chain ID -Blockchain network identifier: -- `137`: Polygon mainnet (production) -- `80001`: Mumbai testnet (testing) - -## Strategy Terms - -### Signal -A trading recommendation generated by a strategy, including: -- Action: BUY, SELL, or HOLD -- Token ID: Which outcome to trade -- Size: Position size -- Confidence: Strategy's confidence level (0.0 to 1.0) - -### Confidence -A strategy's assessment of signal quality, typically between 0.0 (low) and 1.0 (high). Used to filter trades. - -### Risk Management -Rules and limits to protect capital: -- Maximum position size -- Maximum total exposure -- Stop losses -- Position limits - -### Backtesting -Simulating strategy performance on historical data to evaluate profitability before live trading. - -### Paper Trading -Trading with simulated funds to test strategies without financial risk. - -## Market Types - -### Binary Market -A market with exactly two outcomes: Yes and No. Most common market type. - -### Scalar Market -A market with a range of possible outcomes (e.g., "Bitcoin price will be between $50k-$60k"). - -### Multi-Outcome Market -A market with more than two discrete outcomes (e.g., "Which team will win?" with multiple teams). - -## Tag Categories - -Common market categories identified by tag IDs: - -- **Crypto** (tag_id: 21): Cryptocurrency-related markets -- **Politics** (tag_id: varies): Political events and elections -- **Sports** (tag_id: varies): Sports betting markets -- **Economics** (tag_id: varies): Economic indicators and events -- **Technology** (tag_id: varies): Tech industry events - -## Common Abbreviations - -- **CLOB**: Central Limit Order Book -- **PnL**: Profit and Loss -- **USDC**: USD Coin (stablecoin) -- **EOA**: Externally Owned Account -- **API**: Application Programming Interface -- **REST**: Representational State Transfer (API protocol) -- **WebSocket**: Real-time communication protocol -- **JSON**: JavaScript Object Notation (data format) - -## Price Notation - -Prices in Polymarket are represented as probabilities between 0.0 and 1.0: -- `0.50` = 50% probability = $0.50 per share -- `0.75` = 75% probability = $0.75 per share -- `1.00` = 100% probability = $1.00 per share (guaranteed outcome) - -For binary markets, Yes + No prices should sum to approximately 1.0 (accounting for spread). diff --git a/polymarket/docs/QUICKSTART.md b/polymarket/docs/QUICKSTART.md deleted file mode 100644 index 2258c36..0000000 --- a/polymarket/docs/QUICKSTART.md +++ /dev/null @@ -1,173 +0,0 @@ -# Polymarket Trading Framework - Quick Start Guide - -## Installation - -```bash -cd polymarket -pip install -r requirements.txt - -# For live trading, also install: -pip install py-clob-client ethers -``` - -## Configuration - -Create a `.env` file in the `polymarket` directory: - -```env -# Required for live trading -POLYMARKET_PRIVATE_KEY=your_private_key_here -POLYMARKET_CHAIN_ID=137 -POLYMARKET_SIGNATURE_TYPE=0 -POLYMARKET_FUNDER_ADDRESS=your_wallet_address - -# Optional -POLYMARKET_INITIAL_BALANCE=1000.0 -POLYMARKET_MAX_POSITION_SIZE=0.5 -POLYMARKET_REQUEST_DELAY=0.1 -``` - -## Quick Examples - -### 1. Discover Markets - -```python -from polymarket import GammaClient - -gamma = GammaClient() -events = gamma.get_events(active=True, closed=False, limit=10) -for event in events: - print(f"{event['title']}: {event['slug']}") -``` - -### 2. Get Market Prices - -```python -from polymarket import GammaClient, ClobClient - -gamma = GammaClient() -clob = ClobClient() - -# Get market -market = gamma.get_market_by_slug('will-bitcoin-reach-100k-by-2025') -prices = gamma.get_market_prices(market) - -# Get orderbook -token_id = market['clobTokenIds'][0] -best_bid_ask = clob.get_best_bid_ask(token_id) -print(f"Best Bid: {best_bid_ask['bid']}, Best Ask: {best_bid_ask['ask']}") -``` - -### 3. Run a Backtest - -```python -from datetime import datetime, timedelta -from polymarket import BacktestEngine -from polymarket.strategies.examples import SimpleProbabilityStrategy - -# Create strategy -strategy = SimpleProbabilityStrategy( - threshold=0.15, # Trade when probability deviates 15% from 0.5 - min_confidence=0.7 -) - -# Set period -end_date = datetime.now() -start_date = end_date - timedelta(days=30) - -# Run backtest -engine = BacktestEngine(strategy, start_date, end_date, initial_balance=1000.0) -results = engine.run() -engine.generate_report() -``` - -### 4. Live Trading - -```python -from polymarket import LiveTradingEngine -from polymarket.strategies.examples import SimpleProbabilityStrategy - -# Create strategy -strategy = SimpleProbabilityStrategy() - -# Create engine -engine = LiveTradingEngine(strategy, poll_interval=60) - -# Add markets to monitor -engine.monitor_tag(tag_id=21, limit=10) # Monitor crypto markets - -# Start trading -engine.start() -``` - -## Creating Your Own Strategy - -```python -from polymarket.strategies import BaseStrategy, MarketSignal - -class MyStrategy(BaseStrategy): - def __init__(self): - super().__init__(name="MyStrategy", initial_balance=1000.0) - self.my_parameter = 0.2 - - def analyze_market(self, market_data): - prices = market_data.get('prices', {}) - yes_price = prices.get('Yes', 0.5) - - # Your trading logic here - if yes_price < 0.3: # Undervalued - return MarketSignal( - action='BUY', - token_id=market_data['market']['clobTokenIds'][0], - size=0.2, # 20% of balance - confidence=0.8, - reason="Yes probability is undervalued", - metadata={} - ) - - return None - - def get_parameters(self): - return {'my_parameter': self.my_parameter} -``` - -## API Reference - -### GammaClient (Market Discovery) - -- `get_events()` - Fetch active events -- `get_event_by_slug()` - Get event by slug -- `get_market_by_slug()` - Get market by slug -- `get_tags()` - Get all categories -- `get_sports()` - Get sports leagues -- `search_events()` - Search events - -### ClobClient (Trading Data) - -- `get_price()` - Get current price -- `get_orderbook()` - Get full orderbook -- `get_best_bid_ask()` - Get best bid/ask -- `get_market_depth()` - Get market depth -- `calculate_impact()` - Calculate price impact - -### DataClient (Portfolio) - -- `get_positions()` - Get user positions -- `get_trades()` - Get trade history -- `get_portfolio()` - Get portfolio summary - -## Notes - -1. **Historical Data**: Polymarket API may not provide full historical data. The backtesting engine uses simulated price evolution. For production, you'd need to store historical snapshots. - -2. **Rate Limits**: Be mindful of API rate limits. The framework includes request delays, but check Polymarket documentation for current limits. - -3. **Authentication**: Live trading requires proper authentication with `py-clob-client`. See Polymarket documentation for setup. - -4. **Testing**: Always test strategies thoroughly in backtesting before live trading. - -## Next Steps - -- Read [Strategy Development Guide](STRATEGIES.md) -- Read [Backtesting Guide](BACKTESTING.md) -- Read [Live Trading Guide](LIVE_TRADING.md) diff --git a/polymarket/docs/STRATEGY_GUIDE.md b/polymarket/docs/STRATEGY_GUIDE.md deleted file mode 100644 index b45d6f0..0000000 --- a/polymarket/docs/STRATEGY_GUIDE.md +++ /dev/null @@ -1,423 +0,0 @@ -# Strategy Development Guide - -Complete guide to developing trading strategies for Polymarket. - -## Table of Contents - -- [Strategy Basics](#strategy-basics) -- [Creating Your First Strategy](#creating-your-first-strategy) -- [Advanced Patterns](#advanced-patterns) -- [Best Practices](#best-practices) -- [Common Strategies](#common-strategies) -- [Testing Strategies](#testing-strategies) - -## Strategy Basics - -### What is a Strategy? - -A strategy is a Python class that: -1. Analyzes market data -2. Generates trading signals -3. Manages risk and positions -4. Tracks performance - -### Strategy Lifecycle - -1. **Initialization**: Set up parameters and initial state -2. **Market Analysis**: Receive market data and analyze -3. **Signal Generation**: Decide whether to trade -4. **Position Management**: Track open positions -5. **Performance Tracking**: Monitor PnL and metrics - -## Creating Your First Strategy - -### Step 1: Inherit from BaseStrategy - -```python -from polymarket.strategies import BaseStrategy, MarketSignal - -class MyStrategy(BaseStrategy): - def __init__(self): - super().__init__(name="MyStrategy", initial_balance=1000.0) - # Your initialization code -``` - -### Step 2: Implement analyze_market() - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - """ - Analyze market and generate signal. - - Args: - market_data: Dictionary with: - - 'event': Event information - - 'market': Market information - - 'prices': Current outcome prices {'Yes': 0.65, 'No': 0.35} - - 'orderbook': Orderbook data - - 'timestamp': Current timestamp - - Returns: - MarketSignal or None - """ - prices = market_data.get('prices', {}) - yes_price = prices.get('Yes', 0.5) - - # Your trading logic here - if yes_price < 0.4: # Undervalued - return MarketSignal( - action='BUY', - token_id=market_data['market']['clobTokenIds'][0], - size=0.2, # 20% of balance - confidence=0.8, - reason="Yes probability is undervalued", - metadata={'yes_price': yes_price} - ) - - return None # No trade -``` - -### Step 3: Implement get_parameters() - -```python -def get_parameters(self) -> Dict[str, Any]: - """Return strategy parameters""" - return { - 'threshold': 0.4, - 'position_size': 0.2 - } -``` - -### Complete Example - -```python -from typing import Dict, Optional -from polymarket.strategies import BaseStrategy, MarketSignal - -class MeanReversionStrategy(BaseStrategy): - """ - Simple mean reversion strategy. - Buys when price deviates significantly from 0.5. - """ - - def __init__(self, threshold: float = 0.15): - super().__init__(name="MeanReversion", initial_balance=1000.0) - self.threshold = threshold - - def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - prices = market_data.get('prices', {}) - yes_price = prices.get('Yes', 0.5) - - # Calculate deviation from fair value (0.5) - deviation = abs(yes_price - 0.5) - - if deviation < self.threshold: - return None # Not enough deviation - - # Determine action - if yes_price < (0.5 - self.threshold): - # Yes is undervalued, buy - confidence = min(1.0, deviation / self.threshold) - - return MarketSignal( - action='BUY', - token_id=market_data['market']['clobTokenIds'][0], - size=0.2, - confidence=confidence, - reason=f"Yes price {yes_price:.2%} is {deviation:.2%} below fair value", - metadata={'yes_price': yes_price, 'deviation': deviation} - ) - - elif yes_price > (0.5 + self.threshold): - # Yes is overvalued, close position if we have one - token_id = market_data['market']['clobTokenIds'][0] - if token_id in self.positions: - return MarketSignal( - action='SELL', - token_id=token_id, - size=1.0, # Close entire position - confidence=confidence, - reason=f"Yes price {yes_price:.2%} is {deviation:.2%} above fair value", - metadata={'yes_price': yes_price, 'deviation': deviation} - ) - - return None - - def get_parameters(self) -> Dict: - return {'threshold': self.threshold} -``` - -## Advanced Patterns - -### Using Orderbook Data - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - orderbook = market_data.get('orderbook', {}) - bids = orderbook.get('bids', []) - asks = orderbook.get('asks', []) - - if not bids or not asks: - return None - - # Calculate bid-ask spread - best_bid = bids[0]['price'] - best_ask = asks[0]['price'] - spread = best_ask - best_bid - - # Trade when spread is tight (good liquidity) - if spread < 0.02: # 2% spread - # Your trading logic - pass -``` - -### Using Historical Data - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - history = market_data.get('history', []) - - if len(history) < 20: - return None # Not enough data - - # Calculate moving average - recent_prices = [h['price'] for h in history[-20:]] - ma = sum(recent_prices) / len(recent_prices) - - current_price = market_data['prices']['Yes'] - - # Mean reversion: buy when below MA - if current_price < ma * 0.95: - return MarketSignal(...) -``` - -### Position Sizing Based on Confidence - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - # Calculate confidence - confidence = self.calculate_confidence(market_data) - - # Size position based on confidence - # Higher confidence = larger position - base_size = 0.1 # 10% base - size = base_size * confidence # Scale by confidence - - return MarketSignal( - action='BUY', - token_id=..., - size=size, - confidence=confidence, - ... - ) -``` - -### Risk Management - -```python -class RiskManagedStrategy(BaseStrategy): - def __init__(self): - super().__init__(name="RiskManaged", initial_balance=1000.0) - # Override risk limits - self.max_position_size = 0.3 # Max 30% per position - self.max_total_exposure = 0.6 # Max 60% total - self.min_confidence = 0.7 # Only trade high confidence - - def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - # Check if we can open new position - if len(self.positions) >= 3: - return None # Max 3 positions - - # Your trading logic - signal = self.generate_signal(market_data) - - if signal and signal.confidence >= self.min_confidence: - # Verify we can open position - size_usdc = signal.size * self.current_balance - if self.can_open_position(size_usdc, signal.token_id): - return signal - - return None -``` - -## Best Practices - -### 1. Always Check Data Availability - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - prices = market_data.get('prices', {}) - if not prices: - return None # No price data - - yes_price = prices.get('Yes') - if yes_price is None: - return None # Missing Yes price -``` - -### 2. Validate Token IDs - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - market = market_data.get('market', {}) - token_ids = market.get('clobTokenIds', []) - - if not token_ids: - return None # No token IDs available - - token_id = token_ids[0] - # Use token_id... -``` - -### 3. Use Confidence Thresholds - -```python -# Only trade high-confidence signals -if signal.confidence < self.min_confidence: - return None -``` - -### 4. Log Trading Decisions - -```python -import logging - -logger = logging.getLogger(__name__) - -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - signal = self.generate_signal(market_data) - - if signal: - logger.info(f"Signal: {signal.action} {signal.token_id} " - f"size={signal.size:.2%} confidence={signal.confidence:.2f} " - f"reason: {signal.reason}") - - return signal -``` - -### 5. Handle Edge Cases - -```python -def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - prices = market_data.get('prices', {}) - yes_price = prices.get('Yes', 0.5) - no_price = prices.get('No', 0.5) - - # Check if prices are valid - if yes_price <= 0 or yes_price >= 1: - return None # Invalid price - - # Check if market is close to resolution - market = market_data.get('market', {}) - end_date = market.get('endDate') - if end_date and self.is_near_resolution(end_date): - return None # Too close to resolution, avoid trading -``` - -## Common Strategies - -### 1. Mean Reversion - -Buy when price deviates from fair value (0.5), sell when it returns. - -### 2. Momentum - -Buy when price is trending up, sell when trend reverses. - -### 3. Arbitrage - -Exploit price differences between related markets. - -### 4. Market Making - -Provide liquidity by placing both buy and sell orders. - -### 5. News-Based - -Trade based on external information and news events. - -### 6. Statistical Arbitrage - -Use statistical models to identify mispriced markets. - -## Testing Strategies - -### Unit Testing - -```python -import unittest -from polymarket.strategies.examples import SimpleProbabilityStrategy - -class TestStrategy(unittest.TestCase): - def setUp(self): - self.strategy = SimpleProbabilityStrategy(threshold=0.15) - - def test_analyze_market_undervalued(self): - market_data = { - 'market': {'clobTokenIds': ['token123']}, - 'prices': {'Yes': 0.3, 'No': 0.7} # Undervalued - } - - signal = self.strategy.analyze_market(market_data) - - self.assertIsNotNone(signal) - self.assertEqual(signal.action, 'BUY') - self.assertGreater(signal.confidence, 0.7) -``` - -### Backtesting - -```python -from datetime import datetime, timedelta -from polymarket import BacktestEngine - -strategy = MyStrategy() -end_date = datetime.now() -start_date = end_date - timedelta(days=30) - -engine = BacktestEngine(strategy, start_date, end_date) -results = engine.run() - -print(f"Total Return: {results['total_return']:.2f}%") -print(f"Win Rate: {results['win_rate']:.2f}%") -``` - -### Paper Trading - -Test strategies with live data but simulated execution: - -```python -from polymarket import LiveTradingEngine - -strategy = MyStrategy() -engine = LiveTradingEngine(strategy, poll_interval=60) - -# Add markets -engine.monitor_tag(tag_id=21, limit=10) - -# Start (will simulate orders) -engine.start() -``` - -## Strategy Checklist - -Before deploying a strategy: - -- [ ] Strategy inherits from `BaseStrategy` -- [ ] `analyze_market()` implemented -- [ ] `get_parameters()` implemented -- [ ] Risk management limits set -- [ ] Edge cases handled -- [ ] Data validation included -- [ ] Backtested on historical data -- [ ] Paper traded successfully -- [ ] Performance metrics reviewed -- [ ] Error handling implemented -- [ ] Logging added - -## Next Steps - -- Read [API Reference](API_REFERENCE.md) for detailed API documentation -- Check [Glossary](GLOSSARY.md) for terminology -- Review example strategies in `strategies/examples/` -- Test your strategy thoroughly before live trading diff --git a/polymarket/gui/QUICKSTART.md b/polymarket/gui/QUICKSTART.md deleted file mode 100644 index 198b3f3..0000000 --- a/polymarket/gui/QUICKSTART.md +++ /dev/null @@ -1,98 +0,0 @@ -# Quick Start - Cyberpunk Dashboard - -## 🚀 Get Started in 3 Steps - -### Step 1: Install Dependencies - -```bash -cd polymarket/gui -pip install -r requirements.txt -``` - -### Step 2: Run the Dashboard - -**Windows:** -```bash -run.bat -``` - -**Linux/Mac:** -```bash -python app.py -``` - -### Step 3: Open in Browser - -Open: **http://localhost:5000** - -## 🎮 Using the Dashboard - -### Starting Trading - -1. **Set Parameters**: - - **Threshold**: How much price deviation to trigger trades (0.15 = 15%) - - **Min Confidence**: Minimum confidence level (0.7 = 70%) - - **Initial Balance**: Starting USDC (e.g., 1000) - - **Category**: Market category (Crypto, Politics, Sports) - -2. **Click "START TRADING"** - -3. **Monitor**: - - Watch markets update in real-time - - See your balance and equity - - Track open positions - - View performance metrics - -### Features - -- **Real-time Market Data**: Markets update every 2 seconds -- **Live Strategy Metrics**: Balance, equity, P&L, win rate -- **Position Tracking**: See all open positions with P&L -- **Cyberpunk Theme**: Neon colors, glitch effects, animations - -## 🎨 Customization - -### Change Colors - -Edit `static/style.css`: - -```css -:root { - --neon-cyan: #00ffff; /* Main color */ - --neon-pink: #ff00ff; /* Accent */ - --neon-green: #00ff00; /* Success */ -} -``` - -### Change Update Frequency - -Edit `static/script.js`: - -```javascript -updateInterval = setInterval(..., 2000); // Change 2000 to desired ms -``` - -## 🐛 Troubleshooting - -**Port 5000 already in use?** -- Edit `app.py`, change: `socketio.run(app, port=5001)` -- Then open: http://localhost:5001 - -**Markets not loading?** -- Check internet connection -- Verify Polymarket API is accessible -- Check browser console (F12) for errors - -**Trading won't start?** -- Ensure all parameters are valid -- Check that markets are available -- Review terminal output for errors - -## 💡 Tips - -- Start with small balance for testing -- Use threshold 0.15-0.20 for balanced trading -- Monitor win rate - should be > 50% for good strategies -- Watch drawdown - keep it under 20% - -Enjoy your cyberpunk trading! 💀🚀 diff --git a/polymarket/gui/README.md b/polymarket/gui/README.md deleted file mode 100644 index bfc841a..0000000 --- a/polymarket/gui/README.md +++ /dev/null @@ -1,125 +0,0 @@ -# Cyberpunk Polymarket Trading Dashboard - -A futuristic, cyberpunk-themed web-based GUI for the Polymarket trading framework. - -## Features - -- 🎮 **Cyberpunk Aesthetic**: Neon colors, glitch effects, and futuristic design -- 📊 **Real-time Market Data**: Live market prices and orderbook data -- 🎯 **Strategy Control**: Start/stop trading with customizable parameters -- 📈 **Performance Metrics**: Real-time P&L, win rate, and position tracking -- 💹 **Position Management**: Visual display of open positions with P&L -- 🔌 **WebSocket Updates**: Real-time data streaming - -## Installation - -```bash -cd polymarket/gui -pip install -r requirements.txt -``` - -## Running the Dashboard - -```bash -python app.py -``` - -Then open your browser to: **http://localhost:5000** - -## Usage - -1. **Configure Strategy**: - - Set threshold (probability deviation) - - Set minimum confidence - - Set initial balance - - Select market category - -2. **Start Trading**: - - Click "START TRADING" button - - Monitor real-time metrics - - View open positions - -3. **Monitor Performance**: - - Watch balance and equity updates - - Track win rate and P&L - - View position details - -4. **Stop Trading**: - - Click "STOP TRADING" when done - -## Controls - -- **Threshold**: Probability deviation threshold (0.05 - 0.3) -- **Min Confidence**: Minimum confidence to trade (0.5 - 1.0) -- **Initial Balance**: Starting USDC balance -- **Category**: Market category to monitor (Crypto, Politics, Sports) - -## Features - -### Real-time Updates -- Market prices update every 2 seconds -- Strategy metrics update in real-time -- Position P&L calculated live - -### Visual Feedback -- Neon color scheme (cyan, pink, green) -- Glitch effects and animations -- Status indicators -- Notification system - -### Responsive Design -- Works on desktop and tablet -- Grid-based layout -- Scrollable market lists - -## Troubleshooting - -**Port already in use?** -- Change port in `app.py`: `socketio.run(app, port=5001)` - -**Markets not loading?** -- Check internet connection -- Verify Polymarket API is accessible -- Check browser console for errors - -**Trading not starting?** -- Ensure strategy parameters are valid -- Check that markets are available -- Review server logs for errors - -## Customization - -### Colors -Edit `static/style.css` CSS variables: -```css -:root { - --neon-cyan: #00ffff; - --neon-pink: #ff00ff; - --neon-green: #00ff00; -} -``` - -### Update Frequency -Change in `static/script.js`: -```javascript -updateInterval = setInterval(..., 2000); // 2 seconds -``` - -## Screenshots - -The dashboard features: -- Glitch text header with "POLYMARKET" -- Three-panel layout (Strategy, Markets, Performance) -- Bottom panel for positions -- Animated background grid -- Particle effects -- Neon glow effects - -## Notes - -- The dashboard runs in simulation mode by default -- For live trading, configure API credentials in `.env` -- All trading is done through the framework's strategy system -- WebSocket provides real-time updates when available - -Enjoy your cyberpunk trading experience! 🚀💀 diff --git a/polymarket/gui/README_COMPONENTS.md b/polymarket/gui/README_COMPONENTS.md deleted file mode 100644 index 35206c8..0000000 --- a/polymarket/gui/README_COMPONENTS.md +++ /dev/null @@ -1,40 +0,0 @@ -# Component Architecture - -The GUI has been refactored into a modular component-based architecture to prevent code bloat. - -## Frontend Components - -### `/static/js/components/` -- **MarketsComponent.js** - Market data fetching and display -- **StrategyComponent.js** - Strategy controls and status -- **PositionsComponent.js** - Position display -- **BacktestComponent.js** - Backtesting functionality - -### `/static/js/utils/` -- **Notification.js** - Global notification system -- **WebSocketManager.js** - WebSocket event management - -### `/static/js/app.js` -- Main application entry point -- Initializes all components -- Manages component lifecycle - -## Backend Blueprints - -### `/api/` -- **markets.py** - Market data endpoints -- **strategy.py** - Strategy control endpoints -- **backtest.py** - Backtesting endpoints -- **__init__.py** - Blueprint registration - -## Benefits - -1. **Separation of Concerns** - Each component handles one responsibility -2. **Reusability** - Components can be reused across different views -3. **Maintainability** - Easier to find and fix bugs -4. **Testability** - Components can be tested independently -5. **Scalability** - Easy to add new features without bloating existing code - -## Usage - -The app automatically loads all components on startup. Each component manages its own state and updates. diff --git a/polymarket/gui/__init__.py b/polymarket/gui/__init__.py deleted file mode 100644 index a6a162a..0000000 --- a/polymarket/gui/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Cyberpunk Polymarket Trading Dashboard""" diff --git a/polymarket/gui/__pycache__/app.cpython-312.pyc b/polymarket/gui/__pycache__/app.cpython-312.pyc deleted file mode 100644 index 5c6dd6e..0000000 Binary files a/polymarket/gui/__pycache__/app.cpython-312.pyc and /dev/null differ diff --git a/polymarket/gui/api/__init__.py b/polymarket/gui/api/__init__.py deleted file mode 100644 index 2a61d8e..0000000 --- a/polymarket/gui/api/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -API Blueprints -""" - -from flask import Blueprint - -def create_api_blueprint(): - """Create and register all API blueprints""" - from api.markets import markets_bp - from api.strategy import strategy_bp - from api.backtest import backtest_bp - - api_bp = Blueprint('api', __name__, url_prefix='/api') - - api_bp.register_blueprint(markets_bp) - api_bp.register_blueprint(strategy_bp) - api_bp.register_blueprint(backtest_bp) - - return api_bp diff --git a/polymarket/gui/api/__pycache__/__init__.cpython-312.pyc b/polymarket/gui/api/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index b696ee7..0000000 Binary files a/polymarket/gui/api/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/gui/api/__pycache__/backtest.cpython-312.pyc b/polymarket/gui/api/__pycache__/backtest.cpython-312.pyc deleted file mode 100644 index 588082b..0000000 Binary files a/polymarket/gui/api/__pycache__/backtest.cpython-312.pyc and /dev/null differ diff --git a/polymarket/gui/api/__pycache__/markets.cpython-312.pyc b/polymarket/gui/api/__pycache__/markets.cpython-312.pyc deleted file mode 100644 index 6c2cf53..0000000 Binary files a/polymarket/gui/api/__pycache__/markets.cpython-312.pyc and /dev/null differ diff --git a/polymarket/gui/api/__pycache__/strategy.cpython-312.pyc b/polymarket/gui/api/__pycache__/strategy.cpython-312.pyc deleted file mode 100644 index 3ecd24c..0000000 Binary files a/polymarket/gui/api/__pycache__/strategy.cpython-312.pyc and /dev/null differ diff --git a/polymarket/gui/api/backtest.py b/polymarket/gui/api/backtest.py deleted file mode 100644 index d3d8880..0000000 --- a/polymarket/gui/api/backtest.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Backtest API Routes -""" - -from flask import Blueprint, jsonify, request -from flask_socketio import emit -from datetime import datetime, timedelta -import threading -import time -import numpy as np - -backtest_bp = Blueprint('backtest', __name__) - -# Global state -backtest_running = False -backtest_results = None -socketio = None # Will be set by app - - -def set_socketio(sio): - """Set SocketIO instance""" - global socketio - socketio = sio - - -@backtest_bp.route('/backtest/run', methods=['POST']) -def run_backtest(): - """Run backtest""" - global backtest_running, backtest_results - - if backtest_running: - return jsonify({'error': 'Backtest already running'}), 400 - - try: - data = request.json - start_date = data.get('start_date') - end_date = data.get('end_date') - initial_balance = float(data.get('initial_balance', 1000.0)) - threshold = float(data.get('threshold', 0.15)) - min_confidence = float(data.get('min_confidence', 0.7)) - - start = datetime.strptime(start_date, '%Y-%m-%d') - end = datetime.strptime(end_date, '%Y-%m-%d') - - def run_backtest_thread(): - global backtest_running, backtest_results - backtest_running = True - - def log_message(msg, msg_type='info'): - if socketio: - socketio.emit('backtest_log', { - 'message': msg, - 'type': msg_type, - 'timestamp': datetime.now().strftime('%H:%M:%S') - }) - time.sleep(0.01) - - try: - log_message(f"Starting backtest from {start.date()} to {end.date()}", 'info') - log_message(f"Initial Balance: ${initial_balance:.2f}", 'info') - - from polymarket.backtesting.engine import BacktestEngine - from polymarket.strategies.examples import SimpleProbabilityStrategy - - strategy = SimpleProbabilityStrategy( - initial_balance=initial_balance, - threshold=threshold, - min_confidence=min_confidence - ) - - log_message("Strategy initialized: SimpleProbabilityStrategy", 'success') - - engine = BacktestEngine(strategy, start, end, initial_balance) - - log_message("Fetching markets...", 'info') - markets = engine.fetch_historical_markets() - - if not markets: - log_message("ERROR: No markets found", 'error') - raise ValueError("No markets found for backtesting") - - log_message(f"Found {len(markets)} markets to backtest", 'success') - - # Run backtest (simplified - full implementation in simple_app.py) - # This is a placeholder - full implementation should be moved here - log_message("Backtest simulation running...", 'info') - - # Emit completion - if socketio: - socketio.emit('backtest_complete', { - 'total_return': 0.0, - 'total_trades': 0, - 'win_rate': 0.0, - 'sharpe_ratio': 0.0, - 'max_drawdown': 0.0, - 'final_equity': initial_balance, - 'equity_curve': [], - 'net_profit': 0.0 - }) - - except Exception as e: - import traceback - error_msg = f"{str(e)}\n{traceback.format_exc()}" - print(f"Backtest error: {error_msg}") - if socketio: - socketio.emit('backtest_error', {'error': str(e)}) - finally: - backtest_running = False - - thread = threading.Thread(target=run_backtest_thread, daemon=True) - thread.start() - - return jsonify({'status': 'started', 'message': 'Backtest running...'}) - - except Exception as e: - return jsonify({'error': str(e)}), 500 - - -@backtest_bp.route('/backtest/status') -def get_backtest_status(): - """Get backtest status""" - return jsonify({ - 'running': backtest_running, - 'results': backtest_results - }) diff --git a/polymarket/gui/api/markets.py b/polymarket/gui/api/markets.py deleted file mode 100644 index c5c60d2..0000000 --- a/polymarket/gui/api/markets.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Markets API Routes -""" - -from flask import Blueprint, jsonify -from polymarket.api import GammaClient, ClobClient -import os - -markets_bp = Blueprint('markets', __name__) - -# Initialize API clients -try: - gamma_client = GammaClient() - clob_client = ClobClient() - USE_REAL_API = True -except Exception as e: - print(f"[WARNING] Could not initialize API clients: {e}") - USE_REAL_API = False - gamma_client = None - clob_client = None - - -@markets_bp.route('/markets') -def get_markets(): - """Get markets from real Polymarket API""" - if not USE_REAL_API: - return jsonify({ - 'markets': [ - { - 'id': '1', - 'question': 'Will Bitcoin reach $100k by 2025?', - 'event': 'Crypto Markets', - 'yes_price': 0.65, - 'no_price': 0.35, - 'bid': 0.64, - 'ask': 0.66, - 'spread': 0.02, - 'token_id': 'token123' - } - ] - }) - - try: - events_data = gamma_client.get_events(active=True, closed=False, limit=50) - - if isinstance(events_data, dict): - events = events_data.get('data', events_data.get('events', [])) - else: - events = events_data if isinstance(events_data, list) else [] - - print(f"[DEBUG] Fetched {len(events)} events from API") - - markets = [] - for event in events: - event_markets = event.get('markets', []) - if not event_markets: - continue - - for market in event_markets: - try: - clob_token_ids = market.get('clobTokenIds', []) - if len(clob_token_ids) < 2: - continue - - yes_token = clob_token_ids[0] - no_token = clob_token_ids[1] - - import json - outcomes = json.loads(market.get('outcomes', '["Yes", "No"]')) - outcome_prices = json.loads(market.get('outcomePrices', '[0.5, 0.5]')) - - yes_price = float(outcome_prices[0]) if len(outcome_prices) > 0 else 0.5 - no_price = float(outcome_prices[1]) if len(outcome_prices) > 1 else 0.5 - - # Try to get better prices from orderbook - try: - yes_book = clob_client.get_orderbook(yes_token) - yes_bids = yes_book.get('bids', []) - yes_asks = yes_book.get('asks', []) - - if yes_bids and yes_asks: - yes_bid = float(yes_bids[0].get('price', yes_price)) - yes_ask = float(yes_asks[0].get('price', yes_price)) - yes_price = (yes_bid + yes_ask) / 2 - except Exception as e: - pass - - spread = abs(yes_price - no_price) - try: - yes_book = clob_client.get_orderbook(yes_token) - yes_bids = yes_book.get('bids', []) - yes_asks = yes_book.get('asks', []) - if yes_bids and yes_asks: - best_bid = float(yes_bids[0].get('price', yes_price)) - best_ask = float(yes_asks[0].get('price', yes_price)) - spread = best_ask - best_bid - except: - pass - - markets.append({ - 'id': market.get('id', ''), - 'question': market.get('question', event.get('title', 'Unknown Market')), - 'event': event.get('title', 'Unknown Event'), - 'yes_price': yes_price, - 'no_price': no_price, - 'bid': yes_price - (spread / 2) if yes_price > (spread / 2) else 0.0, - 'ask': yes_price + (spread / 2) if yes_price < (1 - spread / 2) else 1.0, - 'spread': spread, - 'token_id': yes_token, - 'volume': market.get('volume', 0) - }) - except Exception as e: - print(f"Error processing market: {e}") - continue - - print(f"[DEBUG] Returning {len(markets)} markets to frontend") - return jsonify({'markets': markets}) - except Exception as e: - import traceback - print(f"Error fetching markets: {e}") - print(traceback.format_exc()) - return jsonify({'markets': [], 'error': str(e)}) - - -@markets_bp.route('/market/') -def get_market_details(market_id): - """Get market details from real API""" - if not USE_REAL_API: - return jsonify({ - 'orderbook': {'bids': [], 'asks': []}, - 'best_bid_ask': {'bid': 0.5, 'ask': 0.5, 'spread': 0.0}, - 'depth': {'bid_depth': 0, 'ask_depth': 0} - }) - - try: - book = clob_client.get_orderbook(market_id) - - bids = book.get('bids', []) - asks = book.get('asks', []) - - best_bid = float(bids[0].get('price', 0.5)) if bids else 0.5 - best_ask = float(asks[0].get('price', 0.5)) if asks else 0.5 - - bid_depth = sum(float(bid.get('size', 0)) for bid in bids) - ask_depth = sum(float(ask.get('size', 0)) for ask in asks) - - return jsonify({ - 'orderbook': { - 'bids': bids[:10], - 'asks': asks[:10] - }, - 'best_bid_ask': { - 'bid': best_bid, - 'ask': best_ask, - 'spread': best_ask - best_bid, - 'mid': (best_bid + best_ask) / 2 - }, - 'depth': { - 'bid_depth': bid_depth, - 'ask_depth': ask_depth, - 'total_depth': bid_depth + ask_depth - } - }) - except Exception as e: - print(f"Error fetching market details: {e}") - return jsonify({ - 'orderbook': {'bids': [], 'asks': []}, - 'best_bid_ask': {'bid': 0.5, 'ask': 0.5, 'spread': 0.0}, - 'depth': {'bid_depth': 0, 'ask_depth': 0}, - 'error': str(e) - }) diff --git a/polymarket/gui/api/strategy.py b/polymarket/gui/api/strategy.py deleted file mode 100644 index 74d7e91..0000000 --- a/polymarket/gui/api/strategy.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Strategy API Routes -""" - -from flask import Blueprint, jsonify, request - -strategy_bp = Blueprint('strategy', __name__) - -# Global state -is_trading = False -strategy_balance = 1000.0 -strategy_equity = 1000.0 -strategy_positions = 0 -strategy_trades = 0 - - -@strategy_bp.route('/strategy/status') -def get_strategy_status(): - """Get strategy status""" - return jsonify({ - 'active': is_trading, - 'balance': strategy_balance, - 'equity': strategy_equity, - 'positions': strategy_positions, - 'trades': strategy_trades, - 'win_rate': 0, - 'profit': 0, - 'drawdown': 0 - }) - - -@strategy_bp.route('/strategy/positions') -def get_positions(): - """Get positions""" - return jsonify({'positions': []}) - - -@strategy_bp.route('/strategy/start', methods=['POST']) -def start_strategy(): - """Start trading strategy""" - global is_trading - is_trading = True - return jsonify({'status': 'started'}) - - -@strategy_bp.route('/strategy/stop', methods=['POST']) -def stop_strategy(): - """Stop trading strategy""" - global is_trading - is_trading = False - return jsonify({'status': 'stopped'}) diff --git a/polymarket/gui/app.py b/polymarket/gui/app.py deleted file mode 100644 index d6f5a9f..0000000 --- a/polymarket/gui/app.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Main Flask Application -Componentized version with blueprints -""" - -from flask import Flask, render_template -from flask_socketio import SocketIO -from pathlib import Path -import sys -import os -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - -# Setup paths -project_root = Path(__file__).parent.parent.parent -sys.path.insert(0, str(project_root)) -gui_path = Path(__file__).parent -sys.path.insert(0, str(gui_path)) - -# Import API blueprints -try: - from api import create_api_blueprint - from api.backtest import set_socketio -except ImportError: - # Fallback for direct execution - import importlib.util - api_init_path = gui_path / 'api' / '__init__.py' - spec = importlib.util.spec_from_file_location("api", api_init_path) - api_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(api_module) - create_api_blueprint = api_module.create_api_blueprint - - backtest_path = gui_path / 'api' / 'backtest.py' - spec = importlib.util.spec_from_file_location("backtest", backtest_path) - backtest_module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(backtest_module) - set_socketio = backtest_module.set_socketio - -app = Flask(__name__, - template_folder='templates', - static_folder='static') -app.config['SECRET_KEY'] = 'cyberpunk-polymarket-secret' - -socketio = SocketIO(app, cors_allowed_origins="*") - -# Set socketio for backtest routes -set_socketio(socketio) - -# Register API blueprints -api_bp = create_api_blueprint() -app.register_blueprint(api_bp) - - -@app.route('/') -def index(): - """Main dashboard""" - return render_template('dashboard.html') - - -@socketio.on('connect') -def handle_connect(): - """Handle WebSocket connection""" - socketio.emit('status', {'message': 'Connected to Cyberpunk Dashboard'}) - - -@socketio.on('disconnect') -def handle_disconnect(): - """Handle WebSocket disconnection""" - pass - - -if __name__ == '__main__': - print("=" * 60) - print("CYBERPUNK POLYMARKET DASHBOARD") - print("=" * 60) - print("Starting server on http://localhost:5000") - print("Press Ctrl+C to stop") - print("=" * 60) - socketio.run(app, host='0.0.0.0', port=5000, debug=True) diff --git a/polymarket/gui/requirements.txt b/polymarket/gui/requirements.txt deleted file mode 100644 index 777e8f2..0000000 --- a/polymarket/gui/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -Flask>=2.3.0 -flask-socketio>=5.3.0 -python-socketio>=5.8.0 diff --git a/polymarket/gui/run.bat b/polymarket/gui/run.bat deleted file mode 100644 index daa1f88..0000000 --- a/polymarket/gui/run.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off -echo ========================================== -echo 🚀 STARTING CYBERPUNK POLYMARKET DASHBOARD -echo ========================================== -echo. -echo 📦 Installing dependencies... -pip install -r requirements.txt -echo. -echo 🌐 Starting server... -echo 💀 Open http://localhost:5000 in your browser -echo. -python app.py -pause diff --git a/polymarket/gui/run.sh b/polymarket/gui/run.sh deleted file mode 100644 index 2bfca2d..0000000 --- a/polymarket/gui/run.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# Run the Cyberpunk Dashboard - -echo "==========================================" -echo "🚀 STARTING CYBERPUNK POLYMARKET DASHBOARD" -echo "==========================================" -echo "" -echo "📦 Installing dependencies..." -pip install -r requirements.txt -echo "" -echo "🌐 Starting server..." -echo "💀 Open http://localhost:5000 in your browser" -echo "" -python app.py diff --git a/polymarket/gui/simple_app.py b/polymarket/gui/simple_app.py deleted file mode 100644 index 1122d5c..0000000 --- a/polymarket/gui/simple_app.py +++ /dev/null @@ -1,643 +0,0 @@ -"""Simplified dashboard that definitely works""" -from flask import Flask, render_template, jsonify, request -from flask_socketio import SocketIO, emit -import sys -from pathlib import Path -from datetime import datetime, timedelta -import threading -import time -import numpy as np -import os -from dotenv import load_dotenv - -# Load environment variables -load_dotenv() - -# Setup paths -project_root = Path(__file__).parent.parent.parent -sys.path.insert(0, str(project_root)) - -# Import Polymarket API clients -try: - from polymarket.api import GammaClient, ClobClient, DataClient - gamma_client = GammaClient() - clob_client = ClobClient() - data_client = DataClient(api_key=os.getenv('POLYMARKET_API_KEY')) - USE_REAL_API = True - print("[OK] Connected to real Polymarket API") -except Exception as e: - print(f"[WARNING] Could not initialize API clients: {e}") - print("Falling back to mock data") - USE_REAL_API = False - gamma_client = None - clob_client = None - data_client = None - -app = Flask(__name__, - template_folder='templates', - static_folder='static') -socketio = SocketIO(app, cors_allowed_origins="*") - -# Mock state -is_trading = False -strategy_balance = 1000.0 -strategy_equity = 1000.0 -strategy_positions = 0 -strategy_trades = 0 - -# Backtesting state -backtest_running = False -backtest_results = None - -@app.route('/') -def index(): - """Main dashboard""" - return render_template('dashboard.html') - -@app.route('/api/markets') -def get_markets(): - """Get markets from real Polymarket API""" - if not USE_REAL_API: - # Fallback to mock data - return jsonify({ - 'markets': [ - { - 'id': '1', - 'question': 'Will Bitcoin reach $100k by 2025?', - 'event': 'Crypto Markets', - 'yes_price': 0.65, - 'no_price': 0.35, - 'bid': 0.64, - 'ask': 0.66, - 'spread': 0.02, - 'token_id': 'token123' - } - ] - }) - - try: - # Fetch events from Gamma API - events_data = gamma_client.get_events(active=True, closed=False, limit=50) - - # Handle different response formats - if isinstance(events_data, dict): - events = events_data.get('data', events_data.get('events', [])) - else: - events = events_data if isinstance(events_data, list) else [] - - print(f"[DEBUG] Fetched {len(events)} events from API") - - markets = [] - for event in events: - event_markets = event.get('markets', []) - if not event_markets: - # Some events might have markets directly in the event object - if 'question' in event or 'clobTokenIds' in event: - event_markets = [event] - else: - continue - - for market in event_markets: - try: - # Get clobTokenIds from market (per official docs) - # https://docs.polymarket.com/quickstart/fetching-data - clob_token_ids = market.get('clobTokenIds', []) - if len(clob_token_ids) < 2: - continue - - yes_token = clob_token_ids[0] - no_token = clob_token_ids[1] - - # Parse outcomes and prices from market (per docs format) - import json - outcomes = json.loads(market.get('outcomes', '["Yes", "No"]')) - outcome_prices = json.loads(market.get('outcomePrices', '[0.5, 0.5]')) - - # Use prices directly from market data first (faster) - yes_price = float(outcome_prices[0]) if len(outcome_prices) > 0 else 0.5 - no_price = float(outcome_prices[1]) if len(outcome_prices) > 1 else 0.5 - - # Try to get better prices from orderbook (optional enhancement) - try: - yes_book = clob_client.get_orderbook(yes_token) - yes_bids = yes_book.get('bids', []) - yes_asks = yes_book.get('asks', []) - - if yes_bids and yes_asks: - yes_bid = float(yes_bids[0].get('price', yes_price)) - yes_ask = float(yes_asks[0].get('price', yes_price)) - yes_price = (yes_bid + yes_ask) / 2 - except Exception as e: - print(f"Warning: Could not get orderbook for YES token: {e}") - # Use price from market data as fallback - - # Calculate spread from orderbook if available - spread = abs(yes_price - no_price) - try: - yes_book = clob_client.get_orderbook(yes_token) - yes_bids = yes_book.get('bids', []) - yes_asks = yes_book.get('asks', []) - if yes_bids and yes_asks: - best_bid = float(yes_bids[0].get('price', yes_price)) - best_ask = float(yes_asks[0].get('price', yes_price)) - spread = best_ask - best_bid - except: - pass - - markets.append({ - 'id': market.get('id', ''), - 'question': market.get('question', 'Unknown Market'), - 'event': event.get('title', 'Unknown Event'), - 'yes_price': yes_price, - 'no_price': no_price, - 'bid': yes_price - 0.01 if yes_price > 0.01 else 0.0, - 'ask': yes_price + 0.01 if yes_price < 0.99 else 1.0, - 'spread': abs(yes_price - no_price), - 'token_id': yes_token, - 'volume': market.get('volume', 0) - }) - except Exception as e: - print(f"Error processing market: {e}") - continue - - print(f"[DEBUG] Returning {len(markets)} markets to frontend") - return jsonify({'markets': markets}) - except Exception as e: - import traceback - print(f"Error fetching markets: {e}") - print(traceback.format_exc()) - return jsonify({'markets': [], 'error': str(e)}) - -@app.route('/api/strategy/status') -def get_strategy_status(): - """Get strategy status from real API""" - if not USE_REAL_API: - return jsonify({ - 'active': is_trading, - 'balance': strategy_balance, - 'equity': strategy_equity, - 'positions': strategy_positions, - 'trades': strategy_trades, - 'win_rate': 0, - 'profit': 0, - 'drawdown': 0 - }) - - try: - # Get portfolio data (requires user address) - # For now, return basic status - return jsonify({ - 'active': is_trading, - 'balance': strategy_balance, - 'equity': strategy_equity, - 'positions': strategy_positions, - 'trades': strategy_trades, - 'win_rate': 0, - 'profit': 0, - 'drawdown': 0 - }) - except Exception as e: - print(f"Error getting strategy status: {e}") - return jsonify({ - 'active': is_trading, - 'balance': 0.0, - 'equity': 0.0, - 'positions': 0, - 'trades': 0, - 'win_rate': 0, - 'profit': 0, - 'drawdown': 0 - }) - -@app.route('/api/strategy/positions') -def get_positions(): - """Get positions from real API""" - if not USE_REAL_API: - return jsonify({'positions': []}) - - try: - # Get positions (requires user address - would need to be configured) - # For now, return empty - return jsonify({'positions': []}) - except Exception as e: - print(f"Error fetching positions: {e}") - return jsonify({'positions': []}) - -@app.route('/api/strategy/start', methods=['POST']) -def start_strategy(): - """Start trading strategy""" - global is_trading - is_trading = True - return jsonify({'status': 'started'}) - -@app.route('/api/strategy/stop', methods=['POST']) -def stop_strategy(): - """Stop trading strategy""" - global is_trading - is_trading = False - return jsonify({'status': 'stopped'}) - -@app.route('/api/market/') -def get_market_details(market_id): - """Get market details from real API""" - if not USE_REAL_API: - return jsonify({ - 'orderbook': {'bids': [], 'asks': []}, - 'best_bid_ask': {'bid': 0.5, 'ask': 0.5, 'spread': 0.0}, - 'depth': {'bid_depth': 0, 'ask_depth': 0} - }) - - try: - # Get market by ID or slug - # Try to get orderbook for the token - book = clob_client.get_orderbook(market_id) - - bids = book.get('bids', []) - asks = book.get('asks', []) - - best_bid = float(bids[0].get('price', 0.5)) if bids else 0.5 - best_ask = float(asks[0].get('price', 0.5)) if asks else 0.5 - - bid_depth = sum(float(bid.get('size', 0)) for bid in bids) - ask_depth = sum(float(ask.get('size', 0)) for ask in asks) - - return jsonify({ - 'orderbook': { - 'bids': bids[:10], # Top 10 bids - 'asks': asks[:10] # Top 10 asks - }, - 'best_bid_ask': { - 'bid': best_bid, - 'ask': best_ask, - 'spread': best_ask - best_bid, - 'mid': (best_bid + best_ask) / 2 - }, - 'depth': { - 'bid_depth': bid_depth, - 'ask_depth': ask_depth, - 'total_depth': bid_depth + ask_depth - } - }) - except Exception as e: - print(f"Error fetching market details: {e}") - return jsonify({ - 'orderbook': {'bids': [], 'asks': []}, - 'best_bid_ask': {'bid': 0.5, 'ask': 0.5, 'spread': 0.0}, - 'depth': {'bid_depth': 0, 'ask_depth': 0}, - 'error': str(e) - }) - -@app.route('/api/backtest/run', methods=['POST']) -def run_backtest(): - """Run backtest""" - global backtest_running, backtest_results - - if backtest_running: - return jsonify({'error': 'Backtest already running'}), 400 - - try: - data = request.json - start_date = data.get('start_date') - end_date = data.get('end_date') - initial_balance = float(data.get('initial_balance', 1000.0)) - threshold = float(data.get('threshold', 0.15)) - min_confidence = float(data.get('min_confidence', 0.7)) - - # Parse dates - start = datetime.strptime(start_date, '%Y-%m-%d') - end = datetime.strptime(end_date, '%Y-%m-%d') - - # Run backtest in background thread - def run_backtest_thread(): - global backtest_running, backtest_results - backtest_running = True - - def log_message(msg, msg_type='info'): - """Emit log message via WebSocket""" - socketio.emit('backtest_log', { - 'message': msg, - 'type': msg_type, - 'timestamp': datetime.now().strftime('%H:%M:%S') - }) - time.sleep(0.01) # Small delay to prevent flooding - - try: - log_message(f"Starting backtest from {start.date()} to {end.date()}", 'info') - log_message(f"Initial Balance: ${initial_balance:.2f}", 'info') - log_message(f"Strategy Parameters: threshold={threshold}, confidence={min_confidence}", 'info') - - # Import backtesting engine - from polymarket.backtesting.engine import BacktestEngine - from polymarket.strategies.examples import SimpleProbabilityStrategy - import traceback - - # Create strategy - strategy = SimpleProbabilityStrategy( - initial_balance=initial_balance, - threshold=threshold, - min_confidence=min_confidence - ) - - log_message("Strategy initialized: SimpleProbabilityStrategy", 'success') - - # Create engine - engine = BacktestEngine(strategy, start, end, initial_balance) - - # Custom run with logging - log_message("Fetching markets...", 'info') - markets = engine.fetch_historical_markets() - - if not markets: - log_message("ERROR: No markets found", 'error') - raise ValueError("No markets found for backtesting") - - log_message(f"Found {len(markets)} markets to backtest", 'success') - - # Run backtest with progress updates - current_date = start - day_count = 0 - total_days = (end - start).days + 1 - - while current_date <= end: - # Process markets - for market_snapshot in markets: - market = market_snapshot['market'] - import json - outcomes = json.loads(market.get('outcomes', '["Yes", "No"]')) - prices = json.loads(market.get('outcomePrices', '[0.5, 0.5]')) - - market_data = { - 'event': market_snapshot['event'], - 'market': market, - 'timestamp': current_date, - 'prices': { - outcome: float(price) - for outcome, price in zip(outcomes, prices) - } - } - - # Log market data periodically (only once per day, not per market) - if day_count % 5 == 0 and len(markets) > 0 and market_snapshot == markets[0]: - yes_price = market_data['prices'].get('Yes', 0.5) - equity = strategy.calculate_equity() - log_message( - f"[MARKET] {current_date.date()} | Yes: {yes_price:.2%} | " - f"Balance: ${strategy.current_balance:.2f} | Equity: ${equity:.2f} | " - f"Positions: {len(strategy.positions)} | Trades: {strategy.total_trades}", - 'market' - ) - - # Get strategy signal - signal = strategy.analyze_market(market_data) - - if signal: - if signal.confidence >= strategy.min_confidence: - result = engine.execute_signal(signal, market_data, current_date) - if result: - # Calculate PnL for this trade - trade_pnl = 0.0 - if signal.action == 'SELL': - # PnL already calculated in execute_signal - # Get from closed positions - if strategy.closed_positions: - last_closed = strategy.closed_positions[-1] - if hasattr(last_closed, 'realized_pnl'): - trade_pnl = last_closed.realized_pnl if np.isfinite(last_closed.realized_pnl) else 0.0 - - equity = strategy.calculate_equity() - unrealized_pnl = sum( - pos.unrealized_pnl if np.isfinite(pos.unrealized_pnl) else 0.0 - for pos in strategy.positions.values() - ) - - log_message( - f"[TRADE] {signal.action} | Size: ${result['size']:.2f} | " - f"Price: {result['price']:.4f} | Balance: ${strategy.current_balance:.2f} | " - f"Positions: {len(strategy.positions)}", - 'trade' - ) - - # Emit real-time trade update - socketio.emit('backtest_trade', { - 'action': signal.action, - 'price': float(result['price']), - 'size': float(result['size']), - 'timestamp': current_date.isoformat(), - 'balance': float(strategy.current_balance) if np.isfinite(strategy.current_balance) else 0.0, - 'equity': float(equity) if np.isfinite(equity) else 0.0, - 'unrealized_pnl': float(unrealized_pnl) if np.isfinite(unrealized_pnl) else 0.0, - 'trade_pnl': float(trade_pnl), - 'positions': len(strategy.positions), - 'total_trades': strategy.total_trades, - 'winning_trades': strategy.winning_trades, - 'losing_trades': strategy.losing_trades - }) - # Don't log skipped signals to reduce noise - - # Update positions - for token_id, position in strategy.positions.items(): - price_change = np.random.normal(0, 0.02) - new_price = max(0.01, min(0.99, position.current_price + price_change)) - strategy.update_position(token_id, new_price) - - # Update equity curve - strategy.update_drawdown() - equity = strategy.calculate_equity() - unrealized_pnl = sum( - pos.unrealized_pnl if np.isfinite(pos.unrealized_pnl) else 0.0 - for pos in strategy.positions.values() - ) - - equity_point = { - 'date': current_date, - 'equity': equity if np.isfinite(equity) else strategy.current_balance, - 'balance': strategy.current_balance if np.isfinite(strategy.current_balance) else 0.0, - 'unrealized_pnl': unrealized_pnl if np.isfinite(unrealized_pnl) else 0.0 - } - engine.equity_curve.append(equity_point) - - # Emit real-time equity update (every day) - socketio.emit('backtest_equity', { - 'date': current_date.isoformat(), - 'equity': float(equity_point['equity']), - 'balance': float(equity_point['balance']), - 'unrealized_pnl': float(equity_point['unrealized_pnl']), - 'total_trades': strategy.total_trades, - 'positions': len(strategy.positions) - }) - - # Calculate daily return - if len(engine.equity_curve) > 1: - prev_equity = engine.equity_curve[-2]['equity'] - daily_return = (equity - prev_equity) / prev_equity if prev_equity > 0 else 0.0 - engine.daily_returns.append(daily_return) - - # Progress update (less frequent) - if day_count % 10 == 0 or day_count == total_days - 1: - progress = (day_count / total_days * 100) if total_days > 0 else 0 - log_message( - f"[PROGRESS] Day {day_count}/{total_days} ({progress:.1f}%) | " - f"Equity: ${equity:.2f} | Trades: {strategy.total_trades} | " - f"Positions: {len(strategy.positions)} | Win Rate: " - f"{(strategy.winning_trades / strategy.total_trades * 100) if strategy.total_trades > 0 else 0:.1f}%", - 'info' - ) - - current_date += timedelta(days=1) - day_count += 1 - - # Small delay for visibility - time.sleep(0.05) - - # Close positions - log_message("Closing all positions...", 'info') - final_equity = strategy.calculate_equity() - for token_id, position in list(strategy.positions.items()): - if position.size > 0 and position.current_price > 0: - exit_value = position.size * position.current_price - entry_cost = position.size * position.entry_price - pnl = exit_value - entry_cost - strategy.current_balance += exit_value - strategy.total_trades += 1 - - if pnl > 0: - strategy.winning_trades += 1 - strategy.total_profit += pnl - else: - strategy.losing_trades += 1 - strategy.total_loss += abs(pnl) - - log_message( - f"[CLOSE] PnL: ${pnl:.2f} | Entry: {position.entry_price:.4f} | Exit: {position.current_price:.4f}", - 'trade' if pnl > 0 else 'warning' - ) - - del strategy.positions[token_id] - - # Calculate final metrics - if engine.initial_balance > 0: - total_return = (final_equity - engine.initial_balance) / engine.initial_balance * 100 - else: - total_return = 0.0 - - sharpe_ratio = engine._calculate_sharpe_ratio() - - if strategy.total_trades > 0: - win_rate = (strategy.winning_trades / strategy.total_trades * 100) - else: - win_rate = 0.0 - - if abs(strategy.total_loss) > 1e-10: - profit_factor = abs(strategy.total_profit / strategy.total_loss) - else: - profit_factor = 0.0 - - results = { - 'strategy': strategy.name, - 'start_date': engine.start_date, - 'end_date': engine.end_date, - 'initial_balance': engine.initial_balance, - 'final_balance': strategy.current_balance, - 'final_equity': final_equity, - 'total_return': total_return if np.isfinite(total_return) else 0.0, - 'total_trades': strategy.total_trades, - 'winning_trades': strategy.winning_trades, - 'losing_trades': strategy.losing_trades, - 'win_rate': win_rate if np.isfinite(win_rate) else 0.0, - 'total_profit': strategy.total_profit, - 'total_loss': strategy.total_loss, - 'net_profit': strategy.total_profit + strategy.total_loss, - 'profit_factor': profit_factor if np.isfinite(profit_factor) else 0.0, - 'max_drawdown': strategy.max_drawdown * 100 if np.isfinite(strategy.max_drawdown) else 0.0, - 'sharpe_ratio': sharpe_ratio if np.isfinite(sharpe_ratio) else 0.0, - 'trades': engine.trades, - 'equity_curve': engine.equity_curve - } - - log_message("=" * 50, 'info') - log_message("BACKTEST COMPLETE", 'success') - log_message(f"Total Return: {total_return:.2f}%", 'success') - log_message(f"Total Trades: {strategy.total_trades}", 'info') - log_message(f"Win Rate: {win_rate:.2f}%", 'info') - log_message(f"Final Equity: ${final_equity:.2f}", 'success') - - # Prepare results for frontend - equity_curve = results.get('equity_curve', []) - if not equity_curve: - equity_curve = [ - {'date': start, 'equity': initial_balance}, - {'date': end, 'equity': results.get('final_equity', initial_balance)} - ] - - def safe_float(value, default=0.0): - try: - val = float(value) - return val if (val == 0 or (val != float('inf') and val != float('-inf') and not (val != val))) else default - except (ValueError, TypeError): - return default - - backtest_results = { - 'total_return': safe_float(results.get('total_return', 0)), - 'total_trades': int(results.get('total_trades', 0)), - 'winning_trades': int(results.get('winning_trades', 0)), - 'losing_trades': int(results.get('losing_trades', 0)), - 'win_rate': safe_float(results.get('win_rate', 0)), - 'sharpe_ratio': safe_float(results.get('sharpe_ratio', 0)), - 'max_drawdown': safe_float(results.get('max_drawdown', 0)), - 'final_equity': safe_float(results.get('final_equity', initial_balance), initial_balance), - 'equity_curve': [ - { - 'date': str(point.get('date', '')), - 'equity': safe_float(point.get('equity', initial_balance), initial_balance) - } - for point in equity_curve - ], - 'net_profit': safe_float(results.get('net_profit', 0)) - } - - # Emit results via WebSocket - socketio.emit('backtest_complete', backtest_results) - - except Exception as e: - import traceback - error_msg = f"{str(e)}\n{traceback.format_exc()}" - print(f"Backtest error: {error_msg}") - socketio.emit('backtest_error', {'error': str(e)}) - finally: - backtest_running = False - - thread = threading.Thread(target=run_backtest_thread, daemon=True) - thread.start() - - return jsonify({'status': 'started', 'message': 'Backtest running...'}) - - except Exception as e: - return jsonify({'error': str(e)}), 500 - -@app.route('/api/backtest/status') -def get_backtest_status(): - """Get backtest status""" - return jsonify({ - 'running': backtest_running, - 'results': backtest_results - }) - -# WebSocket handlers -@socketio.on('connect') -def handle_connect(): - """Handle WebSocket connection""" - emit('status', {'message': 'Connected to Cyberpunk Dashboard'}) - -@socketio.on('disconnect') -def handle_disconnect(): - """Handle WebSocket disconnection""" - pass - -if __name__ == '__main__': - print("=" * 60) - print("CYBERPUNK POLYMARKET DASHBOARD") - print("=" * 60) - print("Starting server on http://localhost:5000") - print("Press Ctrl+C to stop") - print("=" * 60) - socketio.run(app, host='0.0.0.0', port=5000, debug=True) diff --git a/polymarket/gui/start_dashboard.py b/polymarket/gui/start_dashboard.py deleted file mode 100644 index 5eec17a..0000000 --- a/polymarket/gui/start_dashboard.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Simple launcher for the dashboard""" -import sys -import os -from pathlib import Path - -# Get the project root directory -script_dir = Path(__file__).parent.resolve() -project_root = script_dir.parent.parent.resolve() - -# Add to Python path -if str(project_root) not in sys.path: - sys.path.insert(0, str(project_root)) - -print("=" * 60) -print("CYBERPUNK POLYMARKET DASHBOARD") -print("=" * 60) -print(f"Project root: {project_root}") -print(f"GUI directory: {script_dir}") -print("=" * 60) -print("\nStarting server...") -print("Open http://localhost:5000 in your browser\n") - -# Change to gui directory for Flask templates -os.chdir(script_dir) - -# Now import and run the app -try: - from app import app, socketio - socketio.run(app, host='0.0.0.0', port=5000, debug=True) -except Exception as e: - print(f"ERROR: {e}") - import traceback - traceback.print_exc() - input("\nPress Enter to exit...") diff --git a/polymarket/gui/static/js/app.js b/polymarket/gui/static/js/app.js deleted file mode 100644 index 150a591..0000000 --- a/polymarket/gui/static/js/app.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - * Main Application Entry Point - * Initializes all components and manages the application lifecycle - */ - -import { MarketsComponent } from './components/MarketsComponent.js'; -import { StrategyComponent } from './components/StrategyComponent.js'; -import { PositionsComponent } from './components/PositionsComponent.js'; -import { BacktestComponent } from './components/BacktestComponent.js'; -import { Notification } from './utils/Notification.js'; -import { WebSocketManager } from './utils/WebSocketManager.js'; - -class App { - constructor() { - this.components = {}; - this.wsManager = null; - } - - async initialize() { - console.log('[App] Initializing application...'); - - // Initialize WebSocket - if (window.io) { - this.wsManager = new WebSocketManager(io()); - this.setupWebSocketHandlers(); - } - - // Initialize components - this.components.markets = new MarketsComponent('marketsList'); - this.components.strategy = new StrategyComponent(); - this.components.positions = new PositionsComponent('positionsList'); - this.components.backtest = new BacktestComponent(); - - // Initialize controls - this.initializeControls(); - - // Load initial data - try { - await this.components.markets.load(); - this.components.markets.startAutoRefresh(30000); - } catch (error) { - console.error('[App] Error loading markets:', error); - if (this.components.markets.container) { - this.components.markets.showError('Failed to load markets. Check console for details.'); - } - } - - this.components.strategy.initialize(); - this.components.positions.load(); - this.components.backtest.initialize(); - - // Start position updates - setInterval(() => this.components.positions.load(), 5000); - - console.log('[App] Application initialized'); - } - - initializeControls() { - // Threshold and confidence sliders - const threshold = document.getElementById('threshold'); - const confidence = document.getElementById('confidence'); - const thresholdValue = document.getElementById('thresholdValue'); - const confidenceValue = document.getElementById('confidenceValue'); - - if (threshold && thresholdValue) { - threshold.addEventListener('input', (e) => { - thresholdValue.textContent = parseFloat(e.target.value).toFixed(2); - }); - } - - if (confidence && confidenceValue) { - confidence.addEventListener('input', (e) => { - confidenceValue.textContent = parseFloat(e.target.value).toFixed(2); - }); - } - } - - setupWebSocketHandlers() { - if (!this.wsManager) return; - - // Backtest handlers - this.wsManager.on('backtest_log', (data) => { - this.components.backtest.addTerminalLine(data.message, data.type || 'info'); - }); - - this.wsManager.on('backtest_trade', (data) => { - this.components.backtest.addTrade(data); - this.components.backtest.updateStats(data); - }); - - this.wsManager.on('backtest_equity', (data) => { - this.components.backtest.updateChart(data); - this.components.backtest.updateStats(data); - }); - - this.wsManager.on('backtest_complete', (data) => { - this.components.backtest.displayResults(data); - const btn = document.getElementById('runBacktestBtn'); - if (btn) { - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - } - this.components.backtest.isRunning = false; - }); - - this.wsManager.on('backtest_error', (data) => { - this.components.backtest.addTerminalLine('ERROR: ' + data.error, 'error'); - Notification.show('BACKTEST ERROR: ' + data.error, 'error'); - const btn = document.getElementById('runBacktestBtn'); - if (btn) { - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - } - document.getElementById('backtestStatus').style.display = 'none'; - this.components.backtest.isRunning = false; - }); - - // Strategy handlers - this.wsManager.on('strategy_update', (data) => { - document.getElementById('balanceValue').textContent = '$' + data.balance.toFixed(2); - document.getElementById('equityValue').textContent = '$' + data.equity.toFixed(2); - document.getElementById('positionsValue').textContent = data.positions; - document.getElementById('tradesValue').textContent = data.trades; - }); - } -} - -// Initialize app when DOM is ready -document.addEventListener('DOMContentLoaded', () => { - const app = new App(); - app.initialize(); - window.app = app; // Make available globally for debugging -}); diff --git a/polymarket/gui/static/js/components/BacktestComponent.js b/polymarket/gui/static/js/components/BacktestComponent.js deleted file mode 100644 index 706ae14..0000000 --- a/polymarket/gui/static/js/components/BacktestComponent.js +++ /dev/null @@ -1,295 +0,0 @@ -/** - * Backtest Component - * Handles backtesting functionality - */ - -export class BacktestComponent { - constructor() { - this.equityData = []; - this.chartCanvas = null; - this.chartCtx = null; - this.isRunning = false; - } - - initialize() { - // Set default dates - const endDate = new Date(); - const startDate = new Date(); - startDate.setDate(startDate.getDate() - 30); - - const startInput = document.getElementById('backtestStart'); - const endInput = document.getElementById('backtestEnd'); - if (startInput) startInput.value = startDate.toISOString().split('T')[0]; - if (endInput) endInput.value = endDate.toISOString().split('T')[0]; - - // Event listeners - const runBtn = document.getElementById('runBacktestBtn'); - const clearBtn = document.getElementById('clearTerminalBtn'); - - if (runBtn) runBtn.addEventListener('click', () => this.run()); - if (clearBtn) clearBtn.addEventListener('click', () => this.clearTerminal()); - - // Initialize chart - setTimeout(() => this.initChart(), 100); - } - - initChart() { - this.chartCanvas = document.getElementById('realtimeChart'); - if (!this.chartCanvas) return; - - this.chartCtx = this.chartCanvas.getContext('2d'); - const container = this.chartCanvas.parentElement; - this.chartCanvas.width = container.clientWidth - 30; - this.chartCanvas.height = 250; - - this.drawChart(); - } - - async run() { - if (this.isRunning) { - this.showNotification('Backtest already running', 'error'); - return; - } - - const startDate = document.getElementById('backtestStart')?.value; - const endDate = document.getElementById('backtestEnd')?.value; - const balance = parseFloat(document.getElementById('backtestBalance')?.value || 1000); - const threshold = parseFloat(document.getElementById('threshold')?.value || 0.15); - const confidence = parseFloat(document.getElementById('confidence')?.value || 0.7); - - if (!startDate || !endDate) { - this.showNotification('PLEASE SELECT START AND END DATES', 'error'); - return; - } - - const btn = document.getElementById('runBacktestBtn'); - btn.disabled = true; - btn.innerHTML = '⏳ RUNNING...'; - - const statusDiv = document.getElementById('backtestStatus'); - statusDiv.innerHTML = '
RUNNING BACKTEST...
'; - statusDiv.style.display = 'block'; - - this.clearTerminal(); - this.addTerminalLine('Starting backtest...', 'info'); - - this.equityData = []; - const tradesList = document.getElementById('tradesList'); - if (tradesList) { - tradesList.innerHTML = '
No trades yet
'; - } - - setTimeout(() => this.initChart(), 100); - - try { - const response = await fetch('/api/backtest/run', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - start_date: startDate, - end_date: endDate, - initial_balance: balance, - threshold: threshold, - min_confidence: confidence - }) - }); - - const data = await response.json(); - - if (response.ok) { - this.isRunning = true; - this.showNotification('BACKTEST STARTED', 'success'); - } else { - this.showNotification('ERROR: ' + data.error, 'error'); - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - } - } catch (error) { - console.error('[Backtest] Error running backtest:', error); - this.showNotification('ERROR RUNNING BACKTEST', 'error'); - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - } - } - - displayResults(results) { - const resultsDiv = document.getElementById('backtestResults'); - const statusDiv = document.getElementById('backtestStatus'); - - if (resultsDiv && statusDiv) { - document.getElementById('backtestReturn').textContent = - results.total_return.toFixed(2) + '%'; - document.getElementById('backtestTrades').textContent = results.total_trades; - document.getElementById('backtestWinRate').textContent = - results.win_rate.toFixed(1) + '%'; - document.getElementById('backtestSharpe').textContent = - results.sharpe_ratio.toFixed(2); - document.getElementById('backtestDrawdown').textContent = - results.max_drawdown.toFixed(2) + '%'; - document.getElementById('backtestEquity').textContent = - '$' + results.final_equity.toFixed(2); - - statusDiv.style.display = 'none'; - resultsDiv.style.display = 'block'; - } - } - - clearTerminal() { - const terminal = document.getElementById('terminalOutput'); - if (terminal) { - terminal.innerHTML = '
[SYSTEM] Terminal cleared...
'; - } - } - - addTerminalLine(message, type = 'info') { - const terminal = document.getElementById('terminalOutput'); - if (!terminal) return; - - const line = document.createElement('div'); - line.className = `terminal-line ${type}`; - - const timestamp = new Date().toLocaleTimeString(); - line.textContent = `[${timestamp}] ${message}`; - - terminal.appendChild(line); - terminal.scrollTop = terminal.scrollHeight; - - const lines = terminal.querySelectorAll('.terminal-line'); - if (lines.length > 100) { - lines[0].remove(); - } - } - - updateChart(data) { - if (!this.chartCtx) return; - - this.equityData.push({ - date: new Date(data.date), - equity: data.equity, - balance: data.balance, - unrealized_pnl: data.unrealized_pnl - }); - - if (this.equityData.length > 1000) { - this.equityData.shift(); - } - - this.drawChart(); - } - - drawChart() { - if (!this.chartCtx || this.equityData.length === 0) return; - - const canvas = this.chartCanvas; - const width = canvas.width; - const height = canvas.height; - const padding = 40; - const chartWidth = width - padding * 2; - const chartHeight = height - padding * 2; - - this.chartCtx.fillStyle = '#000'; - this.chartCtx.fillRect(0, 0, width, height); - - if (this.equityData.length < 2) return; - - const equities = this.equityData.map(d => d.equity); - const minEquity = Math.min(...equities); - const maxEquity = Math.max(...equities); - const range = maxEquity - minEquity || 1; - - // Draw grid - this.chartCtx.strokeStyle = 'rgba(0, 255, 255, 0.2)'; - this.chartCtx.lineWidth = 1; - for (let i = 0; i <= 5; i++) { - const y = padding + (chartHeight / 5) * i; - this.chartCtx.beginPath(); - this.chartCtx.moveTo(padding, y); - this.chartCtx.lineTo(width - padding, y); - this.chartCtx.stroke(); - } - - // Draw equity curve - this.chartCtx.strokeStyle = '#00ffff'; - this.chartCtx.lineWidth = 2; - this.chartCtx.beginPath(); - - this.equityData.forEach((point, index) => { - const x = padding + (chartWidth / (this.equityData.length - 1)) * index; - const y = padding + chartHeight - ((point.equity - minEquity) / range) * chartHeight; - - if (index === 0) { - this.chartCtx.moveTo(x, y); - } else { - this.chartCtx.lineTo(x, y); - } - }); - - this.chartCtx.stroke(); - - // Draw labels - this.chartCtx.fillStyle = '#00ffff'; - this.chartCtx.font = '10px Orbitron'; - this.chartCtx.fillText(`$${minEquity.toFixed(0)}`, 5, height - padding + 5); - this.chartCtx.fillText(`$${maxEquity.toFixed(0)}`, 5, padding + 5); - } - - addTrade(trade) { - const tradesList = document.getElementById('tradesList'); - if (!tradesList) return; - - const emptyState = tradesList.querySelector('.empty-state'); - if (emptyState) emptyState.remove(); - - const tradeItem = document.createElement('div'); - tradeItem.className = `trade-item ${trade.action.toLowerCase()}`; - - const pnl = trade.trade_pnl || 0; - const pnlClass = pnl >= 0 ? 'positive' : 'negative'; - const pnlSign = pnl >= 0 ? '+' : ''; - - tradeItem.innerHTML = ` -
-
${trade.action}
-
- Price: ${trade.price.toFixed(4)} | Size: $${trade.size.toFixed(2)} | - ${new Date(trade.timestamp).toLocaleTimeString()} -
-
-
- ${pnlSign}$${Math.abs(pnl).toFixed(2)} -
- `; - - tradesList.insertBefore(tradeItem, tradesList.firstChild); - - while (tradesList.children.length > 50) { - tradesList.removeChild(tradesList.lastChild); - } - - const tradesCount = document.getElementById('tradesCount'); - if (tradesCount) { - tradesCount.textContent = `${trade.total_trades} trades`; - } - } - - updateStats(data) { - const equityEl = document.getElementById('realtimeEquity'); - const pnlEl = document.getElementById('realtimePnL'); - - if (equityEl) equityEl.textContent = `$${data.equity.toFixed(2)}`; - - if (pnlEl) { - const pnl = data.unrealized_pnl || 0; - pnlEl.textContent = `${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}`; - pnlEl.className = pnl >= 0 ? 'pnl-positive' : 'pnl-negative'; - } - } - - showNotification(message, type = 'info') { - if (window.showNotification) { - window.showNotification(message, type); - } else { - console.log(`[${type.toUpperCase()}] ${message}`); - } - } -} diff --git a/polymarket/gui/static/js/components/MarketsComponent.js b/polymarket/gui/static/js/components/MarketsComponent.js deleted file mode 100644 index d1cda54..0000000 --- a/polymarket/gui/static/js/components/MarketsComponent.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Markets Component - * Handles market data fetching and display - */ - -export class MarketsComponent { - constructor(containerId) { - this.container = document.getElementById(containerId); - this.markets = []; - this.updateInterval = null; - } - - async load() { - try { - console.log('[Markets] Loading markets from API...'); - - // Show loading state - if (this.container) { - this.container.innerHTML = '
LOADING MARKETS...
'; - } - - const response = await fetch('/api/markets'); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const data = await response.json(); - - console.log('[Markets] API response:', data); - console.log('[Markets] Markets count:', data.markets ? data.markets.length : 0); - - if (data.markets && Array.isArray(data.markets)) { - this.markets = data.markets; - console.log('[Markets] Rendering', this.markets.length, 'markets'); - this.render(); - } else { - console.error('[Markets] Invalid markets data:', data); - this.showError('Invalid data format: ' + JSON.stringify(data).substring(0, 100)); - } - } catch (error) { - console.error('[Markets] Error loading markets:', error); - this.showError('Failed to load markets: ' + error.message); - } - } - - render() { - if (!this.container) return; - - if (this.markets.length === 0) { - this.container.innerHTML = '
NO ACTIVE MARKETS
'; - return; - } - - this.container.innerHTML = this.markets.map(market => { - const question = market.question || market.event || 'Unknown Market'; - const yesPrice = (market.yes_price || 0) * 100; - const noPrice = (market.no_price || 0) * 100; - const spread = market.spread || Math.abs(yesPrice - noPrice) / 100; - - return ` -
-
${question}
-
-
- YES: ${yesPrice.toFixed(1)}% -
-
- NO: ${noPrice.toFixed(1)}% -
-
-
- Spread: ${(spread * 100).toFixed(2)}% -
-
- `; - }).join(''); - } - - showError(message) { - if (this.container) { - this.container.innerHTML = `
${message}
`; - } - } - - startAutoRefresh(interval = 30000) { - this.updateInterval = setInterval(() => this.load(), interval); - } - - stopAutoRefresh() { - if (this.updateInterval) { - clearInterval(this.updateInterval); - this.updateInterval = null; - } - } -} diff --git a/polymarket/gui/static/js/components/PositionsComponent.js b/polymarket/gui/static/js/components/PositionsComponent.js deleted file mode 100644 index 1d52031..0000000 --- a/polymarket/gui/static/js/components/PositionsComponent.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Positions Component - * Handles position display and updates - */ - -export class PositionsComponent { - constructor(containerId) { - this.container = document.getElementById(containerId); - this.positions = []; - } - - async load() { - try { - const response = await fetch('/api/strategy/positions'); - const data = await response.json(); - this.positions = data.positions || []; - this.render(); - } catch (error) { - console.error('[Positions] Error loading positions:', error); - this.showError('Failed to load positions'); - } - } - - render() { - if (!this.container) return; - - if (this.positions.length === 0) { - this.container.innerHTML = '
NO OPEN POSITIONS
'; - return; - } - - this.container.innerHTML = this.positions.map(pos => { - const isProfit = pos.pnl >= 0; - return ` -
-
-
${pos.outcome}
-
- ${isProfit ? '+' : ''}$${pos.pnl.toFixed(2)} -
-
-
-
Size: ${pos.size.toFixed(2)}
-
Entry: ${(pos.entry_price * 100).toFixed(2)}%
-
Current: ${(pos.current_price * 100).toFixed(2)}%
-
P&L: ${pos.pnl_percent.toFixed(2)}%
-
-
- `; - }).join(''); - } - - showError(message) { - if (this.container) { - this.container.innerHTML = `
${message}
`; - } - } -} diff --git a/polymarket/gui/static/js/components/StrategyComponent.js b/polymarket/gui/static/js/components/StrategyComponent.js deleted file mode 100644 index e2afc61..0000000 --- a/polymarket/gui/static/js/components/StrategyComponent.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Strategy Component - * Handles strategy controls and status - */ - -export class StrategyComponent { - constructor() { - this.isActive = false; - this.statusInterval = null; - } - - initialize() { - const startBtn = document.getElementById('startBtn'); - const stopBtn = document.getElementById('stopBtn'); - - if (startBtn) startBtn.addEventListener('click', () => this.start()); - if (stopBtn) stopBtn.addEventListener('click', () => this.stop()); - - this.updateStatus(); - this.startStatusUpdates(); - } - - async start() { - try { - const threshold = parseFloat(document.getElementById('threshold')?.value || 0.15); - const confidence = parseFloat(document.getElementById('confidence')?.value || 0.7); - const balance = parseFloat(document.getElementById('balance')?.value || 1000); - const category = document.getElementById('category')?.value || '21'; - - const response = await fetch('/api/strategy/start', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - threshold, - min_confidence: confidence, - initial_balance: balance, - tag_id: parseInt(category) - }) - }); - - const data = await response.json(); - - if (response.ok) { - document.getElementById('startBtn').disabled = true; - document.getElementById('stopBtn').disabled = false; - this.isActive = true; - this.updateStatusIndicator(true); - this.showNotification('TRADING STARTED', 'success'); - } else { - this.showNotification('ERROR: ' + data.error, 'error'); - } - } catch (error) { - console.error('[Strategy] Error starting:', error); - this.showNotification('ERROR STARTING TRADING', 'error'); - } - } - - async stop() { - try { - const response = await fetch('/api/strategy/stop', { - method: 'POST' - }); - - const data = await response.json(); - - if (response.ok) { - document.getElementById('startBtn').disabled = false; - document.getElementById('stopBtn').disabled = true; - this.isActive = false; - this.updateStatusIndicator(false); - this.showNotification('TRADING STOPPED', 'info'); - } else { - this.showNotification('ERROR: ' + data.error, 'error'); - } - } catch (error) { - console.error('[Strategy] Error stopping:', error); - this.showNotification('ERROR STOPPING TRADING', 'error'); - } - } - - async updateStatus() { - try { - const response = await fetch('/api/strategy/status'); - const data = await response.json(); - - document.getElementById('balanceValue').textContent = '$' + data.balance.toFixed(2); - document.getElementById('equityValue').textContent = '$' + data.equity.toFixed(2); - document.getElementById('positionsValue').textContent = data.positions; - document.getElementById('tradesValue').textContent = data.trades; - document.getElementById('winRateValue').textContent = data.win_rate.toFixed(1) + '%'; - - const pnlElement = document.getElementById('pnlValue'); - const pnl = data.profit || 0; - pnlElement.textContent = '$' + pnl.toFixed(2); - pnlElement.style.color = pnl >= 0 ? 'var(--neon-green)' : 'var(--neon-pink)'; - } catch (error) { - console.error('[Strategy] Error updating status:', error); - } - } - - updateStatusIndicator(active) { - const statusDot = document.getElementById('statusDot'); - const statusText = document.getElementById('statusText'); - - if (statusDot && statusText) { - if (active) { - statusDot.classList.add('active'); - statusText.textContent = 'ONLINE'; - } else { - statusDot.classList.remove('active'); - statusText.textContent = 'OFFLINE'; - } - } - } - - startStatusUpdates() { - this.statusInterval = setInterval(() => this.updateStatus(), 2000); - } - - stopStatusUpdates() { - if (this.statusInterval) { - clearInterval(this.statusInterval); - this.statusInterval = null; - } - } - - showNotification(message, type = 'info') { - // Use global notification system if available - if (window.showNotification) { - window.showNotification(message, type); - } else { - console.log(`[${type.toUpperCase()}] ${message}`); - } - } -} diff --git a/polymarket/gui/static/js/utils/Notification.js b/polymarket/gui/static/js/utils/Notification.js deleted file mode 100644 index 6b243c1..0000000 --- a/polymarket/gui/static/js/utils/Notification.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Notification Utility - * Global notification system - */ - -export class Notification { - static show(message, type = 'info') { - console.log(`[${type.toUpperCase()}] ${message}`); - - const notification = document.createElement('div'); - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - padding: 15px 25px; - background: rgba(0, 255, 255, 0.1); - border: 2px solid var(--neon-cyan); - color: var(--neon-cyan); - font-family: 'Orbitron', sans-serif; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.1em; - z-index: 10000; - box-shadow: 0 0 20px var(--neon-cyan); - animation: slideIn 0.3s ease; - `; - notification.textContent = message; - - document.body.appendChild(notification); - - setTimeout(() => { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(() => notification.remove(), 300); - }, 3000); - } -} - -// Make it globally available -window.showNotification = Notification.show; diff --git a/polymarket/gui/static/js/utils/WebSocketManager.js b/polymarket/gui/static/js/utils/WebSocketManager.js deleted file mode 100644 index 191fda1..0000000 --- a/polymarket/gui/static/js/utils/WebSocketManager.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * WebSocket Manager - * Handles all WebSocket connections and events - */ - -export class WebSocketManager { - constructor(socket) { - this.socket = socket; - this.handlers = new Map(); - this.setup(); - } - - setup() { - this.socket.on('connect', () => { - console.log('[WebSocket] Connected to server'); - }); - - this.socket.on('disconnect', () => { - console.log('[WebSocket] Disconnected from server'); - }); - - // Backtest events - this.socket.on('backtest_log', (data) => { - this.emit('backtest_log', data); - }); - - this.socket.on('backtest_trade', (data) => { - this.emit('backtest_trade', data); - }); - - this.socket.on('backtest_equity', (data) => { - this.emit('backtest_equity', data); - }); - - this.socket.on('backtest_complete', (data) => { - this.emit('backtest_complete', data); - }); - - this.socket.on('backtest_error', (data) => { - this.emit('backtest_error', data); - }); - - // Strategy events - this.socket.on('strategy_update', (data) => { - this.emit('strategy_update', data); - }); - } - - on(event, handler) { - if (!this.handlers.has(event)) { - this.handlers.set(event, []); - } - this.handlers.get(event).push(handler); - } - - off(event, handler) { - if (this.handlers.has(event)) { - const handlers = this.handlers.get(event); - const index = handlers.indexOf(handler); - if (index > -1) { - handlers.splice(index, 1); - } - } - } - - emit(event, data) { - if (this.handlers.has(event)) { - this.handlers.get(event).forEach(handler => handler(data)); - } - } -} diff --git a/polymarket/gui/static/script.js b/polymarket/gui/static/script.js deleted file mode 100644 index 514d044..0000000 --- a/polymarket/gui/static/script.js +++ /dev/null @@ -1,752 +0,0 @@ -// Cyberpunk Dashboard JavaScript - -const socket = io(); -let updateInterval; - -// Initialize -document.addEventListener('DOMContentLoaded', () => { - initializeControls(); - loadMarkets(); - startStatusUpdates(); - setupWebSocket(); - initializeBacktest(); -}); - -// Control Initialization -function initializeControls() { - const threshold = document.getElementById('threshold'); - const confidence = document.getElementById('confidence'); - const thresholdValue = document.getElementById('thresholdValue'); - const confidenceValue = document.getElementById('confidenceValue'); - const startBtn = document.getElementById('startBtn'); - const stopBtn = document.getElementById('stopBtn'); - - threshold.addEventListener('input', (e) => { - thresholdValue.textContent = parseFloat(e.target.value).toFixed(2); - }); - - confidence.addEventListener('input', (e) => { - confidenceValue.textContent = parseFloat(e.target.value).toFixed(2); - }); - - startBtn.addEventListener('click', startTrading); - stopBtn.addEventListener('click', stopTrading); -} - -// Load Markets -async function loadMarkets() { - try { - console.log('[DEBUG] Loading markets from API...'); - const response = await fetch('/api/markets'); - const data = await response.json(); - - console.log('[DEBUG] API response:', data); - console.log('[DEBUG] Markets array:', data.markets); - console.log('[DEBUG] Markets count:', data.markets ? data.markets.length : 0); - - if (data.markets && Array.isArray(data.markets)) { - console.log('[DEBUG] Displaying', data.markets.length, 'markets'); - displayMarkets(data.markets); - } else { - console.error('[DEBUG] Invalid markets data:', data); - document.getElementById('marketsList').innerHTML = - '
NO ACTIVE MARKETS (Invalid data format)
'; - } - } catch (error) { - console.error('Error loading markets:', error); - document.getElementById('marketsList').innerHTML = - '
ERROR LOADING MARKETS: ' + error.message + '
'; - } -} - -// Display Markets -function displayMarkets(markets) { - const container = document.getElementById('marketsList'); - - if (!container) { - console.error('[DEBUG] marketsList container not found!'); - return; - } - - console.log('[DEBUG] displayMarkets called with', markets.length, 'markets'); - - if (!markets || markets.length === 0) { - container.innerHTML = '
NO ACTIVE MARKETS
'; - return; - } - - container.innerHTML = markets.map(market => { - const question = market.question || market.event || 'Unknown Market'; - const yesPrice = (market.yes_price || 0) * 100; - const noPrice = (market.no_price || 0) * 100; - const spread = market.spread || Math.abs(yesPrice - noPrice) / 100; - - return ` -
-
${question}
-
-
- YES: ${yesPrice.toFixed(1)}% -
-
- NO: ${noPrice.toFixed(1)}% -
-
-
- Spread: ${(spread * 100).toFixed(2)}% -
-
- `; - }).join(''); - - console.log('[DEBUG] Markets displayed successfully'); -} - -// Start Trading -async function startTrading() { - const threshold = parseFloat(document.getElementById('threshold').value); - const confidence = parseFloat(document.getElementById('confidence').value); - const balance = parseFloat(document.getElementById('balance').value); - const category = document.getElementById('category').value; - - try { - const response = await fetch('/api/strategy/start', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - threshold, - min_confidence: confidence, - initial_balance: balance, - tag_id: parseInt(category) - }) - }); - - const data = await response.json(); - - if (response.ok) { - document.getElementById('startBtn').disabled = true; - document.getElementById('stopBtn').disabled = false; - updateStatus(true); - showNotification('TRADING STARTED', 'success'); - } else { - showNotification('ERROR: ' + data.error, 'error'); - } - } catch (error) { - console.error('Error starting trading:', error); - showNotification('ERROR STARTING TRADING', 'error'); - } -} - -// Stop Trading -async function stopTrading() { - try { - const response = await fetch('/api/strategy/stop', { - method: 'POST' - }); - - const data = await response.json(); - - if (response.ok) { - document.getElementById('startBtn').disabled = false; - document.getElementById('stopBtn').disabled = true; - updateStatus(false); - showNotification('TRADING STOPPED', 'info'); - } else { - showNotification('ERROR: ' + data.error, 'error'); - } - } catch (error) { - console.error('Error stopping trading:', error); - showNotification('ERROR STOPPING TRADING', 'error'); - } -} - -// Status Updates -function startStatusUpdates() { - updateInterval = setInterval(async () => { - await updateStrategyStatus(); - await updatePositions(); - }, 2000); -} - -// Update Strategy Status -async function updateStrategyStatus() { - try { - const response = await fetch('/api/strategy/status'); - const data = await response.json(); - - document.getElementById('balanceValue').textContent = - '$' + data.balance.toFixed(2); - document.getElementById('equityValue').textContent = - '$' + data.equity.toFixed(2); - document.getElementById('positionsValue').textContent = - data.positions; - document.getElementById('tradesValue').textContent = - data.trades; - document.getElementById('winRateValue').textContent = - data.win_rate.toFixed(1) + '%'; - - const pnlElement = document.getElementById('pnlValue'); - const pnl = data.profit || 0; - pnlElement.textContent = '$' + pnl.toFixed(2); - pnlElement.style.color = pnl >= 0 ? 'var(--neon-green)' : 'var(--neon-pink)'; - } catch (error) { - console.error('Error updating status:', error); - } -} - -// Update Positions -async function updatePositions() { - try { - const response = await fetch('/api/strategy/positions'); - const data = await response.json(); - - displayPositions(data.positions || []); - } catch (error) { - console.error('Error updating positions:', error); - } -} - -// Display Positions -function displayPositions(positions) { - const container = document.getElementById('positionsList'); - - if (positions.length === 0) { - container.innerHTML = '
NO OPEN POSITIONS
'; - return; - } - - container.innerHTML = positions.map(pos => { - const isProfit = pos.pnl >= 0; - return ` -
-
-
${pos.outcome}
-
- ${isProfit ? '+' : ''}$${pos.pnl.toFixed(2)} -
-
-
-
Size: ${pos.size.toFixed(2)}
-
Entry: ${(pos.entry_price * 100).toFixed(2)}%
-
Current: ${(pos.current_price * 100).toFixed(2)}%
-
P&L: ${pos.pnl_percent.toFixed(2)}%
-
-
- `; - }).join(''); -} - -// Update Status Indicator -function updateStatus(active) { - const statusDot = document.getElementById('statusDot'); - const statusText = document.getElementById('statusText'); - - if (active) { - statusDot.classList.add('active'); - statusText.textContent = 'ONLINE'; - } else { - statusDot.classList.remove('active'); - statusText.textContent = 'OFFLINE'; - } -} - -// WebSocket Setup -function setupWebSocket() { - socket.on('connect', () => { - console.log('Connected to server'); - }); - - socket.on('strategy_update', (data) => { - // Real-time updates via WebSocket - document.getElementById('balanceValue').textContent = - '$' + data.balance.toFixed(2); - document.getElementById('equityValue').textContent = - '$' + data.equity.toFixed(2); - document.getElementById('positionsValue').textContent = - data.positions; - document.getElementById('tradesValue').textContent = - data.trades; - }); - - socket.on('backtest_log', (data) => { - addTerminalLine(data.message, data.type || 'info'); - }); - - socket.on('backtest_trade', (data) => { - addTradeToList(data); - updateRealtimeStats(data); - }); - - socket.on('backtest_equity', (data) => { - updateRealtimeChart(data); - updateRealtimeStats(data); - }); - - socket.on('backtest_complete', (data) => { - displayBacktestResults(data); - }); - - socket.on('backtest_error', (data) => { - addTerminalLine('ERROR: ' + data.error, 'error'); - showNotification('BACKTEST ERROR: ' + data.error, 'error'); - const btn = document.getElementById('runBacktestBtn'); - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - document.getElementById('backtestStatus').style.display = 'none'; - }); -} - -// Notification System -function showNotification(message, type = 'info') { - // Simple notification - can be enhanced with a toast system - console.log(`[${type.toUpperCase()}] ${message}`); - - // Create notification element - const notification = document.createElement('div'); - notification.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - padding: 15px 25px; - background: rgba(0, 255, 255, 0.1); - border: 2px solid var(--neon-cyan); - color: var(--neon-cyan); - font-family: 'Orbitron', sans-serif; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.1em; - z-index: 10000; - box-shadow: 0 0 20px var(--neon-cyan); - animation: slideIn 0.3s ease; - `; - notification.textContent = message; - - document.body.appendChild(notification); - - setTimeout(() => { - notification.style.animation = 'slideOut 0.3s ease'; - setTimeout(() => notification.remove(), 300); - }, 3000); -} - -// Backtesting Functions -function initializeBacktest() { - // Set default dates (last 30 days) - const endDate = new Date(); - const startDate = new Date(); - startDate.setDate(startDate.getDate() - 30); - - document.getElementById('backtestStart').value = startDate.toISOString().split('T')[0]; - document.getElementById('backtestEnd').value = endDate.toISOString().split('T')[0]; - - document.getElementById('runBacktestBtn').addEventListener('click', runBacktest); - document.getElementById('clearTerminalBtn').addEventListener('click', clearTerminal); - - // Initialize real-time chart (wait for DOM to be ready) - setTimeout(() => { - initRealtimeChart(); - }, 100); - - // Clear trades list on new backtest - const tradesList = document.getElementById('tradesList'); - if (tradesList) { - tradesList.innerHTML = '
No trades yet
'; - } - equityData = []; -} - -function clearTerminal() { - document.getElementById('terminalOutput').innerHTML = - '
[SYSTEM] Terminal cleared...
'; -} - -function addTerminalLine(message, type = 'info') { - const terminal = document.getElementById('terminalOutput'); - const line = document.createElement('div'); - line.className = `terminal-line ${type}`; - - const timestamp = new Date().toLocaleTimeString(); - line.textContent = `[${timestamp}] ${message}`; - - terminal.appendChild(line); - terminal.scrollTop = terminal.scrollHeight; - - // Keep only last 100 lines - const lines = terminal.querySelectorAll('.terminal-line'); - if (lines.length > 100) { - lines[0].remove(); - } -} - -// Real-time chart data -let equityData = []; -let chartCanvas = null; -let chartCtx = null; - -function initRealtimeChart() { - chartCanvas = document.getElementById('realtimeChart'); - if (!chartCanvas) return; - - chartCtx = chartCanvas.getContext('2d'); - equityData = []; - - // Set canvas size - const container = chartCanvas.parentElement; - chartCanvas.width = container.clientWidth - 30; - chartCanvas.height = 250; - - // Draw initial chart - drawChart(); -} - -function updateRealtimeChart(data) { - if (!chartCtx) return; - - equityData.push({ - date: new Date(data.date), - equity: data.equity, - balance: data.balance, - unrealized_pnl: data.unrealized_pnl - }); - - // Keep only last 1000 points - if (equityData.length > 1000) { - equityData.shift(); - } - - drawChart(); -} - -function drawChart() { - if (!chartCtx || equityData.length === 0) return; - - const canvas = chartCanvas; - const width = canvas.width; - const height = canvas.height; - const padding = 40; - const chartWidth = width - padding * 2; - const chartHeight = height - padding * 2; - - // Clear canvas - chartCtx.fillStyle = '#000'; - chartCtx.fillRect(0, 0, width, height); - - if (equityData.length < 2) return; - - // Find min/max equity - const equities = equityData.map(d => d.equity); - const minEquity = Math.min(...equities); - const maxEquity = Math.max(...equities); - const range = maxEquity - minEquity || 1; - - // Draw grid - chartCtx.strokeStyle = 'rgba(0, 255, 255, 0.2)'; - chartCtx.lineWidth = 1; - for (let i = 0; i <= 5; i++) { - const y = padding + (chartHeight / 5) * i; - chartCtx.beginPath(); - chartCtx.moveTo(padding, y); - chartCtx.lineTo(width - padding, y); - chartCtx.stroke(); - } - - // Draw equity curve - chartCtx.strokeStyle = '#00ffff'; - chartCtx.lineWidth = 2; - chartCtx.beginPath(); - - equityData.forEach((point, index) => { - const x = padding + (chartWidth / (equityData.length - 1)) * index; - const y = padding + chartHeight - ((point.equity - minEquity) / range) * chartHeight; - - if (index === 0) { - chartCtx.moveTo(x, y); - } else { - chartCtx.lineTo(x, y); - } - }); - - chartCtx.stroke(); - - // Draw balance line - chartCtx.strokeStyle = 'rgba(255, 0, 255, 0.5)'; - chartCtx.lineWidth = 1; - chartCtx.beginPath(); - - equityData.forEach((point, index) => { - const x = padding + (chartWidth / (equityData.length - 1)) * index; - const y = padding + chartHeight - ((point.balance - minEquity) / range) * chartHeight; - - if (index === 0) { - chartCtx.moveTo(x, y); - } else { - chartCtx.lineTo(x, y); - } - }); - - chartCtx.stroke(); - - // Draw labels - chartCtx.fillStyle = '#00ffff'; - chartCtx.font = '10px Orbitron'; - chartCtx.fillText(`$${minEquity.toFixed(0)}`, 5, height - padding + 5); - chartCtx.fillText(`$${maxEquity.toFixed(0)}`, 5, padding + 5); -} - -function addTradeToList(trade) { - const tradesList = document.getElementById('tradesList'); - if (!tradesList) return; - - // Remove empty state - const emptyState = tradesList.querySelector('.empty-state'); - if (emptyState) { - emptyState.remove(); - } - - const tradeItem = document.createElement('div'); - tradeItem.className = `trade-item ${trade.action.toLowerCase()}`; - - const pnl = trade.trade_pnl || 0; - const pnlClass = pnl >= 0 ? 'positive' : 'negative'; - const pnlSign = pnl >= 0 ? '+' : ''; - - tradeItem.innerHTML = ` -
-
${trade.action}
-
- Price: ${trade.price.toFixed(4)} | Size: $${trade.size.toFixed(2)} | - ${new Date(trade.timestamp).toLocaleTimeString()} -
-
-
- ${pnlSign}$${Math.abs(pnl).toFixed(2)} -
- `; - - tradesList.insertBefore(tradeItem, tradesList.firstChild); - - // Keep only last 50 trades - while (tradesList.children.length > 50) { - tradesList.removeChild(tradesList.lastChild); - } - - // Update trades count - const tradesCount = document.getElementById('tradesCount'); - if (tradesCount) { - tradesCount.textContent = `${trade.total_trades} trades`; - } -} - -function updateRealtimeStats(data) { - const equityEl = document.getElementById('realtimeEquity'); - const pnlEl = document.getElementById('realtimePnL'); - - if (equityEl) { - equityEl.textContent = `$${data.equity.toFixed(2)}`; - } - - if (pnlEl) { - const pnl = data.unrealized_pnl || 0; - pnlEl.textContent = `${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}`; - pnlEl.className = pnl >= 0 ? 'pnl-positive' : 'pnl-negative'; - } -} - -async function runBacktest() { - const startDate = document.getElementById('backtestStart').value; - const endDate = document.getElementById('backtestEnd').value; - const balance = parseFloat(document.getElementById('backtestBalance').value); - const threshold = parseFloat(document.getElementById('threshold').value); - const confidence = parseFloat(document.getElementById('confidence').value); - - if (!startDate || !endDate) { - showNotification('PLEASE SELECT START AND END DATES', 'error'); - return; - } - - const btn = document.getElementById('runBacktestBtn'); - btn.disabled = true; - btn.innerHTML = '⏳ RUNNING...'; - - const statusDiv = document.getElementById('backtestStatus'); - statusDiv.innerHTML = '
RUNNING BACKTEST...
'; - statusDiv.style.display = 'block'; - - // Clear terminal and add initial message - clearTerminal(); - addTerminalLine('Starting backtest...', 'info'); - - // Reset chart and trades - equityData = []; - const tradesList = document.getElementById('tradesList'); - if (tradesList) { - tradesList.innerHTML = '
No trades yet
'; - } - - // Reinitialize chart - setTimeout(() => { - initRealtimeChart(); - }, 100); - - try { - const response = await fetch('/api/backtest/run', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - start_date: startDate, - end_date: endDate, - initial_balance: balance, - threshold: threshold, - min_confidence: confidence - }) - }); - - const data = await response.json(); - - if (response.ok) { - showNotification('BACKTEST STARTED', 'success'); - // Results will come via WebSocket - } else { - showNotification('ERROR: ' + data.error, 'error'); - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - } - } catch (error) { - console.error('Error running backtest:', error); - showNotification('ERROR RUNNING BACKTEST', 'error'); - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - } -} - -function displayBacktestResults(results) { - const resultsDiv = document.getElementById('backtestResults'); - const statusDiv = document.getElementById('backtestStatus'); - - // Update metrics - document.getElementById('backtestReturn').textContent = - results.total_return.toFixed(2) + '%'; - document.getElementById('backtestReturn').style.color = - results.total_return >= 0 ? 'var(--neon-green)' : 'var(--neon-pink)'; - - document.getElementById('backtestTrades').textContent = results.total_trades; - document.getElementById('backtestWinRate').textContent = - results.win_rate.toFixed(1) + '%'; - document.getElementById('backtestSharpe').textContent = - results.sharpe_ratio.toFixed(2); - document.getElementById('backtestDrawdown').textContent = - results.max_drawdown.toFixed(2) + '%'; - document.getElementById('backtestEquity').textContent = - '$' + results.final_equity.toFixed(2); - - // Draw equity curve chart - drawEquityChart(results.equity_curve); - - // Show results - statusDiv.style.display = 'none'; - resultsDiv.style.display = 'block'; - - // Re-enable button - const btn = document.getElementById('runBacktestBtn'); - btn.disabled = false; - btn.innerHTML = '▶ RUN BACKTEST'; - - showNotification('BACKTEST COMPLETE', 'success'); -} - -function drawEquityChart(equityCurve) { - const canvas = document.getElementById('backtestChart'); - const ctx = canvas.getContext('2d'); - - if (!equityCurve || equityCurve.length === 0) { - ctx.fillStyle = 'var(--text-secondary)'; - ctx.font = '14px Orbitron'; - ctx.fillText('No data available', 10, 100); - return; - } - - // Clear canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Setup - const padding = 40; - const width = canvas.width - padding * 2; - const height = canvas.height - padding * 2; - - // Find min/max for scaling - const equities = equityCurve.map(p => p.equity); - const minEquity = Math.min(...equities); - const maxEquity = Math.max(...equities); - const range = maxEquity - minEquity || 1; - - // Draw grid - ctx.strokeStyle = 'rgba(0, 255, 255, 0.2)'; - ctx.lineWidth = 1; - for (let i = 0; i <= 5; i++) { - const y = padding + (height / 5) * i; - ctx.beginPath(); - ctx.moveTo(padding, y); - ctx.lineTo(canvas.width - padding, y); - ctx.stroke(); - } - - // Draw equity curve - ctx.strokeStyle = 'var(--neon-cyan)'; - ctx.lineWidth = 2; - ctx.beginPath(); - - equityCurve.forEach((point, index) => { - const x = padding + (width / (equityCurve.length - 1)) * index; - const y = padding + height - ((point.equity - minEquity) / range) * height; - - if (index === 0) { - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - }); - - ctx.stroke(); - - // Draw glow effect - ctx.shadowBlur = 10; - ctx.shadowColor = 'var(--neon-cyan)'; - ctx.stroke(); - - // Draw labels - ctx.fillStyle = 'var(--text-secondary)'; - ctx.font = '10px Orbitron'; - ctx.fillText('$' + minEquity.toFixed(0), 5, canvas.height - padding); - ctx.fillText('$' + maxEquity.toFixed(0), 5, padding + 10); -} - -// Add animations -const style = document.createElement('style'); -style.textContent = ` - @keyframes slideIn { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } - } - - @keyframes slideOut { - from { - transform: translateX(0); - opacity: 1; - } - to { - transform: translateX(100%); - opacity: 0; - } - } -`; -document.head.appendChild(style); diff --git a/polymarket/gui/static/style.css b/polymarket/gui/static/style.css deleted file mode 100644 index 0d6cca6..0000000 --- a/polymarket/gui/static/style.css +++ /dev/null @@ -1,866 +0,0 @@ -/* Cyberpunk Theme Styles */ - -@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;600;700&display=swap'); - -:root { - --neon-cyan: #00ffff; - --neon-pink: #ff00ff; - --neon-green: #00ff00; - --neon-yellow: #ffff00; - --dark-bg: #0a0a0a; - --darker-bg: #050505; - --panel-bg: rgba(10, 10, 20, 0.8); - --border-color: #00ffff; - --text-primary: #00ffff; - --text-secondary: #00ff88; - --glow-intensity: 0 0 10px, 0 0 20px, 0 0 30px; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Rajdhani', sans-serif; - background: var(--dark-bg); - color: var(--text-primary); - overflow-x: hidden; - position: relative; - min-height: 100vh; -} - -.cyberpunk-container { - position: relative; - min-height: 100vh; - padding: 20px; - z-index: 1; -} - -/* Grid Background */ -.grid-background { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-image: - linear-gradient(rgba(0, 255, 255, 0.1) 1px, transparent 1px), - linear-gradient(90deg, rgba(0, 255, 255, 0.1) 1px, transparent 1px); - background-size: 50px 50px; - z-index: 0; - opacity: 0.3; - animation: gridMove 20s linear infinite; -} - -@keyframes gridMove { - 0% { transform: translate(0, 0); } - 100% { transform: translate(50px, 50px); } -} - -/* Particles Effect */ -.particles { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - background-image: - radial-gradient(2px 2px at 20% 30%, var(--neon-cyan), transparent), - radial-gradient(2px 2px at 60% 70%, var(--neon-pink), transparent), - radial-gradient(1px 1px at 50% 50%, var(--neon-green), transparent); - background-size: 200% 200%; - animation: particles 15s ease infinite; - opacity: 0.3; -} - -@keyframes particles { - 0%, 100% { background-position: 0% 0%, 100% 100%, 50% 50%; } - 50% { background-position: 100% 0%, 0% 100%, 50% 50%; } -} - -/* Header */ -.cyberpunk-header { - text-align: center; - padding: 30px 20px; - margin-bottom: 30px; - position: relative; - z-index: 2; -} - -.glitch { - font-family: 'Orbitron', sans-serif; - font-size: 4rem; - font-weight: 900; - color: var(--neon-cyan); - text-transform: uppercase; - letter-spacing: 0.2em; - text-shadow: - 0 0 10px var(--neon-cyan), - 0 0 20px var(--neon-cyan), - 0 0 30px var(--neon-cyan), - 0 0 40px var(--neon-cyan); - animation: glitch 2s infinite; - position: relative; -} - -.glitch::before, -.glitch::after { - content: attr(data-text); - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -.glitch::before { - left: 2px; - text-shadow: -2px 0 var(--neon-pink); - clip: rect(44px, 450px, 56px, 0); - animation: glitch-anim 5s infinite linear alternate-reverse; -} - -.glitch::after { - left: -2px; - text-shadow: 2px 0 var(--neon-green); - clip: rect(44px, 450px, 56px, 0); - animation: glitch-anim 1s infinite linear alternate-reverse; -} - -@keyframes glitch { - 0%, 100% { transform: translate(0); } - 20% { transform: translate(-2px, 2px); } - 40% { transform: translate(-2px, -2px); } - 60% { transform: translate(2px, 2px); } - 80% { transform: translate(2px, -2px); } -} - -@keyframes glitch-anim { - 0% { clip: rect(31px, 9999px, 94px, 0); } - 5% { clip: rect(14px, 9999px, 29px, 0); } - 10% { clip: rect(95px, 9999px, 96px, 0); } - 15% { clip: rect(9px, 9999px, 97px, 0); } - 20% { clip: rect(43px, 9999px, 27px, 0); } - 25% { clip: rect(87px, 9999px, 3px, 0); } - 30% { clip: rect(80px, 9999px, 94px, 0); } - 35% { clip: rect(66px, 9999px, 28px, 0); } - 40% { clip: rect(68px, 9999px, 100px, 0); } - 45% { clip: rect(14px, 9999px, 33px, 0); } - 50% { clip: rect(60px, 9999px, 85px, 0); } - 55% { clip: rect(75px, 9999px, 5px, 0); } - 60% { clip: rect(1px, 9999px, 80px, 0); } - 65% { clip: rect(79px, 9999px, 63px, 0); } - 70% { clip: rect(17px, 9999px, 79px, 0); } - 75% { clip: rect(85px, 9999px, 65px, 0); } - 80% { clip: rect(60px, 9999px, 27px, 0); } - 85% { clip: rect(38px, 9999px, 73px, 0); } - 90% { clip: rect(50px, 9999px, 29px, 0); } - 95% { clip: rect(3px, 9999px, 14px, 0); } - 100% { clip: rect(88px, 9999px, 53px, 0); } -} - -.subtitle { - font-family: 'Orbitron', sans-serif; - font-size: 1.2rem; - color: var(--neon-pink); - letter-spacing: 0.3em; - margin-top: 10px; - text-shadow: 0 0 10px var(--neon-pink); -} - -.status-indicator { - margin-top: 20px; - display: flex; - align-items: center; - justify-content: center; - gap: 10px; - font-family: 'Orbitron', sans-serif; - font-size: 0.9rem; -} - -.status-dot { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--neon-pink); - box-shadow: 0 0 10px var(--neon-pink); - animation: pulse 2s infinite; -} - -.status-dot.active { - background: var(--neon-green); - box-shadow: 0 0 10px var(--neon-green); -} - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -/* Dashboard Grid */ -.dashboard-grid { - display: grid; - grid-template-columns: 1fr 2fr 1fr; - grid-template-rows: auto auto; - gap: 20px; - position: relative; - z-index: 2; -} - -.full-width { - grid-column: 1 / -1; -} - -.backtest-panel.full-width { - grid-column: 1 / -1; -} - -/* Panels */ -.panel { - background: var(--panel-bg); - border: 2px solid var(--border-color); - box-shadow: - 0 0 10px rgba(0, 255, 255, 0.3), - inset 0 0 20px rgba(0, 255, 255, 0.1); - position: relative; - overflow: hidden; -} - -.panel-header { - background: rgba(0, 255, 255, 0.1); - padding: 15px 20px; - border-bottom: 2px solid var(--border-color); - position: relative; -} - -.panel-header h2 { - font-family: 'Orbitron', sans-serif; - font-size: 1.2rem; - color: var(--neon-cyan); - text-transform: uppercase; - letter-spacing: 0.1em; - text-shadow: 0 0 10px var(--neon-cyan); -} - -.scan-line { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 2px; - background: linear-gradient(90deg, - transparent, - var(--neon-cyan), - transparent); - animation: scan 3s linear infinite; -} - -@keyframes scan { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } -} - -.panel-content { - padding: 20px; -} - -/* Controls */ -.control-group { - margin-bottom: 20px; -} - -.control-group label { - display: block; - font-family: 'Orbitron', sans-serif; - font-size: 0.9rem; - color: var(--text-secondary); - margin-bottom: 8px; - text-transform: uppercase; - letter-spacing: 0.1em; -} - -.control-group input[type="range"] { - width: 100%; - height: 6px; - background: rgba(0, 255, 255, 0.2); - border-radius: 3px; - outline: none; - -webkit-appearance: none; -} - -.control-group input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 18px; - height: 18px; - background: var(--neon-cyan); - border-radius: 50%; - cursor: pointer; - box-shadow: 0 0 10px var(--neon-cyan); -} - -.control-group input[type="range"]::-moz-range-thumb { - width: 18px; - height: 18px; - background: var(--neon-cyan); - border-radius: 50%; - cursor: pointer; - border: none; - box-shadow: 0 0 10px var(--neon-cyan); -} - -.control-group input[type="number"], -.control-group select { - width: 100%; - padding: 10px; - background: rgba(0, 255, 255, 0.1); - border: 1px solid var(--border-color); - color: var(--text-primary); - font-family: 'Rajdhani', sans-serif; - font-size: 1rem; - outline: none; -} - -.control-group input[type="number"]:focus, -.control-group select:focus { - box-shadow: 0 0 10px var(--neon-cyan); -} - -.control-group span { - display: inline-block; - margin-left: 10px; - color: var(--neon-cyan); - font-family: 'Orbitron', sans-serif; - font-weight: 700; -} - -/* Buttons */ -.cyber-button { - width: 100%; - padding: 15px; - margin-top: 10px; - background: transparent; - border: 2px solid var(--neon-cyan); - color: var(--neon-cyan); - font-family: 'Orbitron', sans-serif; - font-size: 1rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.1em; - cursor: pointer; - position: relative; - overflow: hidden; - transition: all 0.3s; -} - -.cyber-button::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: var(--neon-cyan); - transition: left 0.3s; - z-index: -1; -} - -.cyber-button:hover::before { - left: 0; -} - -.cyber-button:hover { - color: var(--dark-bg); - box-shadow: 0 0 20px var(--neon-cyan); -} - -.cyber-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.start-btn { - border-color: var(--neon-green); - color: var(--neon-green); -} - -.start-btn::before { - background: var(--neon-green); -} - -.stop-btn { - border-color: var(--neon-pink); - color: var(--neon-pink); -} - -.stop-btn::before { - background: var(--neon-pink); -} - -/* Markets List */ -.markets-list { - max-height: 500px; - overflow-y: auto; -} - -.market-item { - padding: 15px; - margin-bottom: 10px; - background: rgba(0, 255, 255, 0.05); - border: 1px solid rgba(0, 255, 255, 0.3); - border-left: 4px solid var(--neon-cyan); - transition: all 0.3s; -} - -.market-item:hover { - background: rgba(0, 255, 255, 0.1); - border-color: var(--neon-cyan); - box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); - transform: translateX(5px); -} - -.market-question { - font-weight: 700; - color: var(--text-primary); - margin-bottom: 8px; - font-size: 1rem; -} - -.market-prices { - display: flex; - gap: 20px; - font-family: 'Orbitron', sans-serif; - font-size: 0.9rem; -} - -.price-yes { - color: var(--neon-green); -} - -.price-no { - color: var(--neon-pink); -} - -.price-value { - font-weight: 700; - font-size: 1.1rem; -} - -/* Metrics */ -.metric-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 15px; -} - -.metric-card { - background: rgba(0, 255, 255, 0.05); - border: 1px solid rgba(0, 255, 255, 0.3); - padding: 15px; - text-align: center; -} - -.metric-label { - font-family: 'Orbitron', sans-serif; - font-size: 0.8rem; - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.1em; - margin-bottom: 8px; -} - -.metric-value { - font-family: 'Orbitron', sans-serif; - font-size: 1.5rem; - font-weight: 700; - color: var(--neon-cyan); - text-shadow: 0 0 10px var(--neon-cyan); -} - -/* Positions */ -.positions-list { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 15px; -} - -.position-card { - background: rgba(0, 255, 255, 0.05); - border: 1px solid rgba(0, 255, 255, 0.3); - padding: 15px; - border-left: 4px solid var(--neon-cyan); -} - -.position-card.profit { - border-left-color: var(--neon-green); -} - -.position-card.loss { - border-left-color: var(--neon-pink); -} - -.position-header { - display: flex; - justify-content: space-between; - margin-bottom: 10px; -} - -.position-outcome { - font-family: 'Orbitron', sans-serif; - font-weight: 700; - color: var(--text-primary); -} - -.position-pnl { - font-family: 'Orbitron', sans-serif; - font-weight: 700; -} - -.position-pnl.positive { - color: var(--neon-green); -} - -.position-pnl.negative { - color: var(--neon-pink); -} - -.position-details { - font-size: 0.9rem; - color: var(--text-secondary); - line-height: 1.6; -} - -/* Loading & Empty States */ -.loading, -.empty-state { - text-align: center; - padding: 40px; - color: var(--text-secondary); - font-family: 'Orbitron', sans-serif; - text-transform: uppercase; - letter-spacing: 0.2em; -} - -/* Scrollbar */ -::-webkit-scrollbar { - width: 8px; -} - -::-webkit-scrollbar-track { - background: rgba(0, 255, 255, 0.1); -} - -::-webkit-scrollbar-thumb { - background: var(--neon-cyan); - box-shadow: 0 0 10px var(--neon-cyan); -} - -::-webkit-scrollbar-thumb:hover { - background: var(--neon-green); -} - -/* Backtesting */ -.backtest-controls { - margin-bottom: 20px; -} - -.backtest-btn { - margin-top: 20px; - border-color: var(--neon-yellow); - color: var(--neon-yellow); -} - -.backtest-btn::before { - background: var(--neon-yellow); -} - -.backtest-results { - margin-top: 20px; - padding: 15px; - background: rgba(0, 255, 255, 0.05); - border: 1px solid rgba(0, 255, 255, 0.3); -} - -.backtest-metrics { - margin-bottom: 20px; -} - -.metric-row { - display: flex; - justify-content: space-between; - padding: 8px 0; - border-bottom: 1px solid rgba(0, 255, 255, 0.1); - font-family: 'Orbitron', sans-serif; -} - -.metric-row:last-child { - border-bottom: none; -} - -.metric-row .metric-label { - color: var(--text-secondary); - font-size: 0.9rem; -} - -.metric-row .metric-value { - color: var(--neon-cyan); - font-weight: 700; - font-size: 1rem; -} - -#backtestChart { - width: 100%; - max-width: 100%; - background: rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 255, 255, 0.3); -} - -.backtest-status { - margin-top: 15px; - text-align: center; - font-family: 'Orbitron', sans-serif; - color: var(--text-secondary); -} - -/* Update grid for backtesting panel */ -.dashboard-grid { - display: grid; - grid-template-columns: 1fr 2fr 1fr; - grid-template-rows: auto auto; - gap: 20px; -} - -.positions-panel { - grid-column: 1 / 3; -} - -.backtest-panel { - grid-column: 3; -} - -/* Terminal Panel */ -.terminal-panel { - margin-top: 20px; - background: rgba(0, 0, 0, 0.8); - border: 2px solid var(--neon-cyan); - border-radius: 4px; - overflow: hidden; -} - -.terminal-header { - background: rgba(0, 255, 255, 0.1); - padding: 8px 15px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid var(--neon-cyan); - font-family: 'Orbitron', sans-serif; - font-size: 0.8rem; - color: var(--neon-cyan); - text-transform: uppercase; -} - -.terminal-btn { - background: transparent; - border: 1px solid var(--neon-pink); - color: var(--neon-pink); - padding: 4px 10px; - font-family: 'Orbitron', sans-serif; - font-size: 0.7rem; - cursor: pointer; - transition: all 0.2s; -} - -.terminal-btn:hover { - background: var(--neon-pink); - color: var(--dark-bg); -} - -.terminal-output { - height: 200px; - overflow-y: auto; - padding: 10px; - font-family: 'Courier New', monospace; - font-size: 0.85rem; - line-height: 1.4; - background: #000; - color: var(--neon-green); -} - -.terminal-line { - margin-bottom: 4px; - word-wrap: break-word; -} - -.terminal-line.info { - color: var(--neon-cyan); -} - -.terminal-line.success { - color: var(--neon-green); -} - -.terminal-line.warning { - color: var(--neon-yellow); -} - -.terminal-line.error { - color: var(--neon-pink); -} - -.terminal-line.trade { - color: var(--neon-cyan); - font-weight: 700; -} - -.terminal-line.market { - color: var(--text-secondary); -} - -/* Real-time Chart */ -.chart-container { - margin-top: 20px; - background: rgba(0, 0, 0, 0.8); - border: 2px solid var(--neon-cyan); - border-radius: 4px; - padding: 15px; -} - -.chart-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; - font-family: 'Orbitron', sans-serif; - font-size: 0.9rem; - color: var(--neon-cyan); - text-transform: uppercase; -} - -.chart-stats { - display: flex; - gap: 20px; - font-size: 1rem; -} - -.chart-stats span { - font-weight: 700; -} - -.pnl-positive { - color: var(--neon-green); -} - -.pnl-negative { - color: var(--neon-pink); -} - -#realtimeChart { - width: 100%; - height: 250px; - background: #000; - border: 1px solid var(--neon-cyan); -} - -/* Trades Panel */ -.trades-panel { - margin-top: 20px; - background: rgba(0, 0, 0, 0.8); - border: 2px solid var(--neon-pink); - border-radius: 4px; - overflow: hidden; - max-height: 300px; - display: flex; - flex-direction: column; -} - -.trades-header { - background: rgba(255, 0, 255, 0.1); - padding: 10px 15px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid var(--neon-pink); - font-family: 'Orbitron', sans-serif; - font-size: 0.8rem; - color: var(--neon-pink); - text-transform: uppercase; -} - -.trades-list { - overflow-y: auto; - flex: 1; - padding: 10px; -} - -.trade-item { - padding: 8px; - margin-bottom: 6px; - background: rgba(255, 255, 255, 0.05); - border-left: 3px solid var(--neon-cyan); - border-radius: 2px; - font-family: 'Courier New', monospace; - font-size: 0.8rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.trade-item.buy { - border-left-color: var(--neon-green); -} - -.trade-item.sell { - border-left-color: var(--neon-pink); -} - -.trade-info { - display: flex; - flex-direction: column; - gap: 4px; -} - -.trade-action { - font-weight: 700; - color: var(--neon-cyan); -} - -.trade-item.buy .trade-action { - color: var(--neon-green); -} - -.trade-item.sell .trade-action { - color: var(--neon-pink); -} - -.trade-details { - font-size: 0.75rem; - color: var(--text-secondary); -} - -.trade-pnl { - font-weight: 700; - font-size: 0.9rem; -} - -.trade-pnl.positive { - color: var(--neon-green); -} - -.trade-pnl.negative { - color: var(--neon-pink); -} - -/* Responsive */ -@media (max-width: 1200px) { - .dashboard-grid { - grid-template-columns: 1fr; - } - - .positions-panel, - .backtest-panel { - grid-column: 1; - } -} diff --git a/polymarket/gui/templates/dashboard.html b/polymarket/gui/templates/dashboard.html deleted file mode 100644 index 007bc67..0000000 --- a/polymarket/gui/templates/dashboard.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - - - CYBERPUNK POLYMARKET | Trading Dashboard - - - - - -
- -
-
POLYMARKET
-
CYBERPUNK TRADING INTERFACE
-
- - OFFLINE -
-
- - -
- -
-
-

STRATEGY CONTROL

-
-
-
-
- - - 0.15 -
-
- - - 0.70 -
-
- - -
-
- - -
- - -
-
- - -
-
-

ACTIVE MARKETS

-
-
-
-
-
LOADING MARKETS...
-
-
-
- - -
-
-

PERFORMANCE METRICS

-
-
-
-
-
-
BALANCE
-
$0.00
-
-
-
EQUITY
-
$0.00
-
-
-
POSITIONS
-
0
-
-
-
TOTAL TRADES
-
0
-
-
-
WIN RATE
-
0%
-
-
-
NET P&L
-
$0.00
-
-
-
-
- - -
-
-

OPEN POSITIONS

-
-
-
-
-
NO OPEN POSITIONS
-
-
-
- - -
-
-

BACKTESTING

-
-
-
-
-
- - -
-
- - -
-
- - -
- -
- - -
-
- TERMINAL OUTPUT - -
-
-
[SYSTEM] Ready for backtest...
-
-
- - -
-
- EQUITY CURVE -
- $1000.00 - +$0.00 -
-
- -
- - -
-
- RECENT TRADES - 0 trades -
-
-
No trades yet
-
-
- - -
-
-
-
- - -
-
-
- - - - - - - - diff --git a/polymarket/gui/test_server.py b/polymarket/gui/test_server.py deleted file mode 100644 index a77d7b1..0000000 --- a/polymarket/gui/test_server.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Test server startup""" -import sys -from pathlib import Path - -# Add paths -gui_dir = Path(__file__).parent -project_root = gui_dir.parent.parent -sys.path.insert(0, str(project_root)) - -print("Testing imports...") -try: - from polymarket.api import GammaClient, ClobClient - print("✓ API imports OK") -except Exception as e: - print(f"✗ API import failed: {e}") - sys.exit(1) - -try: - from polymarket.strategies.examples import SimpleProbabilityStrategy - print("✓ Strategy imports OK") -except Exception as e: - print(f"✗ Strategy import failed: {e}") - sys.exit(1) - -try: - from flask import Flask - print("✓ Flask import OK") -except Exception as e: - print(f"✗ Flask import failed: {e}") - sys.exit(1) - -print("\nAll imports successful! Starting server...") -print("=" * 60) diff --git a/polymarket/strategies/__init__.py b/polymarket/strategies/__init__.py deleted file mode 100644 index 33c5928..0000000 --- a/polymarket/strategies/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Polymarket Trading Strategies""" - -from .base_strategy import BaseStrategy, MarketSignal, Position - -__all__ = ['BaseStrategy', 'MarketSignal', 'Position'] diff --git a/polymarket/strategies/__pycache__/__init__.cpython-312.pyc b/polymarket/strategies/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 4dbe77b..0000000 Binary files a/polymarket/strategies/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/strategies/__pycache__/base_strategy.cpython-312.pyc b/polymarket/strategies/__pycache__/base_strategy.cpython-312.pyc deleted file mode 100644 index 5a2239a..0000000 Binary files a/polymarket/strategies/__pycache__/base_strategy.cpython-312.pyc and /dev/null differ diff --git a/polymarket/strategies/base_strategy.py b/polymarket/strategies/base_strategy.py deleted file mode 100644 index 67ae2ca..0000000 --- a/polymarket/strategies/base_strategy.py +++ /dev/null @@ -1,218 +0,0 @@ -""" -Base Strategy Class for Polymarket Trading - -All trading strategies should inherit from this class. -""" - -from abc import ABC, abstractmethod -from typing import Dict, Optional, List, Any -from datetime import datetime -from dataclasses import dataclass -import numpy as np - - -@dataclass -class MarketSignal: - """Trading signal from strategy""" - action: str # 'BUY', 'SELL', 'HOLD' - token_id: str # Which outcome token to trade - size: float # Position size (0.0 to 1.0) - confidence: float # Confidence level (0.0 to 1.0) - reason: str # Human-readable reason - metadata: Dict[str, Any] # Additional strategy-specific data - - -@dataclass -class Position: - """Open position tracking""" - token_id: str - outcome: str # 'Yes' or 'No' - size: float - entry_price: float - entry_time: datetime - current_price: float - unrealized_pnl: float - realized_pnl: float = 0.0 - - -class BaseStrategy(ABC): - """ - Base class for all Polymarket trading strategies. - - Inherit from this class and implement: - - analyze_market(): Your trading logic - - get_parameters(): Return strategy parameters - """ - - def __init__(self, name: str, initial_balance: float = 1000.0): - """ - Initialize the strategy. - - Args: - name: Strategy name - initial_balance: Starting USDC balance - """ - self.name = name - self.initial_balance = initial_balance - self.current_balance = initial_balance - self.equity = initial_balance - - # Position tracking - self.positions: Dict[str, Position] = {} # token_id -> Position - self.closed_positions: List[Position] = [] - - # Performance metrics - self.total_trades = 0 - self.winning_trades = 0 - self.losing_trades = 0 - self.total_profit = 0.0 - self.total_loss = 0.0 - self.max_drawdown = 0.0 - self.peak_equity = initial_balance - - # Risk management - self.max_position_size = 0.5 # Max 50% of balance per position - self.max_total_exposure = 0.8 # Max 80% total exposure - self.min_confidence = 0.6 # Minimum confidence to trade - - @abstractmethod - def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - """ - Analyze market and generate trading signal. - - Args: - market_data: Dictionary containing: - - 'event': Event information - - 'market': Market information - - 'prices': Current outcome prices - - 'orderbook': Orderbook data - - 'history': Historical price data (if available) - - Returns: - MarketSignal or None if no trade - """ - pass - - @abstractmethod - def get_parameters(self) -> Dict[str, Any]: - """ - Return strategy parameters. - - Returns: - Dictionary of parameter names and values - """ - pass - - def update_position(self, token_id: str, current_price: float) -> None: - """ - Update position with current price. - - Args: - token_id: Token ID - current_price: Current market price - """ - if token_id in self.positions: - pos = self.positions[token_id] - # Validate inputs - if not (np.isfinite(current_price) and current_price > 0 and current_price < 1): - return # Skip update if price is invalid - if not (np.isfinite(pos.size) and pos.size > 0): - return # Skip update if position size is invalid - if not (np.isfinite(pos.entry_price) and pos.entry_price > 0): - return # Skip update if entry price is invalid - - pos.current_price = current_price - unrealized_pnl = (current_price - pos.entry_price) * pos.size - pos.unrealized_pnl = unrealized_pnl if np.isfinite(unrealized_pnl) else 0.0 - - def calculate_equity(self) -> float: - """Calculate current equity (balance + unrealized PnL)""" - # Validate balance - if not np.isfinite(self.current_balance): - self.current_balance = 0.0 - - unrealized = sum( - pos.unrealized_pnl if np.isfinite(pos.unrealized_pnl) else 0.0 - for pos in self.positions.values() - ) - equity = self.current_balance + unrealized - return equity if np.isfinite(equity) else self.current_balance - - def update_drawdown(self) -> None: - """Update maximum drawdown""" - self.equity = self.calculate_equity() - if self.equity > self.peak_equity: - self.peak_equity = self.equity - - # Safe division - avoid division by zero - if self.peak_equity > 0: - drawdown = (self.peak_equity - self.equity) / self.peak_equity - if drawdown > self.max_drawdown: - self.max_drawdown = drawdown - else: - # If peak_equity is 0, set drawdown to 0 - self.max_drawdown = 0.0 - - def can_open_position(self, size: float, token_id: str) -> bool: - """ - Check if strategy can open a new position. - - Args: - size: Position size in USDC - token_id: Token ID - - Returns: - True if position can be opened - """ - # Validate inputs - if not (np.isfinite(size) and size > 0): - return False - if not (np.isfinite(self.current_balance) and self.current_balance > 0): - return False - - # Check if already have position in this token - if token_id in self.positions: - return False - - # Check position size limit - if size > self.current_balance * self.max_position_size: - return False - - # Check total exposure limit - total_exposure = sum( - pos.size if np.isfinite(pos.size) else 0.0 - for pos in self.positions.values() - ) - if not np.isfinite(total_exposure): - total_exposure = 0.0 - - if total_exposure + size > self.current_balance * self.max_total_exposure: - return False - - # Check balance - if size > self.current_balance: - return False - - return True - - def get_performance_metrics(self) -> Dict[str, Any]: - """Get current performance metrics""" - win_rate = (self.winning_trades / self.total_trades * 100) if self.total_trades > 0 else 0.0 - profit_factor = abs(self.total_profit / self.total_loss) if self.total_loss != 0 else 0.0 - - return { - 'name': self.name, - 'total_trades': self.total_trades, - 'winning_trades': self.winning_trades, - 'losing_trades': self.losing_trades, - 'win_rate': win_rate, - 'total_profit': self.total_profit, - 'total_loss': self.total_loss, - 'net_profit': self.total_profit + self.total_loss, - 'profit_factor': profit_factor, - 'max_drawdown': self.max_drawdown, - 'current_balance': self.current_balance, - 'equity': self.equity, - 'unrealized_pnl': sum(pos.unrealized_pnl for pos in self.positions.values()), - 'open_positions': len(self.positions) - } diff --git a/polymarket/strategies/examples/__init__.py b/polymarket/strategies/examples/__init__.py deleted file mode 100644 index a050dec..0000000 --- a/polymarket/strategies/examples/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Example Strategies""" - -from .simple_probability import SimpleProbabilityStrategy - -__all__ = ['SimpleProbabilityStrategy'] diff --git a/polymarket/strategies/examples/__pycache__/__init__.cpython-312.pyc b/polymarket/strategies/examples/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index e32cb14..0000000 Binary files a/polymarket/strategies/examples/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/strategies/examples/__pycache__/simple_probability.cpython-312.pyc b/polymarket/strategies/examples/__pycache__/simple_probability.cpython-312.pyc deleted file mode 100644 index 18abd93..0000000 Binary files a/polymarket/strategies/examples/__pycache__/simple_probability.cpython-312.pyc and /dev/null differ diff --git a/polymarket/strategies/examples/simple_probability.py b/polymarket/strategies/examples/simple_probability.py deleted file mode 100644 index 5d1823c..0000000 --- a/polymarket/strategies/examples/simple_probability.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Simple Probability Strategy Example - -Trades when market probability deviates significantly from fair value. -""" - -from typing import Dict, Optional -from ..base_strategy import BaseStrategy, MarketSignal - - -class SimpleProbabilityStrategy(BaseStrategy): - """ - Simple strategy that buys when probability is too low, - sells when probability is too high. - """ - - def __init__(self, - name: str = "SimpleProbability", - initial_balance: float = 1000.0, - threshold: float = 0.15, - min_confidence: float = 0.7): - """ - Initialize strategy. - - Args: - name: Strategy name - initial_balance: Starting balance - threshold: Probability deviation threshold (0.15 = 15%) - min_confidence: Minimum confidence to trade - """ - super().__init__(name, initial_balance) - self.threshold = threshold - self.min_confidence = min_confidence - - def analyze_market(self, market_data: Dict) -> Optional[MarketSignal]: - """ - Analyze market and generate signal. - - Strategy logic: - - If Yes probability < 0.5 - threshold: Buy (undervalued) - - If Yes probability > 0.5 + threshold: Sell (overvalued) - """ - market = market_data.get('market', {}) - prices = market_data.get('prices', {}) - - if not prices: - return None - - yes_price = prices.get('Yes', 0.5) - no_price = prices.get('No', 0.5) - - # Calculate deviation from fair value (0.5) - deviation = abs(yes_price - 0.5) - - if deviation < self.threshold: - return None # Not enough deviation - - # Get token_id from market - market_obj = market_data.get('market', {}) - token_ids = market_obj.get('clobTokenIds', []) - if not token_ids: - return None - - token_id = token_ids[0] - - # Determine action - if yes_price < (0.5 - self.threshold): - # Yes is undervalued, buy - confidence = min(1.0, deviation / self.threshold) - if confidence >= self.min_confidence: - return MarketSignal( - action='BUY', - token_id=token_id, - size=0.2, # 20% of balance - confidence=confidence, - reason=f"Yes probability {yes_price:.2%} is undervalued (deviation: {deviation:.2%})", - metadata={'yes_price': yes_price, 'deviation': deviation} - ) - - elif yes_price > (0.5 + self.threshold): - # Yes is overvalued, sell (close position if we have one) - confidence = min(1.0, deviation / self.threshold) - if confidence >= self.min_confidence: - # Check if we have a position to close - if token_id in self.positions: - return MarketSignal( - action='SELL', - token_id=token_id, - size=1.0, # Close entire position - confidence=confidence, - reason=f"Yes probability {yes_price:.2%} is overvalued (deviation: {deviation:.2%})", - metadata={'yes_price': yes_price, 'deviation': deviation} - ) - - return None - - def get_parameters(self) -> Dict: - """Return strategy parameters""" - return { - 'threshold': self.threshold, - 'min_confidence': self.min_confidence, - 'max_position_size': self.max_position_size, - 'max_total_exposure': self.max_total_exposure - } diff --git a/polymarket/trading/__init__.py b/polymarket/trading/__init__.py deleted file mode 100644 index d2d5ebb..0000000 --- a/polymarket/trading/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Live Trading Module""" - -from .engine import LiveTradingEngine - -__all__ = ['LiveTradingEngine'] diff --git a/polymarket/trading/__pycache__/__init__.cpython-312.pyc b/polymarket/trading/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index d57cfc7..0000000 Binary files a/polymarket/trading/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/trading/__pycache__/engine.cpython-312.pyc b/polymarket/trading/__pycache__/engine.cpython-312.pyc deleted file mode 100644 index ace4cee..0000000 Binary files a/polymarket/trading/__pycache__/engine.cpython-312.pyc and /dev/null differ diff --git a/polymarket/trading/engine.py b/polymarket/trading/engine.py deleted file mode 100644 index a418901..0000000 --- a/polymarket/trading/engine.py +++ /dev/null @@ -1,302 +0,0 @@ -""" -Live Trading Engine for Polymarket - -Handles real-time order placement and position management. -""" - -from typing import Dict, Optional, List -from datetime import datetime -import time -from ..strategies.base_strategy import BaseStrategy, MarketSignal -from ..api.gamma_client import GammaClient -from ..api.clob_client import ClobClient -from ..api.data_client import DataClient -from ..utils.config import Config - - -class LiveTradingEngine: - """ - Live trading engine for Polymarket. - - Monitors markets, executes strategy signals, and manages positions. - """ - - def __init__(self, - strategy: BaseStrategy, - poll_interval: int = 60): - """ - Initialize live trading engine. - - Args: - strategy: Strategy instance to trade - poll_interval: Seconds between market checks - """ - self.strategy = strategy - self.poll_interval = poll_interval - self.is_running = False - - # Initialize API clients - self.gamma_client = GammaClient() - self.clob_client = ClobClient() - self.data_client = DataClient(api_key=Config.DATA_API_KEY) - - # Trading state - self.monitored_markets: List[Dict] = [] - self.last_check_time: Optional[datetime] = None - - def setup_clob_client(self): - """ - Setup authenticated CLOB client for order placement. - - Note: This requires py-clob-client package and proper authentication. - For full implementation, install: pip install py-clob-client - """ - try: - from py_clob_client.client import ClobClient as PyClobClient - from py_clob_client.utilities import create_or_derive_api_creds - - if not Config.PRIVATE_KEY: - raise ValueError("POLYMARKET_PRIVATE_KEY not set in config") - - # Initialize client - host = "https://clob.polymarket.com" - chain_id = Config.CHAIN_ID - - self.trading_client = PyClobClient( - host=host, - key=Config.PRIVATE_KEY, - chain_id=chain_id - ) - - # Derive API credentials - creds = self.trading_client.create_or_derive_api_creds() - - # Reinitialize with credentials - self.trading_client = PyClobClient( - host=host, - api_key=creds['apiKey'], - api_secret=creds['secret'], - api_passphrase=creds['passphrase'], - signature_type=Config.SIGNATURE_TYPE, - funder=Config.FUNDER_ADDRESS, - chain_id=chain_id - ) - - print("CLOB client authenticated successfully") - return True - - except ImportError: - print("Warning: py-clob-client not installed. Install with: pip install py-clob-client") - print("Live trading will be simulated only.") - self.trading_client = None - return False - except Exception as e: - print(f"Error setting up CLOB client: {e}") - self.trading_client = None - return False - - def add_market(self, event_slug: Optional[str] = None, market_slug: Optional[str] = None): - """ - Add a market to monitor. - - Args: - event_slug: Event slug (e.g., 'will-bitcoin-reach-100k-by-2025') - market_slug: Market slug - """ - if event_slug: - event = self.gamma_client.get_event_by_slug(event_slug) - if event: - self.monitored_markets.append({ - 'event': event, - 'markets': event.get('markets', []) - }) - elif market_slug: - market = self.gamma_client.get_market_by_slug(market_slug) - if market: - self.monitored_markets.append({ - 'event': None, - 'markets': [market] - }) - - def monitor_tag(self, tag_id: int, limit: int = 20): - """ - Monitor all active markets in a tag/category. - - Args: - tag_id: Tag ID to monitor - limit: Maximum number of markets - """ - events = self.gamma_client.get_events( - active=True, - closed=False, - tag_id=tag_id, - limit=limit - ) - - for event in events: - self.monitored_markets.append({ - 'event': event, - 'markets': event.get('markets', []) - }) - - def execute_order(self, signal: MarketSignal, market_data: Dict) -> Optional[Dict]: - """ - Execute a trading order. - - Args: - signal: Trading signal - market_data: Market data - - Returns: - Order result dictionary - """ - if not self.trading_client: - print("Warning: Trading client not available. Simulating order.") - return self._simulate_order(signal, market_data) - - market = market_data['market'] - token_ids = market.get('clobTokenIds', []) - - if not token_ids: - return None - - token_id = token_ids[0] if signal.action == 'BUY' else token_ids[0] - - # Calculate order size - position_size_usdc = signal.size * self.strategy.current_balance - - try: - if signal.action == 'BUY': - # Place buy order - # Note: Actual implementation would use trading_client.create_order() - # This is a placeholder - print(f"Placing BUY order: {position_size_usdc:.2f} USDC at token {token_id}") - # order = self.trading_client.create_order(...) - return {'status': 'placed', 'action': 'BUY', 'size': position_size_usdc} - - elif signal.action == 'SELL': - # Close position - if token_id in self.strategy.positions: - print(f"Closing position: {token_id}") - # order = self.trading_client.create_order(...) - return {'status': 'closed', 'action': 'SELL', 'token_id': token_id} - - except Exception as e: - print(f"Error executing order: {e}") - return None - - def _simulate_order(self, signal: MarketSignal, market_data: Dict) -> Dict: - """Simulate order execution for testing""" - return { - 'status': 'simulated', - 'action': signal.action, - 'timestamp': datetime.now(), - 'signal': signal - } - - def update_positions(self): - """Update all open positions with current prices""" - for token_id, position in list(self.strategy.positions.items()): - try: - current_price = self.clob_client.get_price(token_id, side='buy') - self.strategy.update_position(token_id, current_price) - except Exception as e: - print(f"Error updating position {token_id}: {e}") - - def check_markets(self): - """Check all monitored markets for trading signals""" - for market_data in self.monitored_markets: - for market in market_data['markets']: - # Get current prices - try: - token_ids = market.get('clobTokenIds', []) - if not token_ids: - continue - - # Get orderbook data - orderbook = self.clob_client.get_orderbook(token_ids[0]) - best_bid_ask = self.clob_client.get_best_bid_ask(token_ids[0]) - - # Parse outcomes and prices - import json - outcomes = json.loads(market.get('outcomes', '["Yes", "No"]')) - prices = json.loads(market.get('outcomePrices', '[0.5, 0.5]')) - - market_info = { - 'event': market_data['event'], - 'market': market, - 'prices': { - outcome: float(price) - for outcome, price in zip(outcomes, prices) - }, - 'orderbook': orderbook, - 'best_bid_ask': best_bid_ask, - 'timestamp': datetime.now() - } - - # Get strategy signal - signal = self.strategy.analyze_market(market_info) - - if signal and signal.confidence >= self.strategy.min_confidence: - print(f"\nSignal generated: {signal.action} - {signal.reason}") - result = self.execute_order(signal, market_info) - if result: - print(f"Order result: {result}") - - except Exception as e: - print(f"Error checking market: {e}") - continue - - def start(self): - """Start the live trading engine""" - print("Starting live trading engine...") - - # Setup trading client - if not self.setup_clob_client(): - print("Warning: Running in simulation mode") - - if not self.monitored_markets: - print("No markets to monitor. Add markets with add_market() or monitor_tag()") - return - - self.is_running = True - print(f"Monitoring {len(self.monitored_markets)} markets") - print(f"Poll interval: {self.poll_interval} seconds") - print("Press Ctrl+C to stop\n") - - try: - while self.is_running: - self.last_check_time = datetime.now() - - # Update positions - self.update_positions() - - # Check markets - self.check_markets() - - # Print status - equity = self.strategy.calculate_equity() - print(f"\n[{self.last_check_time.strftime('%Y-%m-%d %H:%M:%S')}] " - f"Equity: ${equity:.2f} | " - f"Open Positions: {len(self.strategy.positions)} | " - f"Total Trades: {self.strategy.total_trades}") - - # Wait for next poll - time.sleep(self.poll_interval) - - except KeyboardInterrupt: - print("\nStopping trading engine...") - self.stop() - - def stop(self): - """Stop the trading engine""" - self.is_running = False - print("Trading engine stopped") - - # Print final performance - metrics = self.strategy.get_performance_metrics() - print("\nFinal Performance:") - print(f" Total Trades: {metrics['total_trades']}") - print(f" Win Rate: {metrics['win_rate']:.2f}%") - print(f" Net Profit: ${metrics['net_profit']:.2f}") - print(f" Final Equity: ${metrics['equity']:.2f}") diff --git a/polymarket/utils/__init__.py b/polymarket/utils/__init__.py deleted file mode 100644 index 3c95cd3..0000000 --- a/polymarket/utils/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Utility Functions""" - -from .config import Config - -__all__ = ['Config'] diff --git a/polymarket/utils/__pycache__/__init__.cpython-312.pyc b/polymarket/utils/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 3c02af3..0000000 Binary files a/polymarket/utils/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/polymarket/utils/__pycache__/config.cpython-312.pyc b/polymarket/utils/__pycache__/config.cpython-312.pyc deleted file mode 100644 index d2c165c..0000000 Binary files a/polymarket/utils/__pycache__/config.cpython-312.pyc and /dev/null differ diff --git a/polymarket/utils/config.py b/polymarket/utils/config.py deleted file mode 100644 index 26e9962..0000000 --- a/polymarket/utils/config.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Configuration Management - -Loads configuration from environment variables or config file. -""" - -import os -from typing import Optional -from dotenv import load_dotenv - -load_dotenv() - - -class Config: - """Configuration class for Polymarket framework""" - - # API Configuration - PRIVATE_KEY: Optional[str] = os.getenv('POLYMARKET_PRIVATE_KEY') - CHAIN_ID: int = int(os.getenv('POLYMARKET_CHAIN_ID', '137')) - SIGNATURE_TYPE: int = int(os.getenv('POLYMARKET_SIGNATURE_TYPE', '0')) - FUNDER_ADDRESS: Optional[str] = os.getenv('POLYMARKET_FUNDER_ADDRESS') - - # API Keys (for authenticated endpoints) - GAMMA_API_KEY: Optional[str] = os.getenv('POLYMARKET_GAMMA_API_KEY') - CLOB_API_KEY: Optional[str] = os.getenv('POLYMARKET_CLOB_API_KEY') - DATA_API_KEY: Optional[str] = os.getenv('POLYMARKET_DATA_API_KEY') - - # Trading Configuration - DEFAULT_INITIAL_BALANCE: float = float(os.getenv('POLYMARKET_INITIAL_BALANCE', '1000.0')) - MAX_POSITION_SIZE: float = float(os.getenv('POLYMARKET_MAX_POSITION_SIZE', '0.5')) - MAX_TOTAL_EXPOSURE: float = float(os.getenv('POLYMARKET_MAX_TOTAL_EXPOSURE', '0.8')) - - # Rate Limiting - REQUEST_DELAY: float = float(os.getenv('POLYMARKET_REQUEST_DELAY', '0.1')) # 100ms between requests - MAX_REQUESTS_PER_MINUTE: int = int(os.getenv('POLYMARKET_MAX_REQUESTS_PER_MINUTE', '60')) - - # Backtesting - BACKTEST_START_DATE: Optional[str] = os.getenv('POLYMARKET_BACKTEST_START_DATE') - BACKTEST_END_DATE: Optional[str] = os.getenv('POLYMARKET_BACKTEST_END_DATE') - - @classmethod - def validate(cls) -> bool: - """Validate that required configuration is present""" - if not cls.PRIVATE_KEY: - print("Warning: POLYMARKET_PRIVATE_KEY not set") - return False - if not cls.FUNDER_ADDRESS: - print("Warning: POLYMARKET_FUNDER_ADDRESS not set") - return False - return True