FactorPortfolioTriggerStrategy
♡
Basics
mode: futures
timeframe: 2h
interface version: 3
Settings
stoploss: -0.99
has minimal roi
process only new candles
startup candle count: 1
5 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 | """ Factor portfolio trigger strategy for Binance USDT-M futures. The portfolio logic is computed by user_data/scripts/update_factor_portfolio_basket.py. This strategy intentionally stays thin: - Enter long/short pairs when the external basket enables entries. - Exit open positions when the external basket enables portfolio take-profit. - No stoploss in normal operation. """ from __future__ import annotations import json from pathlib import Path from typing import Any from pandas import DataFrame from freqtrade.strategy import IStrategy class FactorPortfolioTriggerStrategy(IStrategy): INTERFACE_VERSION = 3 timeframe = "2h" can_short = True startup_candle_count = 1 process_only_new_candles = True minimal_roi = {"0": 100} stoploss = -0.99 use_custom_stoploss = False use_exit_signal = False exit_profit_only = False ignore_roi_if_entry_signal = True basket_path = Path("user_data/current_factor_portfolio_basket.json") def _load_basket(self) -> dict[str, Any]: try: return json.loads(self.basket_path.read_text(encoding="utf-8")) except FileNotFoundError: return { "entries_enabled": False, "exit_enabled": False, "long_pairs": [], "short_pairs": [], } def _side_for_pair(self, pair: str) -> str: basket = self._load_basket() if pair in basket.get("long_pairs", []): return "long" if pair in basket.get("short_pairs", []): return "short" return "none" def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["enter_long"] = 0 dataframe["enter_short"] = 0 dataframe["enter_tag"] = None if dataframe.empty: return dataframe basket = self._load_basket() if not basket.get("entries_enabled", False): return dataframe side = self._side_for_pair(metadata["pair"]) last_index = dataframe.index[-1] sequence = basket.get("trade_sequence", 0) if side == "long": dataframe.loc[last_index, "enter_long"] = 1 dataframe.loc[last_index, "enter_tag"] = f"drawdown60_top10_seq{sequence}" elif side == "short": dataframe.loc[last_index, "enter_short"] = 1 dataframe.loc[last_index, "enter_tag"] = f"lowqv_weakbuy_seq{sequence}" return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: dataframe["exit_long"] = 0 dataframe["exit_short"] = 0 return dataframe def custom_exit( self, pair: str, trade, current_time, current_rate: float, current_profit: float, **kwargs, ): basket = self._load_basket() if basket.get("exit_enabled", False): return "portfolio_take_profit" return None def leverage( self, pair: str, current_time, current_rate: float, proposed_leverage: float, max_leverage: float, entry_tag: str | None, side: str, **kwargs, ) -> float: return min(1.0, max_leverage) |
Strategy League — fixed backtest that feeds the ranking
Failed — timeframe 2h not in sandbox data (1m 3m 5m 15m 30m 1h 4h 12h 1d)
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 · 2 thing(s) worth reviewing before trusting the numbers
| Line | Pattern | Detail | |
|---|---|---|---|
| 92 | review | dead_callback | custom_exit() is defined but use_exit_signal is False, and freqtrade only consults it inside that flag -- the method never runs |
| 80 | review | enter_tag_overwrite | enter_tag/exit_tag is written by 2 separate assignments -- they share one column and run in source order, so a row matching more than one condition keeps only the LAST tag. Per-tag statistics won't mean what they appear to |
ran by Ron · took s
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.