Browse AI-generated trading strategies shared by the community. Fork, learn, and build on each other's work.
| Score▼ | Strategy | Author | Win Rate▼ | Return▼ | PF▼ | MDD▼ | Trades▼ | Actions | ||
|---|---|---|---|---|---|---|---|---|---|---|
|
—
|
BB Squeeze Breakout + ATR Filter (GBM)
Maximize risk-adjusted return (Sharpe/Calmar). GradientBoosting chosen for its strong performance on tabular data with structured non-linear…
|
D
@delta_one
|
EURUSD | 15min | 59.9%50.0% | +1.64%-10.60% | 1.070.74 | 2.90%2.90% | 30254 |
|
# ╔══════════════════════════════════════════════════════════════╗
# ║ STRATEGY REQUEST LOG ║
# ╚══════════════════════════════════════════════════════════════╝
# Generated : 2026-05-06 03:01:00
# Model : Gradient Boosting
# Feature Eng. : BB (20,2.0), ATR 14 + Auto-add features: ON
# Signal / Entry : Enter when model confidence > threshold; exit on opposite signal or SL/TP
# Optimization : Maximize risk-adjusted return
# Risk Mgmt : Stop loss 0.5%, Take profit 1.0%
# Risk Filter : —
# ══════════════════════════════════════════════════════════════
# ============================================================
# Bollinger Bands Squeeze Breakout — GradientBoosting Strategy
# ============================================================
# SECTION 0 — IMPORTS & CONSTANTS
import numpy as np
import pandas as pd
DATA_PATH = "/root/Desktop/QuantifyMe/data/ohlc/EURUSD_15min.parquet"
START_DATE = "2025-04-24"
END_DATE = "2026-04-24"
VALIDATION_DATE = ""
TRAIN_SPLIT = 0.7
# SECTION 1 — FEATURE ENGINEERING
def feature_engineering(df, close, open_, high, low):
# ── Bollinger Bands (20, 2) ──────────────────────────────────────────────
bb_period = 20
bb_std = 2.0
bb_mid = close.rolling(bb_period).mean()
bb_sigma = close.rolling(bb_period).std(ddof=0)
bb_upper = bb_mid + bb_std * bb_sigma
bb_lower = bb_mid - bb_std * bb_sigma
df["bb_mid"] = bb_mid
df["bb_upper"] = bb_upper
df["bb_lower"] = bb_lower
# Required derived features
df["bb_width"] = (bb_upper - bb_lower) / bb_mid
df["bb_pct"] = (close - bb_lower) / (bb_upper - bb_lower)
# ── ATR (14) ─────────────────────────────────────────────────────────────
atr_period = 14
tr = pd.concat([
high - low,
(high - close.shift(1)).abs(),
(low - close.shift(1)).abs()
], axis=1).max(axis=1)
atr = tr.ewm(span=atr_period, min_periods=atr_period, adjust=False).mean()
df["atr"] = atr
df["natr"] = atr / close
# ── Squeeze Detection ────────────────────────────────────────────────────
# Squeeze = BB width is near its lowest over recent N bars (compression)
squeeze_window = 20
bb_width_min = df["bb_width"].rolling(squeeze_window).min()
bb_width_max = df["bb_width"].rolling(squeeze_window).max()
# Normalised squeeze score: 0 = fully squeezed, 1 = fully expanded
df["squeeze_score"] = (df["bb_width"] - bb_width_min) / (bb_width_max - bb_width_min + 1e-10)
# Squeeze flag: 1 if currently squeezed (bottom 20th percentile of width)
df["in_squeeze"] = np.where(df["squeeze_score"] < 0.20, 1, 0)
# Breakout direction: momentum after squeeze
df["squeeze_breakout_up"] = np.where((df["in_squeeze"].shift(1) == 1) & (close > bb_upper), 1, 0)
df["squeeze_breakout_down"] = np.where((df["in_squeeze"].shift(1) == 1) & (close < bb_lower), 1, 0)
# ── Momentum / Rate-of-Change ────────────────────────────────────────────
df["roc_5"] = close.pct_change(5)
df["roc_10"] = close.pct_change(10)
df["roc_20"] = close.pct_change(20)
# ── RSI (14) ─────────────────────────────────────────────────────────────
rsi_period = 14
delta = close.diff()
gain = delta.clip(lower=0)
loss = (-delta).clip(lower=0)
avg_gain = gain.ewm(span=rsi_period, min_periods=rsi_period, adjust=False).mean()
avg_loss = loss.ewm(span=rsi_period, min_periods=rsi_period, adjust=False).mean()
rs = avg_gain / (avg_loss + 1e-10)
df["rsi_14"] = 100 - (100 / (1 + rs))
# RSI distance from 50 (overbought/oversold)
df["rsi_dist50"] = df["rsi_14"] - 50.0
# ── MACD (12, 26, 9) ────────────────────────────────────────────────────
ema12 = close.ewm(span=12, adjust=False).mean()
ema26 = close.ewm(span=26, adjust=False).mean()
macd_line = ema12 - ema26
macd_signal = macd_line.ewm(span=9, adjust=False).mean()
df["macd_line"] = macd_line
df["macd_signal"] = macd_signal
df["macd_hist"] = macd_line - macd_signal
# Normalise MACD by ATR to make scale-invariant
df["macd_hist_norm"] = df["macd_hist"] / (atr + 1e-10)
# ── Volume / Candle Body Features ────────────────────────────────────────
body = (close - open_).abs()
candle_range = high - low
df["body_ratio"] = body / (candle_range + 1e-10) # 0=doji, 1=full body
df["close_pos"] = (close - low) / (candle_range + 1e-10) # position within bar
# ── Trend / SMA Features ─────────────────────────────────────────────────
df["sma_20"] = bb_mid # reuse already computed
df["sma_50"] = close.rolling(50).mean()
df["sma_100"] = close.rolling(100).mean()
df["price_vs_sma20"] = (close - df["sma_20"]) / (atr + 1e-10)
df["price_vs_sma50"] = (close - df["sma_50"]) / (atr + 1e-10)
df["price_vs_sma100"] = (close - df["sma_100"]) / (atr + 1e-10)
# SMA cross: 20 vs 50
df["sma20_vs_sma50"] = (df["sma_20"] - df["sma_50"]) / (atr + 1e-10)
# ── BB Width Rate-of-Change (squeeze momentum) ───────────────────────────
df["bb_width_roc3"] = df["bb_width"].pct_change(3)
df["bb_width_roc8"] = df["bb_width"].pct_change(8)
# ── Lagged BB features ───────────────────────────────────────────────────
df["bb_pct_lag1"] = df["bb_pct"].shift(1)
df["bb_pct_lag2"] = df["bb_pct"].shift(2)
df["bb_pct_lag4"] = df["bb_pct"].shift(4)
df["bb_width_lag1"] = df["bb_width"].shift(1)
df["bb_width_lag4"] = df["bb_width"].shift(4)
# ── Volatility Regime ────────────────────────────────────────────────────
natr_ma = df["natr"].rolling(40).mean()
df["vol_regime"] = np.where(df["natr"] > natr_ma, 1, 0) # 1=high vol, 0=low vol
# ── Price Distance from Bands (ATR-normalised) ───────────────────────────
df["dist_upper"] = (bb_upper - close) / (atr + 1e-10)
df["dist_lower"] = (close - bb_lower) / (atr + 1e-10)
# ── Hour / Session (cyclical) ────────────────────────────────────────────
if hasattr(df.index, "hour"):
hour = df.index.hour
df["hour_sin"] = np.sin(2 * np.pi * hour / 24)
df["hour_cos"] = np.cos(2 * np.pi * hour / 24)
# ── Fill warm-up NaN ────────────────────────────────────────────────────
df = df.bfill().ffill()
return df
# SECTION 2 — STRATEGY CONFIG
def strategy_config():
return {
"title": "BB Squeeze Breakout + ATR Filter (GBM)",
"model_type": "GradientBoostingClassifier",
"model_params": {
"n_estimators": 400,
"max_depth": 4,
"learning_rate": 0.04,
"subsample": 0.75,
"max_features": "sqrt",
"min_samples_leaf": 20,
"min_samples_split": 40,
"n_iter_no_change": 30,
"validation_fraction": 0.12,
"tol": 1e-4,
"random_state": 42,
},
"signal_threshold": 0.55,
"direction": "both",
"stop_loss": 0.005,
"take_profit": 0.010,
"cooldown": 0,
"max_positions": 1,
"on_opposite": "reverse",
"session_filter": [6, 20],
"min_atr": 0.0002,
"trend_filter": None,
"target_horizon": 4,
"objective": (
"Maximize risk-adjusted return (Sharpe/Calmar). "
"GradientBoosting chosen for its strong performance on tabular data with "
"structured non-linear interactions. Shallow trees (depth=4) with high "
"n_estimators and low learning_rate reduce overfitting. subsample=0.75 "
"adds stochasticity. Early stopping via n_iter_no_change avoids "
"over-training on the 15-min EURUSD regime. SL=0.5%/TP=1.0% gives 2:1 "
"reward-risk, consistent with a squeeze-breakout edge. session_filter "
"[6,20] UTC captures London+NY sessions where BB squeezes resolve cleanly. "
"min_atr guards against ultra-low-volatility false breakouts."
),
"notes": (
"Features centre on BB squeeze mechanics: bb_width, squeeze_score, "
"in_squeeze flag, breakout_up/down, width RoC, and lagged bb_pct. "
"Complemented by RSI, MACD histogram (ATR-normalised), SMA trend "
"distances, candle body ratio, and cyclical hour encoding. "
"target_horizon=4 bars (1 hour) aligns with the typical squeeze "
"resolution time on 15-min EURUSD. Signal threshold 0.55 filters "
"marginal signals while preserving sufficient trade frequency."
),
}
|
||||||||||
|
—
|
EUR/USD SMA+RSI+MACD+BB Momentum XGBoost
Maximize risk-adjusted return (Sharpe/Calmar) on EUR/USD 15-min. XGBoost with moderate depth and strong regularisation to avoid overfitting …
|
E
@echo-quanta-127
|
EURUSD | 15min | 42.7%45.2% | +5.42%-1.84% | 1.600.95 | 2.51%2.51% | 9631 |
|
# ╔══════════════════════════════════════════════════════════════╗
# ║ STRATEGY REQUEST LOG ║
# ╚══════════════════════════════════════════════════════════════╝
# Generated : 2026-05-06 02:04:57
# Model : XGBoost
# Feature Eng. : SMA (20,50,200), BB (20,2.0), RSI 14, MACD (12,26,9), ATR 14 + Auto-add features: ON
# Signal / Entry : Enter when model confidence > threshold; exit on opposite signal or SL/TP
# Optimization : Maximize risk-adjusted return
# Risk Mgmt : Stop loss 0.5%, Take profit 1.0%
# Risk Filter : —
# ══════════════════════════════════════════════════════════════
# ============================================================
# EUR/USD Multi-Indicator Momentum + Mean-Reversion (XGBoost)
# ============================================================
# SECTION 0 — IMPORTS & CONSTANTS
import numpy as np
import pandas as pd
DATA_PATH = "/root/Desktop/QuantifyMe/data/ohlc/EURUSD_15min.parquet"
START_DATE = "2025-04-24"
END_DATE = "2026-04-24"
VALIDATION_DATE = ""
TRAIN_SPLIT = 0.7
# SECTION 1 — FEATURE ENGINEERING
def feature_engineering(df, close, open_, high, low):
# ── SMA 20, 50, 200 + distance-from-close ──────────────────────────────
for period in [20, 50, 200]:
sma = close.rolling(period).mean()
df[f"sma_{period}"] = sma
df[f"dm_sma_{period}"] = (close - sma) / sma
# ── Bollinger Bands (20, 2) ─────────────────────────────────────────────
bb_mid = close.rolling(20).mean()
bb_std = close.rolling(20).std(ddof=0)
bb_upper = bb_mid + 2.0 * bb_std
bb_lower = bb_mid - 2.0 * bb_std
df["bb_mid"] = bb_mid
df["bb_upper"] = bb_upper
df["bb_lower"] = bb_lower
df["bb_width"] = (bb_upper - bb_lower) / bb_mid
bb_range = bb_upper - bb_lower
df["bb_pct"] = np.where(bb_range != 0, (close - bb_lower) / bb_range, 0.5)
# ── RSI 14 ──────────────────────────────────────────────────────────────
delta = close.diff()
gain = delta.clip(lower=0)
loss = (-delta).clip(lower=0)
avg_gain = gain.ewm(com=13, min_periods=14, adjust=False).mean()
avg_loss = loss.ewm(com=13, min_periods=14, adjust=False).mean()
rs = np.where(avg_loss != 0, avg_gain / avg_loss, 100.0)
df["rsi_14"] = 100.0 - (100.0 / (1.0 + rs))
# ── MACD (12, 26, 9) ────────────────────────────────────────────────────
ema_12 = close.ewm(span=12, adjust=False).mean()
ema_26 = close.ewm(span=26, adjust=False).mean()
macd_line = ema_12 - ema_26
signal_line = macd_line.ewm(span=9, adjust=False).mean()
df["macd_line"] = macd_line
df["macd_signal"] = signal_line
df["macd_hist"] = macd_line - signal_line
# ── ATR 14 + NATR ───────────────────────────────────────────────────────
prev_close = close.shift(1)
tr = pd.concat([
high - low,
(high - prev_close).abs(),
(low - prev_close).abs()
], axis=1).max(axis=1)
atr = tr.ewm(com=13, min_periods=14, adjust=False).mean()
df["atr_14"] = atr
df["natr"] = np.where(close != 0, atr / close, 0.0)
# ── Price momentum features ─────────────────────────────────────────────
for lag in [1, 2, 4, 8]:
df[f"ret_{lag}"] = close.pct_change(lag)
# ── Candle body / wick features ─────────────────────────────────────────
candle_range = (high - low).replace(0, np.nan)
df["body_ratio"] = np.where(
candle_range.notna(),
(close - open_).abs() / candle_range,
0.0
)
df["upper_wick"] = np.where(
candle_range.notna(),
(high - pd.concat([close, open_], axis=1).max(axis=1)) / candle_range,
0.0
)
df["lower_wick"] = np.where(
candle_range.notna(),
(pd.concat([close, open_], axis=1).min(axis=1) - low) / candle_range,
0.0
)
df["candle_dir"] = np.where(close >= open_, 1.0, -1.0)
# ── Volume-proxy: normalised range ─────────────────────────────────────
df["norm_range"] = (high - low) / close.rolling(20).mean()
# ── RSI derived ─────────────────────────────────────────────────────────
df["rsi_ob"] = np.where(df["rsi_14"] > 70, 1.0, 0.0)
df["rsi_os"] = np.where(df["rsi_14"] < 30, 1.0, 0.0)
df["rsi_mid_cross"] = np.where(df["rsi_14"] > 50, 1.0, -1.0)
# ── Trend alignment flags ───────────────────────────────────────────────
df["above_sma_20"] = np.where(close > df["sma_20"], 1.0, -1.0)
df["above_sma_50"] = np.where(close > df["sma_50"], 1.0, -1.0)
df["above_sma_200"] = np.where(close > df["sma_200"], 1.0, -1.0)
df["sma_20_50_cross"] = np.where(df["sma_20"] > df["sma_50"], 1.0, -1.0)
# ── MACD cross flag ─────────────────────────────────────────────────────
df["macd_cross"] = np.where(df["macd_line"] > df["macd_signal"], 1.0, -1.0)
# ── Bollinger squeeze ───────────────────────────────────────────────────
bb_width_ma = df["bb_width"].rolling(20).mean()
df["bb_squeeze"] = np.where(df["bb_width"] < bb_width_ma, 1.0, 0.0)
# ── Lagged RSI and MACD hist for regime detection ───────────────────────
df["rsi_14_lag1"] = df["rsi_14"].shift(1)
df["macd_hist_lag1"] = df["macd_hist"].shift(1)
df["rsi_slope"] = df["rsi_14"] - df["rsi_14_lag1"]
df["macd_hist_slope"] = df["macd_hist"] - df["macd_hist_lag1"]
# ── Rolling volatility ratio ─────────────────────────────────────────────
vol_short = close.pct_change().rolling(8).std()
vol_long = close.pct_change().rolling(32).std()
df["vol_ratio"] = np.where(vol_long != 0, vol_short / vol_long, 1.0)
# ── Hour-of-day (session proxy) ─────────────────────────────────────────
if hasattr(df.index, "hour"):
df["hour_sin"] = np.sin(2 * np.pi * df.index.hour / 24.0)
df["hour_cos"] = np.cos(2 * np.pi * df.index.hour / 24.0)
else:
df["hour_sin"] = 0.0
df["hour_cos"] = 1.0
# ── Fill any NaN from warm-up ───────────────────────────────────────────
df = df.bfill().ffill()
return df
# SECTION 2 — STRATEGY CONFIG
def strategy_config():
return {
"title": "EUR/USD SMA+RSI+MACD+BB Momentum XGBoost",
"model_type": "XGBClassifier",
"model_params": {
"n_estimators": 600,
"max_depth": 4,
"learning_rate": 0.04,
"subsample": 0.8,
"colsample_bytree": 0.75,
"min_child_weight": 3,
"gamma": 0.1,
"reg_alpha": 0.05,
"reg_lambda": 1.5,
"objective": "binary:logistic",
"random_state": 42,
"n_jobs": -1,
},
"signal_threshold": 0.54,
"direction": "both",
"stop_loss": 0.005,
"take_profit": 0.010,
"cooldown": 0,
"max_positions": 1,
"on_opposite": "reverse",
"session_filter": [6, 20],
"min_atr": 0.0002,
"trend_filter": "sma_50",
"target_horizon": 4,
"objective": (
"Maximize risk-adjusted return (Sharpe/Calmar) on EUR/USD 15-min. "
"XGBoost with moderate depth and strong regularisation to avoid "
"overfitting on noisy FX data. 2:1 TP:SL ratio with trend filter "
"on SMA-50 to avoid counter-trend noise. Session filter 06-20 UTC "
"covers London + NY overlap for best liquidity."
),
"notes": (
"Features: SMA cross distances, RSI overbought/oversold flags, "
"MACD histogram slope, Bollinger squeeze, ATR normalisation, "
"candle body/wick ratios, momentum returns at 1/2/4/8 bars, "
"session encoding via hour sin/cos. "
"Threshold 0.54 keeps precision high while capturing enough trades. "
"Reverse on opposite signal maximises capital utilisation."
),
}
|
||||||||||