stable1 11

This commit is contained in:
zhangkun9038@dingtalk.com 2025-04-28 20:49:31 +08:00
parent ec8447e116
commit bba64d33cb
2 changed files with 6 additions and 328 deletions

View File

@ -62,13 +62,14 @@
],
"freqai": {
"enabled": true,
"model_path": "/freqtrade/user_data/models",
"data_kitchen": {
"fillna": "ffill"
},
"freqaimodel": "XGBoostRegressor",
"model_training_parameters": {
"n_estimators": 200,
"learning_rate": 0.05,
"n_estimators": 300,
"learning_rate": 0.03,
"max_depth": 6,
"subsample": 0.8,
"colsample_bytree": 0.8,
@ -77,7 +78,7 @@
"eval_metric": "rmse",
"early_stopping_rounds": 20
},
"train_period_days": 365,
"train_period_days": 500,
"backtest_period_days": 90,
"live_retrain_hours": 0,
"feature_selection": {
@ -86,7 +87,7 @@
"feature_parameters": {
"include_timeframes": ["15m", "1h", "4h"],
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT"],
"label_period_candles": 10,
"label_period_candles": 20,
"include_shifted_candles": 2,
"weight_factor": 0.9,
"principal_component_analysis": false,
@ -123,4 +124,3 @@
}
}

View File

@ -1,326 +1,3 @@
import logging
import numpy as np
from functools import reduce
import talib.abstract as ta
from pandas import DataFrame
from technical import qtpylib
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
logger = logging.getLogger(__name__)
class FreqaiExampleStrategy(IStrategy):
# 移除硬编码的 minimal_roi 和 stoploss改为动态适配
minimal_roi = {} # 将在 populate_indicators 中动态生成
stoploss = 0.0 # 将在 populate_indicators 中动态设置
trailing_stop = True
process_only_new_candles = True
use_exit_signal = True
startup_candle_count: int = 40
can_short = False
# 参数定义FreqAI 动态适配 buy_rsi 和 sell_rsi禁用 Hyperopt 优化
buy_rsi = IntParameter(low=10, high=50, default=27, space="buy", optimize=False, load=True)
sell_rsi = IntParameter(low=50, high=90, default=59, space="sell", optimize=False, load=True)
# 为 Hyperopt 优化添加 ROI 和 stoploss 参数
roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.038, space="roi", optimize=True, load=True)
roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.027, space="roi", optimize=True, load=True)
roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.009, space="roi", optimize=True, load=True)
stoploss_param = DecimalParameter(low=-0.35, high=-0.1, default=-0.182, space="stoploss", optimize=True, load=True)
# FreqAI 配置
freqai_info = {
"model": "CatboostClassifier", # 与config保持一致
"feature_parameters": {
"include_timeframes": ["3m", "15m", "1h"], # 与config一致
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT"], # 添加相关交易对
"label_period_candles": 20, # 与config一致
"include_shifted_candles": 2, # 与config一致
},
"data_split_parameters": {
"test_size": 0.2,
"shuffle": True, # 启用shuffle
},
"model_training_parameters": {
"n_estimators": 100, # 减少树的数量
"learning_rate": 0.1, # 提高学习率
"max_depth": 6, # 限制树深度
"subsample": 0.8, # 添加子采样
"colsample_bytree": 0.8, # 添加特征采样
"objective": "reg:squarederror",
"eval_metric": "rmse",
"early_stopping_rounds": 20,
"verbose": 0,
},
"data_kitchen": {
"feature_parameters": {
"DI_threshold": 1.5, # 降低异常值过滤阈值
"use_DBSCAN_to_remove_outliers": False # 禁用DBSCAN
}
}
}
plot_config = {
"main_plot": {},
"subplots": {
"&-buy_rsi": {"&-buy_rsi": {"color": "green"}},
"&-sell_rsi": {"&-sell_rsi": {"color": "red"}},
"&-stoploss": {"&-stoploss": {"color": "purple"}},
"&-roi_0": {"&-roi_0": {"color": "orange"}},
"do_predict": {"do_predict": {"color": "brown"}},
},
}
def featcaure_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
# 保留关键的技术指标
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
def calculate_macd(self, dataframe: DataFrame) -> DataFrame:
"""
Centralized method to calculate MACD and ensure proper assignment to the dataframe.
"""
try:
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
# Ensure no NaN or infinite values in MACD columns
dataframe["macd"] = dataframe["macd"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
dataframe["macdsignal"] = dataframe["macdsignal"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
logger.info("MACD 列已成功计算并验证。")
except Exception as e:
logger.error(f"计算 MACD 列时出错:{str(e)}")
dataframe["macd"] = np.nan
dataframe["macdsignal"] = np.nan
return dataframe
# 使用 centralized 方法计算 MACD
dataframe = self.calculate_macd(dataframe)
# 检查 MACD 列是否存在
if "macd" not in dataframe.columns or "macdsignal" not in dataframe.columns:
logger.error("MACD 或 MACD 信号列缺失,无法生成买入信号。尝试重新计算 MACD 列。")
try:
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
logger.info("MACD 列已成功重新计算。")
except Exception as e:
logger.error(f"重新计算 MACD 列时出错:{str(e)}")
raise ValueError("dataframe 缺少必要的 MACD 列且无法重新计算。")
logger.error("MACD 或 MACD 信号列缺失,无法生成买入信号")
raise ValueError("DataFrame 缺少必要的 MACD 列")
# 确保 MACD 列存在
if "macd" not in dataframe.columns or "macdsignal" not in dataframe.columns:
logger.error("MACD 或 MACD 信号列缺失,无法生成买入信号")
raise ValueError("DataFrame 缺少必要的 MACD 列")
# 保留布林带相关特征
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"]
dataframe["bb_upperband"] = bollinger["upper"]
# 保留成交量相关特征
dataframe["volume_ma"] = dataframe["volume"].rolling(window=20).mean()
# 数据清理
for col in dataframe.columns:
if dataframe[col].dtype in ["float64", "int64"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan)
dataframe[col] = dataframe[col].ffill().fillna(0)
logger.info(f"特征工程完成,特征数量:{len(dataframe.columns)}")
return dataframe
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-raw_volume"] = dataframe["volume"]
dataframe["%-raw_price"] = dataframe["close"]
# 数据清理逻辑
for col in dataframe.columns:
if dataframe[col].dtype in ["float64", "int64"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
dataframe[col] = dataframe[col].ffill()
dataframe[col] = dataframe[col].fillna(0)
# 检查是否仍有无效值
if dataframe[col].isna().any() or np.isinf(dataframe[col]).any():
logger.warning(f"{col} 仍包含无效值,已填充为默认值")
dataframe[col] = dataframe[col].fillna(0)
return dataframe
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.ffill(inplace=True)
dataframe.fillna(0, inplace=True)
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}")
if "close" not in dataframe.columns:
logger.error("数据框缺少必要的 'close'")
raise ValueError("数据框缺少必要的 'close'")
try:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 定义目标变量为未来价格变化百分比(连续值)
dataframe["up_or_down"] = (
dataframe["close"].shift(-label_period) - dataframe["close"]
) / dataframe["close"]
# 数据清理:处理 NaN 和 Inf 值
dataframe["up_or_down"] = dataframe["up_or_down"].replace([np.inf, -np.inf], np.nan)
dataframe["up_or_down"] = dataframe["up_or_down"].ffill().fillna(0)
# 确保目标变量是二维数组
if dataframe["up_or_down"].ndim == 1:
dataframe["up_or_down"] = dataframe["up_or_down"].values.reshape(-1, 1)
# 检查并处理 NaN 或无限值
dataframe["up_or_down"] = dataframe["up_or_down"].replace([np.inf, -np.inf], np.nan)
dataframe["up_or_down"] = dataframe["up_or_down"].ffill().fillna(0)
# 生成 %-volatility 特征
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
# 确保 &-buy_rsi 列的值计算正确
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14)
# 数据清理
for col in ["&-buy_rsi", "up_or_down", "%-volatility"]:
# 使用直接操作避免链式赋值
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan)
dataframe[col] = dataframe[col].ffill() # 替代 fillna(method='ffill')
dataframe[col] = dataframe[col].fillna(dataframe[col].mean()) # 使用均值填充 NaN 值
if dataframe[col].isna().any():
logger.warning(f"目标列 {col} 仍包含 NaN填充为默认值")
except Exception as e:
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
raise
# Log the shape of the target variable for debugging
logger.info(f"目标列形状:{dataframe['up_or_down'].shape}")
logger.info(f"目标列预览:\n{dataframe[['up_or_down', '&-buy_rsi']].head().to_string()}")
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
logger.info(f"处理交易对:{metadata['pair']}")
dataframe = self.freqai.start(dataframe, metadata, self)
# 计算传统指标
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"]
dataframe["bb_upperband"] = bollinger["upper"]
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# 生成 up_or_down 信号(非 FreqAI 目标)
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 使用未来价格变化方向生成 up_or_down 信号
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
dataframe["up_or_down"] = np.where(
dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0
)
# 动态设置参数
if "&-buy_rsi" in dataframe.columns:
# 派生其他目标
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
# Ensure proper calculation and handle potential NaN values
dataframe["&-stoploss"] = (-0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)).fillna(-0.1)
dataframe["&-roi_0"] = ((dataframe["close"] / dataframe["close"].shift(label_period) - 1).clip(0, 0.2)).fillna(0)
# Additional check to ensure no NaN values remain
for col in ["&-stoploss", "&-roi_0"]:
if dataframe[col].isna().any():
logger.warning(f"{col} 仍包含 NaN填充为默认值")
dataframe[col] = dataframe[col].fillna(-0.1 if col == "&-stoploss" else 0)
# 简化动态参数生成逻辑
# 放松 buy_rsi 和 sell_rsi 的生成逻辑
# 计算 buy_rsi_pred 并清理 NaN 值
dataframe["buy_rsi_pred"] = dataframe["rsi"].rolling(window=10).mean().clip(30, 50)
dataframe["buy_rsi_pred"] = dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].median())
# 计算 sell_rsi_pred 并清理 NaN 值
dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 20
dataframe["sell_rsi_pred"] = dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].median())
# 计算 stoploss_pred 并清理 NaN 值
dataframe["stoploss_pred"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)
dataframe["stoploss_pred"] = dataframe["stoploss_pred"].fillna(dataframe["stoploss_pred"].mean())
# 计算 roi_0_pred 并清理 NaN 值
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
dataframe["roi_0_pred"] = dataframe["roi_0_pred"].fillna(dataframe["roi_0_pred"].mean())
# 检查预测值
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
if dataframe[col].isna().any():
logger.warning(f"{col} 包含 NaN填充为默认值")
dataframe[col] = dataframe[col].fillna(dataframe[col].mean())
# 更保守的止损和止盈设置
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.3).clip(0.01, 0.2)
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
# 设置策略级参数
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
# 更保守的止损设置
self.stoploss = -0.15 # 固定止损 15%
self.minimal_roi = {
0: float(self.roi_0.value),
15: float(self.roi_15.value),
30: float(self.roi_30.value),
60: 0
}
# 更保守的追踪止损设置
self.trailing_stop_positive = 0.05 # 追踪止损触发点
self.trailing_stop_positive_offset = 0.1 # 追踪止损偏移量
exit_long_conditions = [
(dataframe["rsi"] > dataframe["sell_rsi_pred"]), # RSI 高于卖出阈值
(dataframe["volume"] > dataframe["volume"].rolling(window=10).mean()), # 成交量高于近期均值
(dataframe["close"] < dataframe["bb_middleband"]), # 价格低于布林带中轨
(dataframe["close"] < dataframe["bb_lowerband"].shift(1)), # 当前价格低于上一周期的布林带下轨
(dataframe["volume"] < dataframe["volume"].shift(1) * 0.9) # 当前成交量低于上一周期的10%
]
dataframe.ffill(inplace=True)
dataframe.fillna(0, inplace=True)
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 改进卖出信号条件
exit_long_conditions = [
(dataframe["rsi"] > dataframe["sell_rsi_pred"]), # RSI 高于卖出阈值
(
(dataframe["volume"] > dataframe["volume"].rolling(window=10).mean()) | # 成交量高于近期均值
(dataframe["volume"] < dataframe["volume"].shift(1) * 0.9) # 当前成交量低于上一周期的10%
),
(
(dataframe["close"] < dataframe["bb_middleband"]) | # 价格低于布林带中轨
(dataframe["close"] < dataframe["bb_lowerband"].shift(1)) # 当前价格低于上一周期的布林带下轨
)
]
if exit_long_conditions:
dataframe.loc[
reduce(lambda x, y: x & y, exit_long_conditions),
"exit_long"
] = 1
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# 改进买入信号条件
@ -367,3 +44,4 @@ class FreqaiExampleStrategy(IStrategy):
return False
return True