Strategy
♡
Basics
mode: spot
Indicators
pandas_ta
Methods
_collect_parameters
_get_order_engine
_has_vectorized_methods
create_buy_order
create_close_order
create_sell_order
enable_lookahead_check
from_class
generate_signals
get_all_strategies
get_informative_methods
get_informative_pair
get_parameter_value
get_parameters
get_strategies_by_category
get_strategy
list_strategies
mock_indicator
on_fill
order_engine
register
risk_checks
set_informative_data
set_parameter_values
set_params
size_positions
ta
update_order_engine_context
Other
parameters
src
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 | """ 策略基类 Freqtrade 风格:支持类属性配置,无需 __init__ """ from __future__ import annotations # 延迟类型注解求值,避免循环导入 from abc import ABC, abstractmethod import inspect from typing import TYPE_CHECKING, Dict, List, Optional, Any, Type, Union import pandas as pd if TYPE_CHECKING: from src.execution.order_engine import OrderEngine from src.core.types import Signal, OrderIntent, MarketData, PortfolioState, RiskState, ActionType, OrderSide, OrderType from src.core.context import RunContext from src.utils.logger import get_logger from .parameters import Parameter, IntParameter, DecimalParameter, BooleanParameter # 延迟导入,避免循环依赖 _order_engine = None def _get_order_engine(): global _order_engine if _order_engine is None: from src.execution.order_engine import OrderEngine, create_order, buy, sell _order_engine = (OrderEngine, create_order, buy, sell) return _order_engine # 先定义 Config 类,再添加类方法 class StrategyConfig: """策略配置""" def __init__(self, strategy_id: str = "", timeframe: str = "1d", params: Optional[Dict[str, Any]] = None): self.strategy_id = strategy_id self.timeframe = timeframe self.params = params or {} @classmethod def from_class(cls, strategy_cls: Any) -> 'StrategyConfig': """从策略类自动创建配置""" strategy_id = getattr(strategy_cls, "strategy_id", strategy_cls.__name__.lower()) timeframe = getattr(strategy_cls, "timeframe", "1d") # 收集类级别的参数 params = {} for attr_name in dir(strategy_cls): if attr_name.startswith("_"): continue attr = getattr(strategy_cls, attr_name, None) if attr is not None and not callable(attr): if attr_name not in ["strategy_id", "timeframe", "auto_register"]: params[attr_name] = attr return cls( strategy_id=strategy_id, timeframe=timeframe, params=params ) class Strategy(ABC): """策略基类""" auto_register: bool = True # 用于存储已注册的策略类(非实例) _registered_strategy_classes: Dict[str, type] = {} def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) # 检查是否应该自动注册 if not getattr(cls, "auto_register", True): return # 跳过抽象类 if inspect.isabstract(cls): return # 获取 strategy_id strategy_id = getattr(cls, "strategy_id", cls.__name__.lower()) # 注册到类级别的注册表 if strategy_id not in Strategy._registered_strategy_classes: Strategy._registered_strategy_classes[strategy_id] = cls get_logger("strategy_registry").debug(f"自动注册策略类: {strategy_id} ({cls.__name__})") # 同时注册到全局注册表(创建实例) registry = globals().get("strategy_registry") if registry is not None: try: # 尝试创建实例并注册 instance = cls() if registry.get_strategy(strategy_id) is None: registry.register(instance) except TypeError: # 抽象类无法实例化,跳过实例注册 pass def __init__(self, config: Optional[StrategyConfig] = None): if config is None: # 从类属性获取配置 cls = self.__class__ strategy_id = getattr(cls, "strategy_id", cls.__name__.lower()) timeframe = getattr(cls, "timeframe", "1d") # 收集类属性作为参数 params = {} for attr_name in dir(cls): if attr_name.startswith("_"): continue attr = getattr(cls, attr_name, None) if attr is not None and not callable(attr): if attr_name not in ["strategy_id", "timeframe", "auto_register"]: params[attr_name] = attr config = StrategyConfig( strategy_id=strategy_id, timeframe=timeframe, params=params ) self.config = config self.strategy_id = config.strategy_id self.timeframe = config.timeframe self.logger = get_logger(f"strategy.{config.strategy_id}") self._informative_data: Dict[str, pd.DataFrame] = {} self._informative_results: Dict[str, pd.DataFrame] = {} self.informative_timeframes: List[str] = [] self._vectorized_data: Dict[str, pd.DataFrame] = {} # 存储向量化预计算后的数据 # 自动收集参数 self._parameters: Dict[str, Parameter] = {} # 订单引擎(必须在 _collect_parameters 之前初始化) # 使用字符串注解避免循环导入 self._order_engine = None # type: Optional['OrderEngine'] self._collect_parameters() def _collect_parameters(self) -> None: """自动收集类定义中的参数对象""" for attr_name in dir(self): if attr_name.startswith("_"): continue attr = getattr(self, attr_name) if isinstance(attr, Parameter): if attr.name != attr_name: # 参数名与属性名不一致时,更新参数名 attr.name = attr_name self._parameters[attr_name] = attr self.logger.debug(f"发现参数: {attr_name} = {attr.value}") def get_parameters(self) -> Dict[str, Parameter]: """获取所有参数定义""" return self._parameters.copy() def get_parameter_value(self, name: str) -> Any: """获取单个参数值""" if name in self._parameters: return self._parameters[name].value # 回退到 config.params return self.config.params.get(name) def set_parameter_values(self, param_dict: Dict[str, Any]) -> None: """设置参数值(供优化器使用)""" for name, value in param_dict.items(): if name in self._parameters: self._parameters[name].value = value self.logger.debug(f"更新参数: {name} = {value}") else: # 未知参数,记录警告 self.logger.warning(f"未知参数: {name}") def _has_vectorized_methods(self) -> bool: """检查是否有向量化方法被重写实现""" # 检查每个方法是否在子类中被重写 vectorized_methods = [ 'populate_indicators', 'populate_entry_trend', 'populate_exit_trend' ] for method_name in vectorized_methods: method = getattr(self.__class__, method_name, None) # 检查方法是否被显式定义(不是从 Strategy 基类继承的默认实现) if method is not None: # 如果方法不在 Strategy 基类中定义,说明被重写了 base_method = Strategy.__dict__.get(method_name) if base_method is not method: return True return False def set_informative_data(self, bars_map: Dict[str, pd.DataFrame]): self._informative_data = bars_map self._main_dataframe_with_informative = bars_map.get(self.timeframe, pd.DataFrame()) def get_informative_methods(self) -> List[Any]: """ 获取所有@informative装饰的方法 Returns: 装饰的方法列表 """ methods = [] for name in dir(self): attr = getattr(self, name) if callable(attr) and hasattr(attr, '_is_informative'): methods.append(attr) return methods def get_informative_pair(self, timeframe: str, column: str) -> Union[pd.Series, pd.DataFrame]: key = f"{column}_{timeframe}" if not hasattr(self, '_informative_results'): self.logger.warning(f"未找到informative数据: {timeframe}/{column}") return pd.Series() informative_df = self._informative_results.get(timeframe) if informative_df is None or informative_df.empty: self.logger.warning(f"未找到informative数据: {timeframe}/{column}") return pd.Series() if key in informative_df.columns: return informative_df[key] self.logger.warning(f"未找到informative数据: {timeframe}/{column}") return pd.Series() @property def ta(self): """ 提供对 pandas_ta 的直接访问 允许在策略中通过 self.ta.<indicator>(...) 调用 如果pandas_ta不可用,返回一个模拟对象 """ try: import pandas_ta as ta return ta except ImportError: # 返回一个模拟对象,避免ImportError class MockTA: def __getattr__(self, name): def mock_indicator(*args, **kwargs): # 返回一个空Series或DataFrame import pandas as pd return pd.Series() return mock_indicator return MockTA() def populate_indicators(self, dataframe: Any, metadata: Dict[str, Any]) -> Any: """ [Vectorized] 批量计算指标 参考 Freqtrade: 一次性计算所有技术指标 Args: dataframe: 原始价格 DataFrame metadata: 元数据 (symbol, etc.) Returns: 带有指标的 DataFrame """ return dataframe def populate_entry_trend(self, dataframe: Any, metadata: Dict[str, Any]) -> Any: """ [Vectorized] 批量计算进场信号 在 'enter_long' 或 'enter_short' 列标记 1 Args: dataframe: 带有指标的 DataFrame metadata: 元数据 Returns: 带有进场信号的 DataFrame """ return dataframe def populate_exit_trend(self, dataframe: Any, metadata: Dict[str, Any]) -> Any: """ [Vectorized] 批量计算离场信号 在 'exit_long' 或 'exit_short' 列标记 1 Args: dataframe: 带有指标的 DataFrame metadata: 元数据 Returns: 带有离场信号的 DataFrame """ return dataframe @abstractmethod def generate_signals( self, md: MarketData, ctx: RunContext, ) -> List[Signal]: """ 生成策略信号 Args: md: 市场数据 ctx: 运行上下文 Returns: 信号列表 """ pass @abstractmethod def size_positions( self, signals: List[Signal], portfolio: PortfolioState, risk: RiskState, ctx: RunContext, ) -> List[OrderIntent]: """ 根据信号和组合状态计算目标仓位(订单意图) Args: signals: 策略信号列表 portfolio: 组合状态 risk: 风险状态 ctx: 运行上下文(可通过 ctx.cross_section 获取当前时刻的横截面数据) Returns: 订单意图列表 """ pass def risk_checks( self, order_intents: List[OrderIntent], portfolio: PortfolioState, risk: RiskState, ctx: RunContext, ) -> tuple[List[OrderIntent], List[Dict[str, Any]]]: """ 风险检查(可选,默认实现) Args: order_intents: 订单意图列表 portfolio: 组合状态 risk: 风险状态 ctx: 运行上下文 Returns: (approved_intents, blocks) 元组 """ approved = [] blocks = [] for intent in order_intents: # 检查黑名单 if intent.symbol in risk.blacklist: blocks.append({ "order_intent": intent, "reason": f"标的 {intent.symbol} 在黑名单中", }) continue # 检查最大持仓数 if risk.max_positions is not None: current_positions = len([s for s in portfolio.positions.values() if abs(s) > 1e-8]) if intent.side == OrderSide.BUY and intent.symbol not in portfolio.positions: if current_positions >= risk.max_positions: blocks.append({ "order_intent": intent, "reason": f"已达到最大持仓数限制: {risk.max_positions}", }) continue # 检查单标的最大仓位 if risk.max_position_size is not None: # 这里需要知道目标仓位大小,简化处理 pass approved.append(intent) return approved, blocks def on_fill(self, fill_event, ctx: RunContext) -> Dict[str, Any]: """ 成交事件处理(可选) Args: fill_event: 成交事件(Fill对象) ctx: 运行上下文 Returns: 状态更新字典 """ return {} def set_params(self, params: Dict[str, Any]): """ 设置策略参数 Args: params: 参数字典 """ self.config.params.update(params) self.logger.info(f"更新策略参数: {params}") def enable_lookahead_check(self) -> bool: """是否开启未来数据检查(由策略配置控制)""" return bool(self.config.params.get("lookahead_check", False)) # ===== 订单引擎集成方法(延迟导入)===== @property def order_engine(self): """获取订单引擎(懒加载)""" from src.execution.order_engine import OrderEngine if not hasattr(self, '_cached_order_engine'): self._cached_order_engine = OrderEngine(self.strategy_id) return self._cached_order_engine def update_order_engine_context(self, ctx: RunContext): """更新订单引擎的运行时上下文""" if hasattr(self, '_cached_order_engine'): self._cached_order_engine.update_context(ctx.now_utc) def create_buy_order(self, symbol: str, qty: float, ctx: RunContext, **metadata) -> OrderIntent: """快速创建买入订单(便捷方法)""" from src.execution.order_engine import buy return buy(symbol, qty, self.strategy_id, ctx.now_utc, **metadata) def create_sell_order(self, symbol: str, qty: float, ctx: RunContext, **metadata) -> OrderIntent: """快速创建卖出订单(便捷方法)""" from src.execution.order_engine import sell return sell(symbol, qty, self.strategy_id, ctx.now_utc, **metadata) def create_close_order(self, symbol: str, ctx: RunContext, qty: Optional[float] = None, **metadata) -> OrderIntent: """快速创建平仓订单(便捷方法)""" from src.execution.order_engine import create_order metadata["reason"] = "close" return create_order( "SELL", symbol, qty if qty is not None else 0, self.strategy_id, ctx.now_utc, **metadata ) class StrategyRegistry: """策略注册表""" def __init__(self): self._strategies: Dict[str, Strategy] = {} self.logger = get_logger("strategy_registry") def register(self, strategy: Strategy): """注册策略""" self._strategies[strategy.strategy_id] = strategy self.logger.debug(f"注册策略: {strategy.strategy_id}") def get_strategy(self, name: str) -> Optional[Strategy]: """获取策略""" return self._strategies.get(name) def list_strategies(self, category: Optional[str] = None) -> List[str]: """列出策略""" if category: return [name for name, strategy in self._strategies.items() if getattr(strategy, 'category', None) == category] return list(self._strategies.keys()) def get_strategies_by_category(self, category: str) -> Dict[str, Strategy]: """按分类获取策略""" return {name: strategy for name, strategy in self._strategies.items() if getattr(strategy, 'category', None) == category} def get_all_strategies(self) -> Dict[str, Strategy]: """获取所有策略""" return self._strategies.copy() # 全局策略注册表 strategy_registry = StrategyRegistry() |
Strategy League — fixed backtest that feeds the ranking
Failed — strategy imports unavailable module: src
rade.configuration.configuration - INFO - Using data directory: /freqle/user_data/data/binance ... 2026-07-20 23:15:37,362 - freqtrade.configuration.configuration - INFO - Parameter --export detected: trades ... 2026-07-20 23:15:37,362 - freqtrade.configuration.configuration - INFO - Parameter --cache=none detected ... 2026-07-20 23:15:37,362 - freqtrade.configuration.configuration - INFO - Filter trades by timerange: 20210101-20260101 2026-07-20 23:15:37,363 - freqtrade.exchange.check_exchange - INFO - Checking exchange... 2026-07-20 23:15:37,370 - freqtrade.exchange.check_exchange - INFO - Exchange "binance" is officially supported by the Freqtrade development team. 2026-07-20 23:15:37,370 - freqtrade.configuration.configuration - INFO - Using pairlist from configuration. 2026-07-20 23:15:37,370 - freqtrade.configuration.config_validation - INFO - Validating configuration ... 2026-07-20 23:15:37,372 - freqtrade.commands.optimize_commands - INFO - Starting freqtrade in Backtesting mode 2026-07-20 23:15:37,373 - freqtrade.exchange.exchange - INFO - Instance is running with dry_run enabled 2026-07-20 23:15:37,373 - freqtrade.exchange.exchange - INFO - Using CCXT 4.5.61 2026-07-20 23:15:37,384 - freqtrade.exchange.exchange - INFO - Using Exchange "Binance" 2026-07-20 23:15:37,560 - freqtrade.resolvers.exchange_resolver - INFO - Using resolved exchange 'Binance'... 2026-07-20 23:15:37,563 - freqtrade.resolvers.iresolver - WARNING - Could not import /freqle/user_data/strategies/Strategy.py due to 'No module named 'src'' 2026-07-20 23:15:37,567 - freqtrade.resolvers.iresolver - WARNING - Could not import /freqle/user_data/strategies/Strategy.py due to 'No module named 'src'' 2026-07-20 23:15:37,570 - freqtrade.resolvers.iresolver - WARNING - Could not import /freqle/user_data/strategies/Strategy.py due to 'No module named 'src'' 2026-07-20 23:15:37,570 - freqtrade - ERROR - Impossible to load Strategy 'Strategy'. This class does not exist or contains Python code errors.
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
Static source analysis — instant, does not run the strategy. Flags future-data leaks, backtest-realism problems, and indicators worth a second look.
Lookahead analysis
freqtrade lookahead-analysis: detects strategies peeking at future candles.