V5.3 策略适配器基类
Timeframe
N/A
Direction
Long Only
Stoploss
N/A
Trailing Stop
No
ROI
N/A
Interface Version
3
Startup Candles
N/A
Indicators
0
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
V5.3 策略适配器(SafeLoader)
所有策略必须继承此适配器,统一接口规范。
"""
from typing import Dict, Optional, Tuple
from datetime import datetime
from pandas import DataFrame
from freqtrade.strategy import IStrategy
from freqtrade.persistence import Trade
import sys
import os
from pathlib import Path
# 添加 guardian 模块路径
# 获取 user_data 目录(策略文件所在目录的父目录)
user_data_dir = Path(__file__).parent.parent
guardian_path = user_data_dir / "guardian"
# 确保 guardian 目录在 Python 路径中
if str(user_data_dir) not in sys.path:
sys.path.insert(0, str(user_data_dir))
# 尝试导入 guardian 模块
try:
from guardian.allocator import Bucket
from guardian.regime_fsm import Regime
except ImportError:
# 如果导入失败,尝试添加绝对路径
abs_user_data = user_data_dir.resolve()
if str(abs_user_data) not in sys.path:
sys.path.insert(0, str(abs_user_data))
from guardian.allocator import Bucket
from guardian.regime_fsm import Regime
class V5_3_Adapter(IStrategy):
"""
V5.3 策略适配器基类
职责:
1. 统一策略接口(信号 + SL)
2. Regime Whitelist 检查
3. 与 Guardian 系统集成
4. Backtest 模式支持
"""
INTERFACE_VERSION = 3
# === 必须由子类声明的属性 ===
# 策略桶分类
bucket: Bucket = Bucket.TREND
# 允许运行的 Regime 列表
allowed_regimes: list = [Regime.SIDEWAYS] # 默认仅允许震荡市
# 单次请求风险(R)
r_request: float = 0.25 # 默认 0.25R
# 策略名称(用于日志)
strategy_name: str = "Unknown"
# === 可选配置 ===
# 最小置信度(0.0-1.0)
min_confidence: float = 0.5
def __init__(self, config: dict) -> None:
super().__init__(config)
# Guardian 系统引用(由外部注入)
self.guardian = None
# Backtest 模式标志
self.is_backtest = config.get("runmode") in ["backtest", "hyperopt"]
# 启动检查
if not self.is_backtest:
self._startup_check()
def _startup_check(self):
"""启动时检查配置"""
assert self.bucket in Bucket, f"策略 {self.strategy_name} 必须声明有效的 bucket"
assert isinstance(self.allowed_regimes, list) and len(self.allowed_regimes) > 0, \
f"策略 {self.strategy_name} 必须声明 allowed_regimes"
assert 0 < self.r_request <= 0.5, \
f"策略 {self.strategy_name} 的 r_request 必须在 (0, 0.5] 范围内"
def set_guardian(self, guardian):
"""设置 Guardian 系统引用(由外部调用)"""
self.guardian = guardian
def _check_regime_allowed(self, current_regime: Optional[Regime]) -> bool:
"""检查当前 Regime 是否允许运行"""
if current_regime is None:
return False
return current_regime in self.allowed_regimes
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
子类必须实现此方法,计算指标
"""
raise NotImplementedError("子类必须实现 populate_indicators")
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
子类必须实现此方法,输出信号
输出格式:
- enter_long / enter_short: 1 或 0
- enter_tag: "StrategyName|RegimeAtEntry"
- proposed_sl_price: 建议的止损价格(可选,用于计算 SL%)
- confidence: 0.0-1.0(可选)
"""
raise NotImplementedError("子类必须实现 populate_entry_trend")
def custom_stake_amount(
self,
pair: str,
current_time: datetime,
current_rate: float,
proposed_stake: float,
min_stake: Optional[float],
max_stake: float,
leverage: float,
entry_tag: Optional[str],
side: str,
**kwargs,
) -> float:
"""
自定义仓位大小(与 Guardian 集成)
注意:在 Backtest 模式下,使用简化逻辑
"""
if self.is_backtest:
# Backtest 模式:使用默认逻辑或简化版本
return proposed_stake
if self.guardian is None:
# Guardian 未初始化,使用默认
return proposed_stake
# 获取策略信号中的 SL 信息
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
try:
df = dataframe.loc[dataframe["date"] <= current_time]
last = df.iloc[-1] if not df.empty else dataframe.iloc[-1]
except Exception:
last = dataframe.iloc[-1]
# 提取 proposed_sl_price(如果存在)
proposed_sl_price = last.get("proposed_sl_price")
if proposed_sl_price is None or proposed_sl_price == 0:
# 使用默认 SL(策略的 stoploss)
strategy_sl_pct = abs(self.stoploss) if self.stoploss < 0 else 0.02
else:
# 计算 SL%
if side == "long":
strategy_sl_pct = abs((proposed_sl_price - current_rate) / current_rate)
else: # short
strategy_sl_pct = abs((current_rate - proposed_sl_price) / current_rate)
# 提取 confidence
confidence = last.get("confidence", 1.0)
if confidence is None:
confidence = 1.0
# 调用 Guardian 检查
direction = "LONG" if side == "long" else "SHORT"
result = self.guardian.check_trade_request(
pair=pair,
direction=direction,
strategy_name=self.strategy_name,
bucket=self.bucket,
r_request=self.r_request,
strategy_sl_pct=strategy_sl_pct,
confidence=confidence,
)
if not result["allowed"]:
# 不允许开仓,返回 0
# 记录 reason_code(可通过日志系统)
return 0.0
# 使用 Guardian 计算的仓位大小
position_value = result["position_value"]
# 转换为 freqtrade 的 stake_amount(考虑杠杆)
stake_amount = position_value / leverage if leverage > 0 else position_value
return min(stake_amount, max_stake)
def adjust_trade_position(
self,
trade: Trade,
current_time: datetime,
current_rate: float,
current_profit: float,
min_stake: Optional[float],
max_stake: float,
current_entry_rate: float,
current_exit_rate: float,
current_entry_profit: float,
current_exit_profit: float,
**kwargs,
) -> Optional[float]:
"""
禁止策略自行加仓(由 Guardian 统一管理)
"""
return None
def confirm_trade_entry(
self,
pair: str,
order_type: str,
amount: float,
rate: float,
time_in_force: str,
current_time: datetime,
entry_tag: Optional[str],
side: str,
**kwargs,
) -> bool:
"""
确认开仓(记录到 Guardian)
"""
if not self.is_backtest and self.guardian:
# 获取实际分配的仓位信息(需要从 custom_stake_amount 的结果中获取)
# 简化:这里只做确认,实际记录在 custom_stake_amount 中完成
pass
return True
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:
"""
确认平仓(记录到 Guardian)
"""
if not self.is_backtest and self.guardian:
# 计算盈亏百分比
profit_pct = trade.calc_profit_ratio(rate)
# 记录到 Guardian
self.guardian.record_trade_closed(
pair=pair,
bucket=self.bucket,
profit_pct=profit_pct,
)
return True