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 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | import talib.abstract as ta from pandas import DataFrame from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter from freqtrade.persistence import Trade from datetime import datetime import logging logger = logging.getLogger(__name__) class GridStrategyV2(IStrategy): """ 专业自适应网格 v2 —— 高胜率版 相比 v1 的改进: 1. EMA 金叉 + EMA200 斜率确认:只在趋势明确向上时入场 2. RSI 中性区过滤:只在 30~55 入场(不追高、不在恐慌区买) 3. 成交量确认:成交量高于20日均量,信号更可靠 4. 棘轮止损:盈利超过 1×ATR 时止损拉到保本位(消灭大亏损) 5. DCA 最多 3 档(v1 是 4 档),减少深度套牢敞口 6. 目标:胜率 85%+,Sharpe > 1.2 """ INTERFACE_VERSION = 3 timeframe = "1h" can_short = False position_adjustment_enable = True max_entry_position_adjustment = 4 # 保留 4 档 DCA,给价格回归留足空间 minimal_roi = { "0": 0.10, "2880": 0.02, "5760": 0.005, } stoploss = -0.25 # 宽止损:让 DCA 有充足空间摊成本 trailing_stop = False # 由 custom_stoploss 接管 use_custom_stoploss = True startup_candle_count = 210 # EMA200 需要预热 # ── Hyperopt 参数 ──────────────────────────────────────────────── # 网格间距:缩小上限,高胜率策略用更窄的格 atr_mult = DecimalParameter(0.5, 2.0, default=1.0, decimals=1, space="buy") adx_max = IntParameter(20, 45, default=32, space="buy") bb_width_max= DecimalParameter(0.05, 0.25, default=0.15, decimals=2, space="buy") rsi_min = IntParameter(25, 45, default=35, space="buy") rsi_max = IntParameter(45, 68, default=58, space="buy") # 止盈更快:高胜率的核心 —— 小利润快速兑现,让更多交易在价格反转前出场 tp_atr_mult = DecimalParameter(0.3, 1.5, default=0.6, decimals=1, space="sell") # 棘轮触发更早:尽快把止损拉到保本,消灭潜在大亏 breakeven_atr = DecimalParameter(0.2, 1.0, default=0.4, decimals=1, space="sell") _atr_cache: dict = {} # ── 指标计算 ───────────────────────────────────────────────────── def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["atr"] = ta.ATR(dataframe, timeperiod=14) dataframe["adx"] = ta.ADX(dataframe, timeperiod=14) dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50) dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200) dataframe["vol_ma"] = dataframe["volume"].rolling(20).mean() upper, mid, lower = ta.BBANDS(dataframe["close"], timeperiod=20, nbdevup=2.0, nbdevdn=2.0) dataframe["bb_upper"] = upper dataframe["bb_mid"] = mid dataframe["bb_lower"] = lower dataframe["bb_width"] = (upper - lower) / mid # EMA200 斜率:当前值 vs 10根K线前的值 dataframe["ema200_slope"] = dataframe["ema200"] - dataframe["ema200"].shift(10) if len(dataframe) > 0: self._atr_cache[metadata["pair"]] = float(dataframe["atr"].iloc[-1]) return dataframe # ── 入场信号(比 v1 严格得多)──────────────────────────────────── def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe.loc[ ( # 大方向:价格 > EMA200,EMA50 > EMA200(金叉),EMA200 本身向上 (dataframe["close"] > dataframe["ema200"]) & (dataframe["ema50"] > dataframe["ema200"]) & (dataframe["ema200_slope"] > 0) & # 震荡市:ADX 低 + BB 宽度不过大 (dataframe["adx"] < self.adx_max.value) & (dataframe["bb_width"] < self.bb_width_max.value) & # RSI 中性区:不追高(< rsi_max),不在极度恐慌区(> rsi_min) (dataframe["rsi"] > self.rsi_min.value) & (dataframe["rsi"] < self.rsi_max.value) & # 入场位置:价格在 BB 中轨以下(不在上半区追入) (dataframe["close"] < dataframe["bb_mid"]) & # 成交量确认 (dataframe["volume"] > dataframe["vol_ma"] * 0.8) & (dataframe["volume"] > 0) ), "enter_long", ] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return dataframe # ── 棘轮止损 ───────────────────────────────────────────────────── def custom_stoploss(self, pair, trade, current_time: datetime, current_rate, current_profit, after_fill, **kwargs): """ 棘轮机制: - 亏损中:维持原始止损 (-20%) - 盈利超过 breakeven_atr × ATR:止损拉到 -0.5%(近似保本) - 盈利超过 2 × breakeven_atr × ATR:止损追踪到当前盈利的 -50% """ atr = self._atr_cache.get(pair) if atr is None or trade.open_rate == 0: return self.stoploss be_threshold = (self.breakeven_atr.value * atr) / trade.open_rate if current_profit >= be_threshold * 2: # 追踪止损:盈利超过2倍阈值,止损锁在当前盈利的一半以上 return max(-current_profit * 0.5, -0.005) elif current_profit >= be_threshold: # 已盈利超过1个ATR,止损拉到保本 return -0.005 else: return self.stoploss # 还在初期,维持原止损 # ── 动态止盈 ───────────────────────────────────────────────────── # OKX 现货手续费(实际费率,按档位调整这个值) FEE_RATE = 0.001 # 0.10% 单边(挂单价) def custom_exit(self, pair, trade, current_time, current_rate, current_profit, **kwargs): atr = self._atr_cache.get(pair) if atr is None or trade.open_rate == 0: return None filled = trade.nr_of_successful_entries # 动态最小止盈:必须覆盖所有入场手续费 + 出场手续费 + 0.2% 净利润缓冲 # current_profit 已由 Freqtrade 扣除手续费,这里额外加 0.2% 安全垫 # 确保每笔交易在任意 DCA 档位下都真正盈利 fee_buffer = self.FEE_RATE * filled + 0.002 # 已入场次数 × 入场费 + 净利缓冲 atr_target = (self.tp_atr_mult.value * atr) / trade.open_rate profit_target = max(atr_target, fee_buffer) if current_profit >= profit_target: return f"grid_tp_lvl{filled}_{current_profit:.3f}" return None # ── 网格加仓逻辑 ───────────────────────────────────────────────── def adjust_trade_entry( self, trade: Trade, current_time, current_rate: float, current_profit: float, min_stake, max_stake: float, current_entry_rate: float, current_exit_rate: float, current_entry_profit: float, current_exit_profit: float, **kwargs, ): filled = trade.nr_of_successful_entries if filled >= (self.max_entry_position_adjustment + 1): return None atr = self._atr_cache.get(trade.pair) if atr: grid_step = (self.atr_mult.value * atr) / current_rate grid_step = max(grid_step, 0.012) grid_step = min(grid_step, 0.08) else: grid_step = 0.03 threshold = -(grid_step * filled) if current_profit > threshold: return None # 仓位递增:1x → 1.5x → 2x → 2.5x multiplier = 1.0 + max(filled - 1, 0) * 0.5 stake = trade.stake_amount * multiplier stake = max(stake, min_stake or 0) stake = min(stake, max_stake) logger.info( f"[GridV2] {trade.pair} 第{filled}次加仓 " f"间距={grid_step:.2%} 当前={current_profit:.2%} 加仓={stake:.2f}U ({multiplier}x)" ) return stake |
Strategy League — fixed backtest that feeds the ranking
Export report Freqtrade logsRun finished · took 42.5s
ℹ️ This strategy uses custom_stoploss() — freqtrade only
re-checks these once per 1h candle by default, not against the price movement within it.
For a more accurate read, re-run this backtest locally with --timeframe-detail 1m
(or 5m — freqtrade's own docs use 5m detail for an hourly strategy as a lighter
alternative). Freqle doesn't do this for every check here: multiplying every
League/sweep backtest by a finer detail timeframe is more compute than the sandbox can sustain
across every indexed strategy. why this matters →
- profit isn't statistically significant (p=1.00) — hard to tell apart from luck
- only 0% of resampled runs were profitable
- profitable in only 0% of rolling 3-month windows
- did not beat simply holding the market
- very deep drawdown (-91%)
Resampling the trade sequence 2,000× shows the spread of results this edge could plausibly produce — separating a dependable strategy from one that got lucky once.
Loading charts…
Monthly breakdown
| Month | Regime | Trades | Profit % | Avg % | Win | Loss | Win % | DD % | Avg dur |
|---|---|---|---|---|---|---|---|---|---|
| Jun 2021 | bearish trending high vol | 37 | -4.21 | -1.14 | 3 | 34 | 8.1 | -90.91 | 3h 42m |
| May 2021 | bearish trending high vol | 84 | -12.71 | -1.51 | 8 | 76 | 9.5 | -86.81 | 14h 04m |
| Apr 2021 | bearish choppy high vol | 360 | -11.14 | -0.31 | 53 | 307 | 14.7 | -74.45 | 3h 31m |
| Mar 2021 | bullish choppy high vol | 304 | -4.71 | -0.16 | 77 | 227 | 25.3 | -63.58 | 6h 53m |
| Feb 2021 | bullish trending high vol | 776 | -28.58 | -0.37 | 116 | 660 | 14.9 | -59.0 | 2h 37m |
| Jan 2021 | bullish trending high vol | 811 | -29.32 | -0.36 | 143 | 668 | 17.6 | -31.26 | 4h 10m |
Trade charts — best 2 and worst 2 performing pairs (full OHLC candles are expensive to render for every pair)
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
1 potential lookahead pattern(s) found · 1 to review
| Line | Pattern | Detail | |
|---|---|---|---|
| 78 | leak | iloc_last | .iloc[-1] in populate_* applies the newest candle to all rows |
| 41 | review | startup_candles_too_small | startup_candle_count is 210, but EMA(timeperiod=200) needing 3x warmup needs at least 600 candles -- so the first 390+ 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.