MultiCoin_AI_Strategy
♡
Basics
mode: spot
timeframe: 5m
interface version: 3
1h
Settings
stoploss: -0.045
has minimal roi
protections
process only new candles
startup candle count: 240
Indicators
ADX
ATR
EMA
MACD
RSI
talib
Concepts
martingale
risk_management
Methods
populate_indicators_1h
15 related strategies (⧉ identical code, ≈ similar name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | # -*- coding: utf-8 -*- """Multi-coin spot long-only strategy (5m + 1h context). Targets: - BTC/USDT, ETH/USDT, SOL/USDT, XRP/USDT, DOGE/USDT Design constraints: - Spot long only, no short, no leverage. - No martingale / no infinite position add-ons. - No OpenAI API calls. - No manual trade-history reads. - Shared indicator logic across all pairs. """ from pandas import DataFrame from freqtrade.strategy import IStrategy, informative import talib.abstract as ta class MultiCoin_AI_Strategy(IStrategy): INTERFACE_VERSION = 3 timeframe = "5m" can_short = False startup_candle_count = 240 process_only_new_candles = True # Keep ROI-based exits available. minimal_roi = { "0": 0.032, "90": 0.020, "240": 0.012, "360": 0.0, } # Exits rely primarily on ROI/stoploss to avoid loss-heavy signal exits. use_exit_signal = False stoploss = -0.045 protections = [ { "method": "CooldownPeriod", "stop_duration_candles": 2, } ] @informative("1h") def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200) dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["ema_20"] = ta.EMA(dataframe, timeperiod=20) dataframe["ema_50"] = ta.EMA(dataframe, timeperiod=50) dataframe["ema_100"] = ta.EMA(dataframe, timeperiod=100) dataframe["ema_200"] = ta.EMA(dataframe, timeperiod=200) dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) dataframe["adx"] = ta.ADX(dataframe, timeperiod=14) dataframe["atr"] = ta.ATR(dataframe, timeperiod=14) dataframe["atr_pct"] = dataframe["atr"] / dataframe["close"] macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) dataframe["macd"] = macd["macd"] dataframe["macdsignal"] = macd["macdsignal"] return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: pair = metadata.get("pair", "") # Core gate: keep strong 1h bias and positive liquidity. base_guard = ( (dataframe["close_1h"] > dataframe["ema_200_1h"]) & (dataframe["rsi_1h"] > 48) & (dataframe["close"] > dataframe["ema_50"]) & (dataframe["ema_20"] > dataframe["ema_50"]) & (dataframe["rsi"] >= 42) & (dataframe["rsi"] <= 66) & (dataframe["adx"] > 15) & (dataframe["atr_pct"] > 0.0018) & (dataframe["atr_pct"] < 0.028) & (dataframe["macd"] > dataframe["macdsignal"]) & (dataframe["volume"] > 0) ) # Continuation setup: slightly looser to raise total trade count. continuation_entry = ( base_guard & (dataframe["close"] > dataframe["ema_100"]) & (dataframe["ema_50"] > dataframe["ema_100"]) & (dataframe["macd"] > 0) ) # Pullback-recovery setup: buy dip recoveries inside uptrend, avoiding weak tape. pullback_entry = ( base_guard & (dataframe["close"] > dataframe["ema_200"]) & (dataframe["close"] > (dataframe["ema_20"] * 0.995)) & (dataframe["rsi"] >= 44) & (dataframe["rsi"] <= 58) & (dataframe["adx"] > 17) & (dataframe["macd"] > (dataframe["macdsignal"] * 0.985)) ) entry_mask = continuation_entry | pullback_entry # ETH is the largest drag in recent runs, so gate entries with stricter # multi-timeframe trend confirmation to reduce low-quality trades. if "ETH/USDT" in pair: eth_filter = ( (dataframe["close_1h"] > dataframe["ema_200_1h"]) & (dataframe["rsi_1h"] > 50) & (dataframe["close"] > dataframe["ema_100"]) & (dataframe["ema_20"] > dataframe["ema_50"]) & (dataframe["rsi"] >= 46) & (dataframe["adx"] > 18) ) entry_mask = entry_mask & eth_filter # DOGE underperformed, so apply stricter filter to cut weak/flat entries. if "DOGE/USDT" in pair: doge_filter = ( (dataframe["close_1h"] > (dataframe["ema_200_1h"] * 1.002)) & (dataframe["rsi_1h"] > 52) & (dataframe["adx"] > 20) & (dataframe["atr_pct"] > 0.0022) & (dataframe["rsi"] >= 46) ) entry_mask = entry_mask & doge_filter dataframe.loc[entry_mask, "enter_long"] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( ( # Extreme trend break only (kept as fallback if use_exit_signal is enabled) (dataframe["close"] < dataframe["ema_200"]) | ((dataframe["close"] < dataframe["ema_100"]) & (dataframe["rsi"] < 35)) | ( (dataframe["ema_20"] < dataframe["ema_50"]) & (dataframe["ema_50"] < dataframe["ema_100"]) & (dataframe["macd"] < dataframe["macdsignal"]) ) ) & (dataframe["volume"] > 0) ), "exit_long", ] = 1 return dataframe |
Strategy League — fixed backtest that feeds the ranking
Failed — timed out after 1800s (strategy too complex/slow for the sandbox)
Backtests — over a market period
Backtest this strategy over a chosen crypto-cycle period. These don't affect the League ranking, and need that period's candle data downloaded.
Log in or sign up to run backtests.
| Period | Range | Total % | Win % | Max DD | Trades | |
|---|---|---|---|---|---|---|
| 2020 · DeFi Summer & Pre-Halving Rally | 20200101-20210101 | not run | ||||
| 2021 · Institutional Bull Market | 20210101-20220101 | not run | ||||
| 2022 · Post-Bull Crash & Macro Tightening | 20220101-20230101 | not run | ||||
| 2023–2024 · Recovery & ETF Anticipation | 20230101-20250101 | not run | ||||
| 2025–2026 · Current Cycle | 20250101-20260101 | not run | ||||
Walk forward
Out-of-sample backtest on recent data · 33 pairs · 20260101-20260701.
Backtest trust check
no lookahead patterns · 1 thing(s) worth reviewing before trusting the numbers
| Line | Pattern | Detail | |
|---|---|---|---|
| 25 | review | startup_candles_too_small | startup_candle_count is 240, but EMA(timeperiod=200) needing 3x warmup needs at least 600 candles -- so the first 360+ candles of every backtest use an indicator that hasn't warmed up. Recursive indicators (EMA/RSI/ADX/ATR) want several times their period, not exactly it |
ran by Ron · took s
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.