myTestFreqAI/freqtrade/templates/freqaiprimer.py
zhangkun9038@dingtalk.com eacad43b80 up
2025-05-30 22:14:12 +00:00

358 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
import numpy as np
import datetime
from functools import reduce
from freqtrade.persistence import Trade
import talib.abstract as ta
from pandas import DataFrame
from typing import Dict, Optional
from technical import qtpylib
from freqtrade.strategy import IStrategy
from logging import getLogger
logger = logging.getLogger(__name__)
class FreqaiPrimer(IStrategy):
"""
策略说明:
- 所有交易信号由 FreqAI 动态预测
- 不使用 Hyperopt 优化任何参数
- 使用 FreqAI 提供的趋势和波动率信号进行交易决策
"""
# 👇 添加在这里
PAIR_PARAMS = {
"BTC/USDT": {"volatility_factor": 1.0, "min_stop": -0.005},
"TON/USDT": {"volatility_factor": 1.3, "min_stop": -0.0035},
"DOGE/USDT": {"volatility_factor": 1.5, "min_stop": -0.003},
"DOT/USDT": {"volatility_factor": 1.3, "min_stop": -0.0035},
"XRP/USDT": {"volatility_factor": 1.35, "min_stop": -0.0032},
"OKB/USDT": {"volatility_factor": 0.9, "min_stop": -0.006},
"SOL/USDT": {"volatility_factor": 1.4, "min_stop": -0.0032},
"WCT/USDT": {"volatility_factor": 1.4, "min_stop": -0.004},
"TRUMP/USDT": {"volatility_factor": 1.4, "min_stop": -0.0035},
"SUI/USDT": {"volatility_factor": 1.6, "min_stop": -0.0025},
"PEPE/USDT": {"volatility_factor": 1.5, "min_stop": -0.0036},
"TRB/USDT": {"volatility_factor": 1.45, "min_stop": -0.004},
"MASK/USDT": {"volatility_factor": 1.65, "min_stop": -0.0045},
"UNI/USDT": {"volatility_factor": 1.45, "min_stop": -0.005},
"KAITO/USDT": {"volatility_factor": 1.45, "min_stop": -0.004},
}
use_custom_stoploss = True
trailing_stop = True
trailing_stop_positive = 0.005
trailing_stop_positive_offset = 0.01
trailing_only_offset_is_reached = True
plot_config = {
"main_plot": {},
"subplots": {
"Signals": {
"enter_long": {"color": "green"},
"exit_long": {"color": "red"}
},
"FreqAI Predictions": {
"&-buy_signal": {"color": "blue"},
"&-sell_signal": {"color": "orange"},
"&-volatility_forecast": {"color": "purple"}
}
}
}
freqai_info = {
"model": "LightGBMClassifier",
"feature_parameters": {
"include_timeframes": ["5m", "15m", "1h"],
"label_period_candles": 24,
"include_shifted_candles": 3,
},
"data_split_parameters": {
"test_size": 0.2,
"shuffle": False,
},
"model_training_parameters": {
"n_estimators": 200,
"learning_rate": 0.05,
"num_leaves": 31,
"verbose": -1,
},
}
def __init__(self, config: dict, *args, **kwargs):
super().__init__(config, *args, **kwargs)
# 设置日志级别为 DEBUG
logger.setLevel(logging.DEBUG)
logger.debug("✅ 策略已初始化,日志级别设置为 DEBUG")
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
# 保留原有指标
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
real = ta.TYPPRICE(dataframe)
upperband, middleband, lowerband = ta.BBANDS(real, timeperiod=period, nbdevup=2.0, nbdevdn=2.0)
dataframe["bb_lowerband-period"] = lowerband
dataframe["bb_upperband-period"] = upperband
dataframe["bb_middleband-period"] = middleband
dataframe["%-bb_width-period"] = (dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]) / dataframe["bb_middleband-period"]
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
# 添加策略 B 的指标
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) # 资金流指标
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period) # 趋势强度
dataframe["%-tema-period"] = ta.TEMA(dataframe, timeperiod=period) # TEMA 指标
dataframe["%-relative_volume-period"] = dataframe["volume"] / dataframe["volume"].rolling(period).mean() # 相对成交量
dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"] # 收盘价/布林带下轨
# 数据清理
columns_to_clean = [
"%-rsi-period", "%-mfi-period", "%-sma-period", "%-ema-period", "%-adx-period",
"bb_lowerband-period", "bb_middleband-period", "bb_upperband-period",
"%-bb_width-period", "%-roc-period", "%-relative_volume-period", "%-close-bb_lower-period", "%-tema-period"
]
for col in columns_to_clean:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0)
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
"""
使用历史窗口预测未来趋势和波动率作为辅助信号,
避免使用任何未来数据(如 shift(-N)
"""
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 1. 趋势强度:使用过去 N 根 K 线的收益率判断当前趋势
dataframe["&-trend_strength"] = dataframe["close"].pct_change(label_period)
# 2. 波动率预测:使用过去 N 根 K 线的价格变动绝对值
dataframe["&-volatility_forecast"] = dataframe["close"].pct_change(label_period).abs()
# 3. 动态 ROI 目标:基于趋势强度缩放
dataframe["&-roi_target"] = np.where(
dataframe["&-trend_strength"] > 0,
dataframe["&-trend_strength"] * 1.5,
0.01 # 最小 ROI
)
# 4. 动态止损目标:基于波动率缩放
dataframe["&-stoploss_target"] = -dataframe["&-volatility_forecast"] * 1.2
# 👇 新增z-score 标准化模型输出
if self.freqai and hasattr(self.freqai, "data"):
labels_mean = self.freqai.data.labels_mean
labels_std = self.freqai.data.labels_std
mean_roi = labels_mean.get("&-roi_target", 0.01)
std_roi = labels_std.get("&-roi_target", 0.01)
mean_stop = labels_mean.get("&-stoploss_target", -0.01)
std_stop = labels_std.get("&-stoploss_target", 0.01)
# z-score 标准化
dataframe["&-roi_target_z"] = (dataframe["&-roi_target"] - mean_roi) / std_roi
dataframe["&-stoploss_target_z"] = (dataframe["&-stoploss_target"] - mean_stop) / std_stop
# 市场状态识别:判断当前是震荡、趋势、超买还是超卖
if "bb_upperband-period" in dataframe.columns and "bb_lowerband-period" in dataframe.columns:
dataframe["&-market_condition"] = np.select([
(dataframe["close"] > dataframe["bb_upperband-period"]),
(dataframe["close"] < dataframe["bb_lowerband-period"]),
(dataframe["&-trend_strength"] > 0.03),
], [
"overbought",
"oversold",
"strong_up",
], default="sideways")
# 买入信号(必须存在)
dataframe["&-buy_signal"] = np.where(dataframe["&-trend_strength"] > 0.01, 1, 0)
# 卖出信号(必须存在)
dataframe["&-sell_signal"] = np.where(dataframe["&-trend_strength"] < -0.01, 1, 0)
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe = self.freqai.start(dataframe, metadata, self)
# 缓存 mean/std 供后续使用
if self.freqai and hasattr(self.freqai, "data"):
labels_mean = self.freqai.data.labels_mean
labels_std = self.freqai.data.labels_std
dataframe["mean_trend"] = labels_mean.get("&-trend_strength", 0.0001)
dataframe["std_trend"] = labels_std.get("&-trend_strength", 0.0031)
dataframe["mean_volatility"] = labels_mean.get("&-volatility_forecast", 0.0021)
dataframe["std_volatility"] = labels_std.get("&-volatility_forecast", 0.0022)
dataframe["mean_roi"] = labels_mean.get("&-roi_target", 0.0065)
dataframe["std_roi"] = labels_std.get("&-roi_target", 0.0041)
dataframe["mean_stop"] = labels_mean.get("&-stoploss_target", -0.0026)
dataframe["std_stop"] = labels_std.get("&-stoploss_target", 0.0027)
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
"""
基于 FreqAI 的预测结果生成入场信号。
使用滚动分位数 + Z-score + 市场状态过滤,提高信号质量。
"""
conditions = []
# 确保 &-buy_signal 存在
if "&-buy_signal" in df.columns:
# 使用过去 20 根 K 线中 buy_signal 的 80 分位作为动态阈值
df["buy_threshold"] = df["&-buy_signal"].rolling(20).quantile(0.8)
buy_condition = df["&-buy_signal"] >= df["buy_threshold"]
# 使用 Z-score 判断趋势显著性(仅保留趋势较强的时间点)
if "mean_trend" in df.columns and "std_trend" in df.columns:
mean_trend = df["mean_trend"].iloc[-1]
std_trend = df["std_trend"].iloc[-1]
z_score_trend = (df["&-trend_strength"] - mean_trend) / std_trend
buy_condition &= (z_score_trend > 1) # 只保留趋势显著的信号
# 市场状态过滤:震荡市减少交易频率
if "&-market_condition" in df.columns:
buy_condition &= ~(
(df["&-market_condition"] == "sideways") &
(df["&-buy_signal"] < df["buy_threshold"] * 1.2)
)
# 在超买市场中进一步过滤买入信号
buy_condition &= ~(df["&-market_condition"] == "overbought")
# EMA 趋势过滤:只在价格高于 EMA50 时开仓
df["ema50"] = ta.EMA(df, timeperiod=50)
buy_condition &= (df["close"] > df["ema50"])
# 成交量过滤:成交量需大于 20 日均值的 1 倍
df["vol_mean"] = df["volume"].rolling(20).mean()
buy_condition &= (df["volume"] > df["vol_mean"] * 1)
conditions.append(buy_condition)
else:
self.logger.warning("⚠️ &-buy_signal 列缺失,跳过该条件")
# 如果有多个条件,使用逻辑与合并
if len(conditions):
df.loc[reduce(lambda x, y: x & y, conditions), 'enter_long'] = 1
return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
# Compute sell_signal_high outside the conditions list
pair = metadata['pair']
trade = Trade.get_trades([("pair", pair)]).first()
if trade and trade.is_open:
open_candle_index = df.index.get_loc(trade.open_date.replace(tzinfo=None))
current_index = len(df) - 1
holding_period = current_index - open_candle_index
if holding_period < 5:
df.loc[df.index[-1], 'exit_long'] = 0
return df
df["sell_signal_high"] = (df["&-sell_signal"] > 0.3).rolling(3).sum() >= 2
conditions = [
df["sell_signal_high"] # Append the condition, not an assignment
]
if "&-market_condition" in df.columns:
# 在超卖市场中减少卖出信号
conditions.append(df["&-market_condition"] != "oversold")
# 在超买市场中增强卖出信号
conditions.append(~((df["&-market_condition"] == "overbought") & (df["&-sell_signal"] < 0.7)))
df.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1
return df
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_roi: Dict[float, float], max_profit: float) -> Optional[Dict[float, float]]:
"""
动态调整持仓逻辑,基于 FreqAI 预测的 ROI 目标。
根据 Z-score 判断趋势强度,动态设置止盈时间窗口。
"""
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
last_candle = dataframe.iloc[-1]
# 获取预测的 ROI 目标 和 统计信息
roi_target = last_candle.get("&-roi_target", 0.0065)
mean_roi = last_candle.get("mean_roi", 0.0065)
std_roi = last_candle.get("std_roi", 0.0041)
# 计算 Z-score
z_score_roi = (roi_target - mean_roi) / std_roi if std_roi != 0 else 0
# 根据 Z-score 设置不同的 ROI 表
if z_score_roi > 1:
return {
"0": roi_target * 1.2,
"10": roi_target * 0.9,
"30": roi_target * 0.6,
"60": roi_target * 0.3,
"120": 0.01
}
elif z_score_roi > 0:
return {
"0": roi_target * 1.0,
"20": roi_target * 0.7,
"60": roi_target * 0.3,
"120": 0.01
}
else:
return {
"0": max(0.005, roi_target),
"60": 0.005,
"180": 0.005
}
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
动态止损回调函数,基于 FreqAI 预测的 &-stoploss_target 值。
"""
# 获取当前币种的 dataframe
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1]
# 获取预测的止损目标(使用 z-score 版本)
stoploss_z = last_candle.get("&-stoploss_target_z", -1.0) # 默认值为 -1.0(一个标准差)
# 获取模型统计信息用于自适应判断
mean_stop = -0.01
std_stop = 0.01
if self.freqai and hasattr(self.freqai, "data"):
labels_mean = self.freqai.data.labels_mean
labels_std = self.freqai.data.labels_std
mean_stop = labels_mean.get("&-stoploss_target", mean_stop)
std_stop = labels_std.get("&-stoploss_target", std_stop)
# 获取币种特定参数
params = self.PAIR_PARAMS.get(pair, {"volatility_factor": 1.0, "min_stop": -0.005})
volatility_factor = params["volatility_factor"]
min_stop = params["min_stop"]
# 动态止损下限 = 平均值 - volatility_factor * 标准差
dynamic_min_stop = min(min_stop, mean_stop - volatility_factor * std_stop)
# 如果 z-score < -1则说明当前止损偏移较大适当放宽
if stoploss_z < -1.0:
dynamic_min_stop = min(dynamic_min_stop * 1.2, -0.003)
# 最终止损值 = 模型建议值 和 动态下限中的较小者
dynamic_stoploss = dynamic_min_stop
return dynamic_stoploss