79% 年华

This commit is contained in:
zhangkun9038@dingtalk.com 2025-04-23 23:31:32 +08:00
parent a6ef1ce401
commit b41320a809
4 changed files with 101 additions and 63 deletions

View File

@ -67,7 +67,7 @@
"freqaimodel": "CatboostClassifier",
"purge_old_models": 2,
"train_period_days": 15,
"identifier": "test62",
"identifier": "test77",
"train_period_days": 30,
"backtest_period_days": 10,
"live_retrain_hours": 0,

View File

@ -60,13 +60,13 @@ services:
# --hyperopt-loss SharpeHyperOptLoss
# --spaces roi stoploss
# -e 200
# --config /freqtrade/templates/FreqaiExampleStrategy.json
command: >
backtesting
--logfile /freqtrade/user_data/logs/freqtrade.log
--freqaimodel LightGBMRegressor
--config /freqtrade/config_examples/config_freqai.okx.json
--config /freqtrade/templates/FreqaiExampleStrategy.json
--strategy-path /freqtrade/templates
--strategy FreqaiExampleStrategy
--timerange 20240920-20250420
--timerange 20240905-20250420

View File

@ -0,0 +1,32 @@
{
"strategy_name": "FreqaiExampleStrategy",
"params": {
"trailing": {
"trailing_stop": true,
"trailing_stop_positive": 0.01,
"trailing_stop_positive_offset": 0.02,
"trailing_only_offset_is_reached": false
},
"max_open_trades": {
"max_open_trades": 4
},
"buy": {
"buy_rsi": 39.92672300850069
},
"sell": {
"sell_rsi": 69.92672300850067
},
"protection": {},
"roi": {
"0": 0.132,
"8": 0.047,
"14": 0.007,
"60": 0
},
"stoploss": {
"stoploss": -0.322
}
},
"ft_stratparam_v": 1,
"export_time": "2025-04-23 12:30:05.550433+00:00"
}

View File

