8 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 | from freqtrade.strategy import IStrategy import pandas as pd import numpy as np import talib from scipy.fft import fft from typing import Dict, List class HurstCycle3(IStrategy): timeframe = '15m' minimal_roi = {"0": 0.05, "60": 0.03, "120": 0.01} stoploss = -0.04 trailing_stop = True trailing_stop_positive = 0.015 trailing_stop_positive_offset = 0.02 # Hyperparameters to optimize base_cycle_period = 20 envelope_factor_short = 1.2 envelope_factor_long = 2.0 filter_weights = [1, 2, 3, 2, 1] # Symmetric weights for smoothing (p. 177) def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame: # Spectral Analysis close = dataframe['close'].values n = min(len(close), 500) yf = fft(close[-n:]) freq = np.fft.fftfreq(n) power = np.abs(yf) ** 2 dominant_idx = np.argmax(power[1:n//2]) + 1 dominant_period = int(1 / freq[dominant_idx]) if freq[dominant_idx] != 0 else self.base_cycle_period self.short_cycle = max(10, min(30, dominant_period)) self.long_cycle = self.short_cycle * 2 # Numerical Filter weights = np.array(self.filter_weights) / sum(self.filter_weights) filtered = np.convolve(close, weights, mode='valid') dataframe['filtered_close'] = pd.Series(filtered, index=dataframe.index[-len(filtered):]) # FLD with shift and extrapolation half_short = int(self.short_cycle / 2) half_long = int(self.long_cycle / 2) fld_short_base = dataframe['filtered_close'].rolling(window=self.short_cycle, center=True).mean() fld_long_base = dataframe['filtered_close'].rolling(window=self.long_cycle, center=True).mean() dataframe['fld_short'] = fld_short_base.shift(-half_short) dataframe['fld_long'] = fld_long_base.shift(-half_short) for i in range(1, half_short + 1): if len(dataframe) > half_short + 1: # Ensure enough data dataframe['fld_short'].iloc[-i] = dataframe['fld_short'].iloc[-half_short-1] + \ (dataframe['fld_short'].iloc[-half_short-1] - dataframe['fld_short'].iloc[-half_short-2]) dataframe['fld_long'].iloc[-i] = dataframe['fld_long'].iloc[-half_short-1] + \ (dataframe['fld_long'].iloc[-half_short-1] - dataframe['fld_long'].iloc[-half_short-2]) # [Rest of indicators: envelopes, VTLs, trend...] dataframe['sma_short'] = talib.SMA(dataframe['filtered_close'], timeperiod=self.short_cycle) dataframe['sma_long'] = talib.SMA(dataframe['filtered_close'], timeperiod=self.long_cycle) dataframe['atr_short'] = talib.ATR(dataframe['high'], dataframe['low'], dataframe['filtered_close'], timeperiod=self.short_cycle) dataframe['atr_long'] = talib.ATR(dataframe['high'], dataframe['low'], dataframe['filtered_close'], timeperiod=self.long_cycle) dataframe['upper_env_short'] = dataframe['sma_short'] + self.envelope_factor_short * dataframe['atr_short'] dataframe['lower_env_short'] = dataframe['sma_short'] - self.envelope_factor_short * dataframe['atr_short'] dataframe['upper_env_long'] = dataframe['sma_long'] + self.envelope_factor_long * dataframe['atr_long'] dataframe['lower_env_long'] = dataframe['sma_long'] - self.envelope_factor_long * dataframe['atr_long'] dataframe['trough'] = dataframe['filtered_close'].rolling(self.short_cycle).min() dataframe['crest'] = dataframe['filtered_close'].rolling(self.short_cycle).max() dataframe['is_trough'] = np.where(dataframe['filtered_close'] == dataframe['trough'], 1, 0) dataframe['is_crest'] = np.where(dataframe['filtered_close'] == dataframe['crest'], 1, 0) troughs = dataframe[dataframe['is_trough'] == 1].index crests = dataframe[dataframe['is_crest'] == 1].index if len(troughs) >= 2: t1, t2 = troughs[-2], troughs[-1] dataframe['vtl_up_slope'] = (dataframe['filtered_close'][t2] - dataframe['filtered_close'][t1]) / (t2 - t1) if len(crests) >= 2: c1, c2 = crests[-2], crests[-1] dataframe['vtl_down_slope'] = (dataframe['filtered_close'][c2] - dataframe['filtered_close'][c1]) / (c2 - c1) dataframe['trend'] = np.where(dataframe['filtered_close'] > dataframe['sma_long'], 1, -1) return dataframe def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame: # Long Entry: Trough, below FLD, short envelope breach, VTL breakout dataframe.loc[ # (dataframe['is_trough'] == 1) & (dataframe['filtered_close'] < dataframe['fld_short']) & (dataframe['filtered_close'].shift() > dataframe['fld_short'].shift()), # (dataframe['trend'] == 1), 'enter_long'] = 1 # # Short Entry: Crest, above FLD, short envelope breach, VTL breakout # dataframe.loc[ # (dataframe['is_crest'] == 1) & # (dataframe['filtered_close'] > dataframe['fld_short']) & # (dataframe['filtered_close'] > dataframe['upper_env_short']) & # (dataframe['trend'] == -1) & # (dataframe['vtl_down_slope'].notna() & (dataframe['vtl_down_slope'] < 0)), # 'enter_short'] = 1 return dataframe def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame: # Exit Long: Hit FLD or upper long envelope dataframe.loc[ (dataframe['filtered_close'] > dataframe['fld_short']) & (dataframe['filtered_close'].shift() < dataframe['fld_short'].shift()), 'exit_long'] = 1 # # Exit Short: Hit FLD or lower long envelope # dataframe.loc[ # (dataframe['enter_short'].shift(1) == 1) & # ((dataframe['filtered_close'] <= dataframe['fld_long']) | # (dataframe['filtered_close'] <= dataframe['lower_env_long'])), # 'exit_short'] = 1 return dataframe def leverage(self, pair: str, current_time, current_rate: float, proposed_leverage: float, max_leverage: float, side: str, **kwargs) -> float: return 3.0 |
Strategy League — fixed backtest that feeds the ranking
Export report Freqtrade logsRun finished · took 250.4s
ℹ️ This strategy uses a trailing stop — freqtrade only
re-checks these once per 15m candle by default, not against the price movement within it.
For a more accurate read, re-run this backtest locally with --timeframe-detail 1m. 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 →
- statistically significant edge (p=0.00)
- 100% of resampled runs stayed profitable
- profitable across 100% of rolling 3-month windows
- comfortably beat buy-and-hold
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 |
|---|---|---|---|---|---|---|---|---|---|
| Jan 2026 | bullish trending low vol | 2 | +0.04 | 0.18 | 2 | 0 | 100.0 | 0.0 | 8h 38m |
| Dec 2025 | bearish trending low vol | 1318 | +74.10 | 0.56 | 1116 | 202 | 84.7 | -0.01 | 3h 03m |
| Nov 2025 | bearish trending high vol | 1404 | +69.10 | 0.49 | 1147 | 257 | 81.7 | -0.15 | 2h 26m |
| Oct 2025 | bearish trending low vol | 1281 | +73.12 | 0.57 | 1113 | 168 | 86.9 | -0.25 | 2h 47m |
| Sep 2025 | bullish choppy low vol | 1217 | +63.58 | 0.52 | 1013 | 204 | 83.2 | -0.01 | 3h 05m |
| Aug 2025 | bullish choppy low vol | 1291 | +86.68 | 0.67 | 1145 | 146 | 88.7 | -0.0 | 2h 57m |
| Jul 2025 | bullish choppy low vol | 1307 | +90.27 | 0.69 | 1151 | 156 | 88.1 | -0.03 | 2h 52m |
| Jun 2025 | bearish choppy low vol | 1251 | +70.97 | 0.57 | 1048 | 203 | 83.8 | -0.06 | 3h 02m |
| May 2025 | bullish trending low vol | 1291 | +87.60 | 0.68 | 1144 | 147 | 88.6 | -0.03 | 2h 43m |
| Apr 2025 | bullish choppy low vol | 1294 | +83.91 | 0.65 | 1120 | 174 | 86.6 | -0.04 | 2h 49m |
| Mar 2025 | bearish trending high vol | 1348 | +90.81 | 0.67 | 1181 | 167 | 87.6 | -0.11 | 2h 38m |
| Feb 2025 | bearish trending low vol | 1217 | +85.09 | 0.70 | 1070 | 147 | 87.9 | -0.06 | 2h 19m |
| Jan 2025 | bearish choppy low vol | 1343 | +93.70 | 0.70 | 1184 | 159 | 88.2 | -0.05 | 2h 37m |
| Dec 2024 | bullish trending low vol | 1475 | +81.15 | 0.55 | 1242 | 233 | 84.2 | -0.19 | 2h 11m |
| Nov 2024 | bullish trending low vol | 1476 | +95.54 | 0.65 | 1260 | 216 | 85.4 | -0.06 | 2h 19m |
| Oct 2024 | bullish choppy low vol | 1397 | +79.61 | 0.57 | 1180 | 217 | 84.5 | -0.01 | 2h 56m |
| Sep 2024 | bearish choppy low vol | 1280 | +78.14 | 0.61 | 1123 | 157 | 87.7 | -0.05 | 3h 04m |
| Aug 2024 | bearish choppy high vol | 1360 | +75.00 | 0.55 | 1124 | 236 | 82.6 | -0.19 | 2h 46m |
| Jul 2024 | bearish trending low vol | 1389 | +81.96 | 0.59 | 1170 | 219 | 84.2 | -0.07 | 2h 46m |
| Jun 2024 | bearish choppy low vol | 1329 | +68.66 | 0.52 | 1104 | 225 | 83.1 | -0.06 | 3h 02m |
| May 2024 | bullish choppy high vol | 1423 | +80.84 | 0.57 | 1194 | 229 | 83.9 | -0.09 | 2h 58m |
| Apr 2024 | bearish choppy high vol | 1346 | +72.54 | 0.54 | 1147 | 199 | 85.2 | -0.3 | 2h 33m |
| Mar 2024 | bullish trending high vol | 1618 | +91.10 | 0.56 | 1338 | 280 | 82.7 | -0.22 | 2h 23m |
| Feb 2024 | bullish trending low vol | 1340 | +81.92 | 0.61 | 1161 | 179 | 86.6 | -0.04 | 3h 04m |
| Jan 2024 | bearish choppy high vol | 1470 | +85.18 | 0.58 | 1238 | 232 | 84.2 | -0.08 | 2h 37m |
| Dec 2023 | bullish trending low vol | 1507 | +97.37 | 0.65 | 1300 | 207 | 86.3 | -0.09 | 2h 41m |
| Nov 2023 | bullish trending low vol | 1376 | +83.33 | 0.61 | 1180 | 196 | 85.8 | -0.08 | 2h 46m |
| Oct 2023 | bullish trending low vol | 1403 | +71.93 | 0.51 | 1169 | 234 | 83.3 | -0.02 | 3h 15m |
| Sep 2023 | bearish choppy low vol | 1352 | +62.19 | 0.46 | 1087 | 265 | 80.4 | -0.01 | 3h 22m |
| Aug 2023 | bearish choppy low vol | 1412 | +62.07 | 0.44 | 1098 | 314 | 77.8 | -0.03 | 3h 04m |
| Jul 2023 | bullish trending low vol | 1468 | +77.25 | 0.53 | 1225 | 243 | 83.4 | -0.03 | 2h 59m |
| Jun 2023 | bullish trending low vol | 1305 | +71.88 | 0.55 | 1107 | 198 | 84.8 | -0.04 | 3h 05m |
| May 2023 | bearish choppy low vol | 1301 | +57.17 | 0.44 | 1018 | 283 | 78.2 | -0.01 | 3h 15m |
| Apr 2023 | bullish trending low vol | 1279 | +68.15 | 0.53 | 1071 | 208 | 83.7 | -0.02 | 3h 03m |
| Mar 2023 | bullish trending high vol | 1336 | +83.24 | 0.62 | 1140 | 196 | 85.3 | -0.05 | 2h 42m |
| Feb 2023 | bullish trending low vol | 1178 | +75.80 | 0.64 | 1024 | 154 | 86.9 | -0.03 | 2h 55m |
| Jan 2023 | bullish trending low vol | 1410 | +84.37 | 0.60 | 1186 | 224 | 84.1 | -0.06 | 2h 49m |
| Dec 2022 | bearish trending low vol | 1307 | +62.42 | 0.48 | 1060 | 247 | 81.1 | -0.02 | 3h 13m |
| Nov 2022 | bearish trending high vol | 1346 | +79.13 | 0.59 | 1130 | 216 | 84.0 | -0.24 | 2h 28m |
| Oct 2022 | bullish choppy low vol | 1370 | +69.33 | 0.51 | 1103 | 267 | 80.5 | -0.16 | 2h 55m |
| Sep 2022 | bearish choppy high vol | 1331 | +87.16 | 0.65 | 1139 | 192 | 85.6 | -0.02 | 2h 40m |
| Aug 2022 | bullish choppy high vol | 1306 | +91.00 | 0.70 | 1154 | 152 | 88.4 | -0.05 | 2h 36m |
| Jul 2022 | bearish trending high vol | 1316 | +97.29 | 0.74 | 1159 | 157 | 88.1 | -0.04 | 2h 26m |
| Jun 2022 | bearish trending high vol | 1376 | +79.56 | 0.58 | 1138 | 238 | 82.7 | -0.43 | 2h 00m |
| May 2022 | bearish trending high vol | 1403 | +77.64 | 0.55 | 1120 | 283 | 79.8 | -0.89 | 1h 58m |
| Apr 2022 | bearish choppy high vol | 1162 | +73.95 | 0.64 | 995 | 167 | 85.6 | -0.03 | 2h 44m |
| Mar 2022 | bullish choppy high vol | 1377 | +91.71 | 0.67 | 1206 | 171 | 87.6 | -0.07 | 2h 39m |
| Feb 2022 | bearish trending high vol | 1122 | +79.76 | 0.71 | 976 | 146 | 87.0 | -0.12 | 2h 33m |
| Jan 2022 | bearish trending high vol | 1290 | +73.22 | 0.57 | 1085 | 205 | 84.1 | -0.76 | 2h 27m |
| Dec 2021 | bearish trending high vol | 1406 | +87.94 | 0.63 | 1169 | 237 | 83.1 | -0.45 | 2h 17m |
| Nov 2021 | bullish trending high vol | 1367 | +86.59 | 0.63 | 1165 | 202 | 85.2 | -0.33 | 2h 25m |
| Oct 2021 | bullish trending high vol | 1448 | +101.42 | 0.70 | 1281 | 167 | 88.5 | -0.16 | 2h 29m |
| Sep 2021 | bearish trending high vol | 1451 | +72.63 | 0.50 | 1181 | 270 | 81.4 | -1.31 | 1h 59m |
| Aug 2021 | bullish trending high vol | 1417 | +98.31 | 0.69 | 1221 | 196 | 86.2 | -0.6 | 2h 08m |
| Jul 2021 | bearish trending high vol | 1238 | +100.24 | 0.81 | 1111 | 127 | 89.7 | -0.11 | 2h 23m |
| Jun 2021 | bearish trending high vol | 1320 | +71.41 | 0.54 | 1075 | 245 | 81.4 | -6.72 | 1h 48m |
| May 2021 | bearish trending high vol | 1640 | +13.73 | 0.08 | 1060 | 580 | 64.6 | -9.83 | 1h 11m |
| Apr 2021 | bearish choppy high vol | 1699 | +81.08 | 0.48 | 1338 | 361 | 78.8 | -1.89 | 1h 48m |
| Mar 2021 | bullish choppy high vol | 1500 | +105.19 | 0.70 | 1307 | 193 | 87.1 | -9.72 | 2h 07m |
| Feb 2021 | bullish trending high vol | 1599 | +30.46 | 0.19 | 1137 | 462 | 71.1 | -10.4 | 1h 34m |
| Jan 2021 | bullish trending high vol | 1756 | +37.83 | 0.22 | 1235 | 521 | 70.3 | -12.78 | 1h 31m |
Yearly breakdown
| Year | Trades | Profit % | Avg % | Win | Loss | Win % | DD % | Avg dur |
|---|---|---|---|---|---|---|---|---|
| 2026 | 2 | +0.04 | 0.18 | 2 | 0 | 100.0 | 0.0 | 8h 38m |
| 2025 | 15562 | +968.93 | 0.62 | 13432 | 2130 | 86.3 | -0.25 | 2h 46m |
| 2024 | 16903 | +971.64 | 0.58 | 14281 | 2622 | 84.5 | -0.3 | 2h 42m |
| 2023 | 16327 | +894.75 | 0.55 | 13605 | 2722 | 83.3 | -0.09 | 2h 59m |
| 2022 | 15706 | +962.17 | 0.61 | 13265 | 2441 | 84.5 | -0.89 | 2h 33m |
| 2021 | 17841 | +886.83 | 0.50 | 14280 | 3561 | 80.0 | -12.78 | 1h 57m |
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
2 potential lookahead pattern(s) found · 1 to review
| Line | Pattern | Detail | |
|---|---|---|---|
| 42 | leak | center_rolling | rolling(center=True) window includes future values |
| 43 | |||
| 8 | review | missing_startup_candles | uses recursive indicators (ATR) but startup_candle_count is not set (default 0). Their value at a bar depends on all bars before it, so freqtrade trims no warmup and the backtest opens with unwarmed values that can't occur live. Set it to a few times the longest period and confirm with `freqtrade recursive-analysis` |
ran by Doge · took s
Lookahead analysis
Freqtrade logsFailed — lookahead analysis couldn't run: this strategy's indicators change shape with the amount of history (e.g. centered/rolling or dynamically-built columns) — freqtrade's analyzer can't diff them. See the Bias check, which flags these patterns directly.
instance.start(progress)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/freqtrade/freqtrade/optimize/analysis/lookahead.py", line 255, in start
self.analyze_row(idx, result_row)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/freqtrade/freqtrade/optimize/analysis/lookahead.py", line 201, in analyze_row
self.analyze_indicators(self.full_varHolder, self.entry_varHolders[idx], result_row["pair"])
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/freqtrade/freqtrade/optimize/analysis/lookahead.py", line 76, in analyze_indicators
compare_df = cut_full_df.compare(cut_df)
File "/home/ftuser/.local/lib/python3.14/site-packages/pandas/core/frame.py", line 10170, in compare
return super().compare(
~~~~~~~~~~~~~~~^
other=other,
^^^^^^^^^^^^
...<3 lines>...
result_names=result_names,
^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/ftuser/.local/lib/python3.14/site-packages/pandas/core/generic.py", line 9638, in compare
mask = ~((self == other) | (self.isna() & other.isna())) # type: ignore[operator]
^^^^^^^^^^^^^
File "/home/ftuser/.local/lib/python3.14/site-packages/pandas/core/ops/common.py", line 85, in new_method
return method(self, other)
File "/home/ftuser/.local/lib/python3.14/site-packages/pandas/core/arraylike.py", line 42, in __eq__
return self._cmp_method(other, operator.eq)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/home/ftuser/.local/lib/python3.14/site-packages/pandas/core/frame.py", line 9135, in _cmp_method
self, other = self._align_for_op(other, axis, flex=False, level=None)
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/ftuser/.local/lib/python3.14/site-packages/pandas/core/frame.py", line 9449, in _align_for_op
raise ValueError(
...<2 lines>...
)
ValueError: Can only compare identically-labeled (both index and columns) DataFrame objects