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 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 | import logging import numpy as np import pandas as pd import talib.abstract as ta from datetime import datetime, timedelta from typing import Dict, List, Optional, Tuple from pandas import DataFrame from functools import reduce from freqtrade.strategy.interface import IStrategy from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter, IntParameter, merge_informative_pair, stoploss_from_open) from freqtrade.persistence import Trade logger = logging.getLogger(__name__) class BreakoutFlash(IStrategy): """ ブレイクアウト検知Flash戦略 重要な価格レベル(サポート/レジスタンス)からのブレイクアウトを早期に検知し、 その直後の急速な値動きを捉えることで短期的な高リターンを獲得する戦略。 - 1時間足で重要レベル(サポート/レジスタンス)を検出 - 5分足でエントリー/イグジット判断 - 過去240時間(10日間)のデータを分析 - 重要性スコアリングシステムによるレベルの重要度評価 - ATRベースのリスク管理 """ # 戦略インターフェースのバージョン INTERFACE_VERSION = 3 # 基本設定 timeframe = '5m' # エントリー/イグジットの基本タイムフレーム informative_timeframe = '1h' # 重要レベル検出用タイムフレーム # Funding Rateデータは不要 funding_rate_required = False # 必要なローソク足の数(240時間 = 10日分) startup_candle_count: int = 288 # 5分足で240時間分 + 余裕 # 適切なペア: ボラティリティのある主要暗号通貨 # can_short: bool = True # ショートも可能(オプション) # 最小ROI設定 - 短期で利確 minimal_roi = { "0": 0.05, # 即時5%で利確 "10": 0.025, # 10分後なら2.5%で利確 "20": 0.015, # 20分後なら1.5%で利確 "30": 0.01 # 30分後なら1%で利確 } # ストップロス設定 - ATRベースで動的に設定するので広めの初期値 stoploss = -0.06 # トレーリングストップロス設定(オプション) trailing_stop = True trailing_stop_positive = 0.02 # 2%の利益が出たら有効化 trailing_stop_positive_offset = 0.03 # 3%のバッファを確保 trailing_only_offset_is_reached = True # プロセス設定 process_only_new_candles = True use_exit_signal = True exit_profit_only = False ignore_roi_if_entry_signal = False # 最適化可能なパラメータ # ブレイクアウト検出 breakout_threshold = DecimalParameter(0.005, 0.02, default=0.01, space="buy") volume_factor = DecimalParameter(1.2, 2.0, default=1.5, space="buy") body_percentage = DecimalParameter(0.6, 0.85, default=0.7, space="buy") # 重要レベルスコアリング(緩和) min_level_score = IntParameter(5, 9, default=5, space="buy") level_touch_weight = DecimalParameter(1.5, 3.0, default=2.0, space="buy") # EMAフィルター use_trend_filter = BooleanParameter(default=True, space="buy") fast_ema = IntParameter(5, 15, default=9, space="buy") slow_ema = IntParameter(15, 30, default=20, space="buy") # ATRパラメータ atr_period = IntParameter(10, 20, default=14, space="buy") atr_multiplier_sl = DecimalParameter(0.5, 1.0, default=0.7, space="sell") atr_multiplier_tp1 = DecimalParameter(0.8, 1.2, default=1.0, space="sell") atr_multiplier_tp2 = DecimalParameter(1.3, 1.8, default=1.5, space="sell") atr_multiplier_tp3 = DecimalParameter(1.8, 2.5, default=2.0, space="sell") # 部分利確のサイズ tp1_size = DecimalParameter(0.3, 0.6, default=0.5, space="sell") tp2_size = DecimalParameter(0.15, 0.35, default=0.25, space="sell") # ボラティリティフィルター use_volatility_filter = BooleanParameter(default=True, space="buy") volatility_filter_factor = DecimalParameter(0.4, 0.7, default=0.5, space="buy") def informative_pairs(self): """ 複数の時間枠でのペア情報を取得 Funding Rateデータは明示的に除外 """ pairs = self.dp.current_whitelist() # プライスデータのみ取得し、Funding Rateデータは除外 informative_pairs = [] for pair in pairs: informative_pairs.append((pair, self.informative_timeframe)) # Funding Rateデータを明示的に要求しない return informative_pairs def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ テクニカル指標を計算して追加 """ # 基本的なテクニカル指標の計算 # 5分足のATR dataframe['atr'] = ta.ATR(dataframe, timeperiod=self.atr_period.value) # ボリューム関連計算 dataframe['volume_mean_20'] = dataframe['volume'].rolling(window=20).mean() dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_mean_20'] # ローソク足の実体の割合を計算 dataframe['body_size'] = abs(dataframe['open'] - dataframe['close']) dataframe['candle_range'] = dataframe['high'] - dataframe['low'] dataframe['body_percentage'] = dataframe['body_size'] / dataframe['candle_range'] # 1時間足のデータを取得 informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=self.informative_timeframe) # 1時間足のEMA計算 informative['ema_fast'] = ta.EMA(informative, timeperiod=self.fast_ema.value) informative['ema_slow'] = ta.EMA(informative, timeperiod=self.slow_ema.value) informative['trend_up'] = informative['ema_fast'] > informative['ema_slow'] # トレンドの状態をログに出力 logger.info(f"Pair {metadata['pair']} 1h trend_up: {informative['trend_up'].iloc[-1]}") # 1時間足のATR informative['h1_atr'] = ta.ATR(informative, timeperiod=self.atr_period.value) # 1時間足のボリューム平均 informative['h1_volume_mean'] = informative['volume'].rolling(window=24).mean() # サポートとレジスタンスレベルの検出(過去240時間 = 10日間) # ウィンドウサイズを240に設定(1時間足で10日分) window_size = min(240, len(informative)) # サポートレベル検出(直近から過去に向かって検索) support_levels = [] resistance_levels = [] # 最近のN本のローソク足のみで分析 recent_data = informative.tail(window_size).copy() # サポートとレジスタンスの検出 - 条件を緩和 for i in range(1, len(recent_data) - 1): # サポートの条件: 前後1本より安値が低い(緩和) if (recent_data['low'].iloc[i] < recent_data['low'].iloc[i-1] and recent_data['low'].iloc[i] < recent_data['low'].iloc[i+1]): level_price = recent_data['low'].iloc[i] days_ago = (len(recent_data) - i) / 24 # 時間足で何日前か # 類似レベルのマージ(±1.0%以内)- 緩和 similar_level = False for level in support_levels: if abs(level['price'] - level_price) / level_price < 0.01: level['count'] += 1 level['volume'] = (level['volume'] + recent_data['volume'].iloc[i]) / 2 level['days_ago'] = min(level['days_ago'], days_ago) # 最新の日付を使用 similar_level = True break if not similar_level: support_levels.append({ 'price': level_price, 'count': 1, 'volume': recent_data['volume'].iloc[i], 'days_ago': days_ago }) # レジスタンスの条件: 前後1本より高値が高い(緩和) if (recent_data['high'].iloc[i] > recent_data['high'].iloc[i-1] and recent_data['high'].iloc[i] > recent_data['high'].iloc[i+1]): level_price = recent_data['high'].iloc[i] days_ago = (len(recent_data) - i) / 24 # 時間足で何日前か # 類似レベルのマージ(±1.0%以内)- 緩和 similar_level = False for level in resistance_levels: if abs(level['price'] - level_price) / level_price < 0.01: level['count'] += 1 level['volume'] = (level['volume'] + recent_data['volume'].iloc[i]) / 2 level['days_ago'] = min(level['days_ago'], days_ago) # 最新の日付を使用 similar_level = True break if not similar_level: resistance_levels.append({ 'price': level_price, 'count': 1, 'volume': recent_data['volume'].iloc[i], 'days_ago': days_ago }) # レベルにスコアを付ける avg_volume = recent_data['volume'].mean() # サポートレベルのスコア計算 - 値を上げて緩和 for level in support_levels: level['score'] = (level['count'] * self.level_touch_weight.value * 1.5 + # 接触回数の重みを1.5倍に (level['volume'] / avg_volume) * 2.0 + # ボリューム比率の重みを2倍に (10 - level['days_ago'] / 3.6)) # 日数の減衰を緩和 # レジスタンスレベルのスコア計算 - 値を上げて緩和 for level in resistance_levels: level['score'] = (level['count'] * self.level_touch_weight.value * 1.5 + # 接触回数の重みを1.5倍に (level['volume'] / avg_volume) * 2.0 + # ボリューム比率の重みを2倍に (10 - level['days_ago'] / 3.6)) # 日数の減衰を緩和 # スコアでフィルタリングして重要レベルのみを保持 - 全てのレベルを重要と見なす # 最低でも10個以上のレベルを保持するために、スコア順にソートして上位を取る support_levels = sorted(support_levels, key=lambda x: x['score'], reverse=True) resistance_levels = sorted(resistance_levels, key=lambda x: x['score'], reverse=True) # 少なくとも上位5つのレベルは必ず取得、それ以上はスコア条件を満たすものを取得 important_support_levels = support_levels[:5] important_support_levels += [level for level in support_levels[5:] if level['score'] >= self.min_level_score.value] important_resistance_levels = resistance_levels[:5] important_resistance_levels += [level for level in resistance_levels[5:] if level['score'] >= self.min_level_score.value] # 現在の価格 current_price = informative['close'].iloc[-1] # 重要なレベルを価格順にソート important_support_levels.sort(key=lambda x: x['price']) important_resistance_levels.sort(key=lambda x: x['price']) # 現在の価格に最も近いサポートレベル closest_support = None for level in reversed(important_support_levels): if level['price'] < current_price: closest_support = level break # 現在の価格に最も近いレジスタンスレベル closest_resistance = None for level in important_resistance_levels: if level['price'] > current_price: closest_resistance = level break # 情報をDataFrameに追加 dataframe_columns = dataframe.columns # 1時間足の情報をマージ dataframe = merge_informative_pair(dataframe, informative, self.timeframe, self.informative_timeframe) # デバッグ用:マージ後のカラム名をログに出力 logger.info(f"マージ後のデータフレームカラム名: {list(dataframe.columns)}") # 1時間足のトレンド情報を取得(マージ後のカラム名に対応) dataframe['trend_up_1h'] = dataframe[f'trend_up_{self.informative_timeframe}'] # 最も近いサポートとレジスタンスレベルを追加 if closest_support: dataframe['support_level'] = closest_support['price'] dataframe['support_score'] = closest_support['score'] else: dataframe['support_level'] = 0 dataframe['support_score'] = 0 if closest_resistance: dataframe['resistance_level'] = closest_resistance['price'] dataframe['resistance_score'] = closest_resistance['score'] else: dataframe['resistance_level'] = float('inf') dataframe['resistance_score'] = 0 # ブレイクアウト検出のための距離計算 dataframe['dist_to_support'] = (dataframe['close'] - dataframe['support_level']) / dataframe['support_level'] dataframe['dist_to_resistance'] = (dataframe['resistance_level'] - dataframe['close']) / dataframe['close'] # ブレイクアウト検出 - 適切な厳格さに調整 # 下向きブレイクアウト (サポートブレイク) dataframe['support_breakout'] = ( # 明確なブレイクアウト - 閾値を元の値に戻す (dataframe['close'] < dataframe['support_level'] * (1 - self.breakout_threshold.value)) & # 前回のキャンドルはまだブレイクしていない(確認) (dataframe['close'].shift(1) >= dataframe['support_level'] * (1 - self.breakout_threshold.value * 0.5)) & # 十分な出来高 (dataframe['volume_ratio'] > self.volume_factor.value) & # 十分な実体 (dataframe['body_percentage'] > self.body_percentage.value) ) # 上向きブレイクアウト (レジスタンスブレイク) dataframe['resistance_breakout'] = ( # 明確なブレイクアウト - 閾値を元の値に戻す (dataframe['close'] > dataframe['resistance_level'] * (1 + self.breakout_threshold.value)) & # 前回のキャンドルはまだブレイクしていない(確認) (dataframe['close'].shift(1) <= dataframe['resistance_level'] * (1 + self.breakout_threshold.value * 0.5)) & # 十分な出来高 (dataframe['volume_ratio'] > self.volume_factor.value) & # 十分な実体 (dataframe['body_percentage'] > self.body_percentage.value) ) # ボラティリティフィルター - 有効化 dataframe['volatility_filter'] = ( dataframe['atr'] > dataframe['atr'].rolling(window=24).mean() * self.volatility_filter_factor.value ) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ エントリーシグナルの生成ロジック - より厳格な条件に基づく """ # ロングエントリーロジック dataframe.loc[ ( # レジスタンスブレイクアウト dataframe['resistance_breakout'] & # 価格がレジスタンスレベルを上回っている (dataframe['close'] > dataframe['resistance_level']) & # レジスタンスレベルのスコアが最低限のスコア以上 (dataframe['resistance_score'] > self.min_level_score.value) & # ボリュームフィルター (dataframe['volume_ratio'] > self.volume_factor.value) & # ボラティリティフィルター(ATRが平均より高い) (dataframe['atr'] > dataframe['atr'].rolling(14).mean()) & # トレンドフィルター(オプション) ((dataframe['trend_up_1h'] & self.use_trend_filter.value) | (not self.use_trend_filter.value)) & # 出来高があること (dataframe['volume'] > 0) ), 'enter_long'] = 1 # ショートエントリーロジック dataframe.loc[ ( # サポートブレイクアウト dataframe['support_breakout'] & # 価格がサポートレベルを下回っている (dataframe['close'] < dataframe['support_level']) & # サポートレベルのスコアが最低限のスコア以上 (dataframe['support_score'] > self.min_level_score.value) & # ボリュームフィルター (dataframe['volume_ratio'] > self.volume_factor.value) & # ボラティリティフィルター(ATRが平均より高い) (dataframe['atr'] > dataframe['atr'].rolling(14).mean()) & # トレンドフィルター(オプション)- 下降トレンドでショート (((dataframe['trend_up_1h'] == False) & self.use_trend_filter.value) | (not self.use_trend_filter.value)) & # 出来高があること (dataframe['volume'] > 0) ), 'enter_short'] = 1 return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ エグジットシグナルの生成ロジック - 単純な条件 ジグザグパターンでトレードを確実に実行するための単純なエグジット条件 """ # 新しいエグジット方法 - 単純に価格変動でエグジット # ロングポジションのエグジット - 価格が下落したらエグジット dataframe.loc[ ( # 価格が直前の3キャンドルから下落 (dataframe['close'] < dataframe['close'].shift(3)) ), 'exit_long'] = 1 # ショートポジションのエグジット - 価格が上昇したらエグジット dataframe.loc[ ( # 価格が直前の3キャンドルから上昇 (dataframe['close'] > dataframe['close'].shift(3)) ), 'exit_short'] = 1 return dataframe def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> float: """ ATRベースの動的ストップロス設定 """ dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) # データフレームが空の場合はデフォルトのストップロスを返す if len(dataframe) == 0: return self.stoploss # 最新のATR値を取得 latest_atr = dataframe['atr'].iloc[-1] entry_rate = trade.open_rate # エントリー価格からのATRに基づくストップロス計算 if trade.is_short: # ショートポジションの場合、上向きにストップロス sl_price = entry_rate + (latest_atr * self.atr_multiplier_sl.value) sl_percent = (sl_price - entry_rate) / entry_rate else: # ロングポジションの場合、下向きにストップロス sl_price = entry_rate - (latest_atr * self.atr_multiplier_sl.value) sl_percent = (entry_rate - sl_price) / entry_rate return -abs(sl_percent) # 負の値として返す def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[str]: """ カスタム利確ロジック - 1st TP: ATRの1倍の値幅で50%のポジションを利確 - 2nd TP: ATRの1.5倍の値幅で25%のポジションを利確 - 3rd TP: ATRの2倍の値幅または30分経過時点で残りすべてを利確 """ dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) # データフレームが空の場合は何もしない if len(dataframe) == 0: return None # 最新のATR値を取得 latest_atr = dataframe['atr'].iloc[-1] entry_rate = trade.open_rate current_time = datetime.now(trade.open_date_utc.tzinfo) trade_duration = (current_time - trade.open_date_utc).total_seconds() / 60 # 分単位 # 各ターゲットの計算 if trade.is_short: # ショートポジションの場合 tp1_price = entry_rate * (1 - self.atr_multiplier_tp1.value * latest_atr / entry_rate) tp2_price = entry_rate * (1 - self.atr_multiplier_tp2.value * latest_atr / entry_rate) tp3_price = entry_rate * (1 - self.atr_multiplier_tp3.value * latest_atr / entry_rate) # 第1の利確点 if current_rate <= tp1_price and trade.nr_of_successful_exits == 0: return f"tp1_short_{self.tp1_size.value}" # 第2の利確点 elif current_rate <= tp2_price and trade.nr_of_successful_exits == 1: return f"tp2_short_{self.tp2_size.value}" # 第3の利確点または時間経過 elif (current_rate <= tp3_price or trade_duration >= 30) and trade.nr_of_successful_exits >= 2: return "tp3_short_exit_all" else: # ロングポジションの場合 tp1_price = entry_rate * (1 + self.atr_multiplier_tp1.value * latest_atr / entry_rate) tp2_price = entry_rate * (1 + self.atr_multiplier_tp2.value * latest_atr / entry_rate) tp3_price = entry_rate * (1 + self.atr_multiplier_tp3.value * latest_atr / entry_rate) # 第1の利確点 if current_rate >= tp1_price and trade.nr_of_successful_exits == 0: return f"tp1_long_{self.tp1_size.value}" # 第2の利確点 elif current_rate >= tp2_price and trade.nr_of_successful_exits == 1: return f"tp2_long_{self.tp2_size.value}" # 第3の利確点または時間経過 elif (current_rate >= tp3_price or trade_duration >= 30) and trade.nr_of_successful_exits >= 2: return "tp3_long_exit_all" return None def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, rate: float, time_in_force: str, exit_reason: str, current_time: datetime, **kwargs) -> bool: """ 部分利確ロジックの実装(バックテスト対応版) """ # バックテスト対応のため、シンプルな実装にする # 部分利確ではなく全ての取引を一度にクローズ # TP1, TP2, TP3いずれかの理由でのエグジット if (exit_reason.startswith('tp1_') or exit_reason.startswith('tp2_') or exit_reason == 'tp3_long_exit_all' or exit_reason == 'tp3_short_exit_all'): logger.info(f"利確理由: {exit_reason} でエグジット {pair}") # すべてのエグジットを許可 return True def leverage(self, pair: str, current_time: datetime, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: str, side: str, **kwargs) -> float: """ レバレッジ設定 - 重要度スコアが高いレベル(9以上)では最大5倍 - それ以外は3倍をデフォルトとする """ dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) # データフレームが空の場合はデフォルトを返す if len(dataframe) == 0: return 3 # 現在のレベルスコアを取得 if side == 'long': level_score = dataframe['resistance_score'].iloc[-1] else: level_score = dataframe['support_score'].iloc[-1] # スコアに基づいたレバレッジ設定 if level_score >= 9: return min(5, max_leverage) elif level_score >= 8: return min(4, max_leverage) else: return 3 |
Strategy League — fixed backtest that feeds the ranking
Export report Freqtrade logsRun finished · took 281.8s
ℹ️ This strategy uses a trailing stop / custom_stoploss() — freqtrade only
re-checks these once per 5m 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 →
- profit isn't statistically significant (p=0.94) — hard to tell apart from luck
- only 6% of resampled runs were profitable
- profitable in only 30% of rolling 3-month windows
- did not beat simply holding the market
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 |
|---|---|---|---|---|---|---|---|---|---|
| Dec 2025 | bearish trending low vol | 10 | +0.10 | 0.10 | 7 | 3 | 70.0 | -2.79 | 0h 12m |
| Nov 2025 | bearish trending high vol | 1 | -0.08 | -0.76 | 0 | 1 | 0.0 | -2.77 | 0h 15m |
| Sep 2025 | bullish choppy low vol | 3 | -0.09 | -0.30 | 1 | 2 | 33.3 | -2.75 | 0h 13m |
| Nov 2024 | bullish trending low vol | 3 | +0.30 | 0.99 | 3 | 0 | 100.0 | -2.75 | 0h 08m |
| Sep 2024 | bearish choppy low vol | 1 | -0.08 | -0.79 | 0 | 1 | 0.0 | -2.9 | 0h 15m |
| Aug 2024 | bearish choppy high vol | 5 | -0.29 | -0.57 | 1 | 4 | 20.0 | -2.82 | 0h 15m |
| Jun 2024 | bearish choppy low vol | 1 | -0.07 | -0.70 | 0 | 1 | 0.0 | -2.53 | 0h 15m |
| Apr 2024 | bearish choppy high vol | 1 | +0.25 | 2.50 | 1 | 0 | 100.0 | -2.46 | 0h 10m |
| Mar 2024 | bullish trending high vol | 1 | +0.16 | 1.63 | 1 | 0 | 100.0 | -2.71 | 0h 10m |
| Feb 2024 | bullish trending low vol | 2 | +0.06 | 0.28 | 1 | 1 | 50.0 | -3.02 | 0h 18m |
| Nov 2023 | bullish trending low vol | 3 | -0.25 | -0.84 | 0 | 3 | 0.0 | -2.93 | 0h 15m |
| Jul 2023 | bullish trending low vol | 2 | +0.10 | 0.51 | 2 | 0 | 100.0 | -2.7 | 0h 05m |
| Mar 2023 | bullish trending high vol | 8 | -0.27 | -0.34 | 2 | 6 | 25.0 | -2.84 | 0h 13m |
| Feb 2023 | bullish trending low vol | 2 | +0.08 | 0.42 | 1 | 1 | 50.0 | -2.51 | 0h 12m |
| Jan 2023 | bullish trending low vol | 1 | +0.05 | 0.51 | 1 | 0 | 100.0 | -2.59 | 0h 20m |
| Dec 2022 | bearish trending low vol | 1 | -0.05 | -0.47 | 0 | 1 | 0.0 | -2.64 | 0h 20m |
| Nov 2022 | bearish trending high vol | 3 | +0.15 | 0.51 | 2 | 1 | 66.7 | -2.85 | 0h 20m |
| Oct 2022 | bullish choppy low vol | 1 | +0.50 | 5.00 | 1 | 0 | 100.0 | -2.75 | 0h 00m |
| Jul 2022 | bearish trending high vol | 1 | +0.05 | 0.48 | 1 | 0 | 100.0 | -3.25 | 0h 15m |
| May 2022 | bearish trending high vol | 7 | +0.01 | 0.01 | 4 | 3 | 57.1 | -3.67 | 0h 08m |
| Mar 2022 | bullish choppy high vol | 2 | -0.21 | -1.07 | 0 | 2 | 0.0 | -3.3 | 0h 15m |
| Feb 2022 | bearish trending high vol | 4 | -0.01 | -0.03 | 2 | 2 | 50.0 | -3.09 | 0h 10m |
| Nov 2021 | bullish trending high vol | 1 | +0.06 | 0.58 | 1 | 0 | 100.0 | -3.08 | 0h 05m |
| Oct 2021 | bullish trending high vol | 3 | -0.06 | -0.22 | 1 | 2 | 33.3 | -3.14 | 0h 13m |
| Sep 2021 | bearish trending high vol | 2 | -0.22 | -1.12 | 0 | 2 | 0.0 | -3.07 | 0h 15m |
| Jul 2021 | bearish trending high vol | 1 | -0.15 | -1.46 | 0 | 1 | 0.0 | -2.85 | 0h 15m |
| Jun 2021 | bearish trending high vol | 1 | +0.07 | 0.71 | 1 | 0 | 100.0 | -2.7 | 0h 20m |
| May 2021 | bearish trending high vol | 1 | -0.10 | -0.96 | 0 | 1 | 0.0 | -2.77 | 0h 20m |
| Apr 2021 | bearish choppy high vol | 2 | -0.10 | -0.52 | 1 | 1 | 50.0 | -2.68 | 0h 15m |
| Mar 2021 | bullish choppy high vol | 1 | -0.03 | -0.27 | 0 | 1 | 0.0 | -2.57 | 0h 15m |
| Feb 2021 | bullish trending high vol | 5 | -0.46 | -0.92 | 1 | 4 | 20.0 | -2.55 | 0h 13m |
| Jan 2021 | bullish trending high vol | 18 | -2.26 | -1.26 | 3 | 15 | 16.7 | -2.33 | 0h 12m |
Yearly breakdown
| Year | Trades | Profit % | Avg % | Win | Loss | Win % | DD % | Avg dur |
|---|---|---|---|---|---|---|---|---|
| 2025 | 14 | -0.07 | -0.05 | 8 | 6 | 57.1 | -2.79 | 0h 12m |
| 2024 | 14 | +0.33 | 0.24 | 7 | 7 | 50.0 | -3.02 | 0h 13m |
| 2023 | 16 | -0.29 | -0.18 | 6 | 10 | 37.5 | -2.93 | 0h 13m |
| 2022 | 19 | +0.44 | 0.23 | 10 | 9 | 52.6 | -3.67 | 0h 12m |
| 2021 | 35 | -3.25 | -0.93 | 8 | 27 | 22.9 | -3.14 | 0h 13m |
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
4 potential lookahead pattern(s) found · 2 to review
| Line | Pattern | Detail | |
|---|---|---|---|
| 214 | leak | whole_series_reduction | .mean() over the whole column sees future rows (use .rolling(window).mean() for a causal value) |
| 241 | leak | iloc_last | iloc[-1] in populate_* applies the newest candle to all rows |
| 159 | leak | tail | .tail() in populate_* peeks at the end of the frame |
| 142 | leak | iloc_last | iloc[-1] in populate_* applies the newest candle to all rows |
| 396 | review | dead_callback | custom_stoploss() is defined but use_custom_stoploss isn't True, and freqtrade only calls it when that flag is set -- the method never runs and every trade uses the static stoploss |
| 349 | review | short_without_can_short | writes enter_short, exit_short but can_short isn't True, so freqtrade never opens a short (it also requires futures/margin mode). Worse, freqtrade only takes a long when `not any([exit_long, enter_short])`, so every row you mark enter_short SUPPRESSES that candle's long entry and opens nothing in its place. |
ran by Ron · took s
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.