@ -1,5 +1,6 @@
import logging
import numpy as np
import pandas as pd
from functools import reduce
import talib.abstract as ta
from pandas import DataFrame
@ -9,24 +10,27 @@ 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 中动态设置
minimal_roi = {}
stoploss = 0.0
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 优化
# 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)
stoploss_param = DecimalParameter(low=-0.25, high=-0.05, default=-0.1, space="stoploss", optimize=True, load=True)
# 保护机制
protections = [
{"method": "StoplossGuard", "stop_duration": 60, "lookback_period": 120},
{"method": "MaxDrawdown", "lookback_period": 120, "max_allowed_drawdown": 0.05}
]
# FreqAI 配置
freqai_info = {
@ -44,7 +48,7 @@ class FreqaiExampleStrategy(IStrategy):
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.1,
"num_leaves": 31,
"num_leaves": 15, # 降低以减少警告
"verbose": -1,
},
}
@ -78,26 +82,26 @@ class FreqaiExampleStrategy(IStrategy):
dataframe["%-relative_volume-period"] = (
dataframe["volume"] / dataframe["volume"].rolling(period).mean()
)
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.ffill(inplace=True)
dataframe.fillna(0, inplace=True)
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
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"]
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.fillna(method='ffill', inplace=True)
dataframe.fillna(0, inplace=True)
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.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.fillna(method='ffill', inplace=True)
dataframe.fillna(0, inplace=True)
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
@ -108,17 +112,13 @@ class FreqaiExampleStrategy(IStrategy):
try:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 生成 %-volatility 特征
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
# 单一回归目标
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).shift(-label_period)
# 数据清理
for col in ["&-buy_rsi", "%-volatility"]:
dataframe[col].replace([np.inf, -np.inf], 0, inplace=True)
dataframe[col].fillna(method='ffill', inplace=True)
dataframe[col].fillna(0, inplace=True)
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():
logger.warning(f"目标列 {col} 仍包含 NaN检查数据生成逻辑")
except Exception as e:
@ -140,13 +140,16 @@ class FreqaiExampleStrategy(IStrategy):
dataframe["bb_upperband"] = bollinger["upper"]
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# 生成 up_or_down 信号(非 FreqAI 目标)
# 生成 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
)
# 动态设置参数
# 预填充 NaN
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
if "&-buy_rsi" in dataframe.columns:
# 派生其他目标
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
@ -154,53 +157,54 @@ class FreqaiExampleStrategy(IStrategy):
dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)
dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2)
# 限制预测值,添加平滑
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50)
dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean(), inplace=True)
if dataframe["buy_rsi_pred"].isna().any():
logger.warning("buy_rsi_pred 列包含 NaN已填充为默认值")
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90)
dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].mean(), inplace=True)
if dataframe["sell_rsi_pred"].isna().any():
logger.warning("sell_rsi_pred 列包含 NaN已填充为默认值")
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1)
dataframe["stoploss_pred"].fillna(dataframe["stoploss_pred"].mean(), inplace=True)
if dataframe["stoploss_pred"].isna().any():
logger.warning("stoploss_pred 列包含 NaN已填充为默认值")
# 计算预测值并减少 NaN
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5, min_periods=1).mean().clip(10, 50)
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5, min_periods=1).mean().clip(50, 90)
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.25, -0.05)
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
dataframe["roi_0_pred"].fillna(dataframe["roi_0_pred"].mean(), inplace=True)
if dataframe["roi_0_pred"].isna().any():
logger.warning("roi_0_pred 列包含 NaN已填充为默认值")
# 检查预测值
# 处理 NaN
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].fillna(dataframe[col].mean(), inplace=True)
mean_value = dataframe[col].mean()
if pd.isna(mean_value):
logger.warning(f"{col} 均值仍为 NaN使用默认值")
mean_value = {
"buy_rsi_pred": 30,
"sell_rsi_pred": 70,
"stoploss_pred": -0.1,
"roi_0_pred": 0.05,
"&-sell_rsi": 70,
"&-stoploss": -0.1,
"&-roi_0": 0.05
}.get(col, 0)
dataframe[col] = dataframe[col].fillna(mean_value)
# 动态追踪止盈
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4)
# 设置策略级参数
# 设置动态参数
self.stoploss = float(dataframe["stoploss_pred"].iloc[-1])
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
self.stoploss = float(self.stoploss_param.value)
self.minimal_roi = {
0: float(self.roi_0.value),
15: float(self.roi_15.value),
30: float(self.roi_30.value),
60: 0
60: 0.0
}
self.trailing_stop_positive = float(dataframe["trailing_stop_positive"].iloc[-1])
self.trailing_stop_positive_offset = float(dataframe["trailing_stop_positive_offset"].iloc[-1])
logger.info(f"minimal_roi 键:{list(self.minimal_roi.keys())}")
logger.info(f"动态参数buy_rsi={self.buy_rsi.value}, sell_rsi={self.sell_rsi.value}, "
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}")
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.fillna(method='ffill', inplace=True)
dataframe.fillna(0, inplace=True)
dataframe = dataframe.replace([np.inf, -np.inf], 0)
dataframe = dataframe.ffill()
dataframe = dataframe.fillna(0)
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()}")
@ -209,7 +213,7 @@ class FreqaiExampleStrategy(IStrategy):
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
enter_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"] + (5 if metadata["pair"] == "BTC/USDT" else 0)),
df["tema"] > df["tema"].shift(1),
df["volume"] > 0,
df["do_predict"] == 1,
@ -224,17 +228,19 @@ class FreqaiExampleStrategy(IStrategy):
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
(df["close"] < df["close"].shift(1) * 0.97),
(qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"])) |
(df["close"] < df["close"].shift(1) * 0.98) |
(df["close"] < df["bb_lowerband"]),
df["volume"] > 0,
df["do_predict"] == 1,
df["up_or_down"] == 0
]
if exit_long_conditions:
df.loc[
reduce(lambda x, y: x & y, exit_long_conditions),
"exit_long"
] = 1
time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
df.loc[
(reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
"exit_long"
] = 1
return df
def confirm_trade_entry(
@ -244,6 +250,6 @@ class FreqaiExampleStrategy(IStrategy):
df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = df.iloc[-1].squeeze()
if side == "long":
if rate > (last_candle["close"] * (1 + 0.0025)):
if rate > (last_candle["close"] * (1 + 0.001)):
return False
return True