diff --git a/### **4. 测试优化后的策略** b/### **4. 测试优化后的策略** deleted file mode 100644 index 3693000a..00000000 --- a/### **4. 测试优化后的策略** +++ /dev/null @@ -1,5 +0,0 @@ - -<<<<<<< HEAD -======= -freqtrade backtesting --strategy FreqaiExampleStrategy --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --timerange 20230101-20230401 ->>>>>>> Snippet diff --git a/#### **3. 检查目标变量** b/#### **3. 检查目标变量** deleted file mode 100644 index 63f98aa1..00000000 --- a/#### **3. 检查目标变量** +++ /dev/null @@ -1,6 +0,0 @@ - -<<<<<<< HEAD -======= -dataframe['target'] = np.where(short_ma > long_ma, 2, - np.where(short_ma < long_ma, 0, 1)) ->>>>>>> Snippet diff --git a/03a54a5b0ae8efd49daf13c408a37a2d3c7816bf.diff b/03a54a5b0ae8efd49daf13c408a37a2d3c7816bf.diff deleted file mode 100644 index 785769de..00000000 --- a/03a54a5b0ae8efd49daf13c408a37a2d3c7816bf.diff +++ /dev/null @@ -1,264 +0,0 @@ -diff --git "a/4. **\346\270\205\347\220\206\347\274\223\345\255\230**\357\274\232" "b/4. **\346\270\205\347\220\206\347\274\223\345\255\230**\357\274\232" -new file mode 100644 -index 0000000..3bb7671 ---- /dev/null -+++ "b/4. **\346\270\205\347\220\206\347\274\223\345\255\230**\357\274\232" -@@ -0,0 +1,5 @@ -+ -+<<<<<<< HEAD -+======= -+ rm -rf /freqtrade/user_data/models/test62/ -+>>>>>>> Snippet -diff --git "a/5. **\351\207\215\346\226\260\350\256\255\347\273\203**\357\274\232" "b/5. **\351\207\215\346\226\260\350\256\255\347\273\203**\357\274\232" -new file mode 100644 -index 0000000..8a18d3d ---- /dev/null -+++ "b/5. **\351\207\215\346\226\260\350\256\255\347\273\203**\357\274\232" -@@ -0,0 +1,5 @@ -+ -+<<<<<<< HEAD -+======= -+ freqtrade trade --config config_examples/config_freqai.okx.json --strategy FreqaiExampleStrategy -+>>>>>>> Snippet -diff --git a/config_examples/config_freqai.okx.json b/config_examples/config_freqai.okx.json -index 1816983..4535010 100644 ---- a/config_examples/config_freqai.okx.json -+++ b/config_examples/config_freqai.okx.json -@@ -67,45 +67,31 @@ - "freqaimodel": "CatboostClassifier", - "purge_old_models": 2, - "train_period_days": 15, -- "identifier": "test62", -- "train_period_days": 30, -- "backtest_period_days": 10, -+ "train_period_days": 180, -+ "backtest_period_days": 60, - "live_retrain_hours": 0, - "feature_selection": { - "method": "recursive_elimination" - }, - "feature_parameters": { -- "include_timeframes": [ -- "3m", -- "15m", -- "1h" -- ], -- "include_corr_pairlist": [ -- "BTC/USDT", -- "SOL/USDT" -- ], -- "label_period_candles": 20, -- "include_shifted_candles": 2, -- "DI_threshold": 0.9, -+ "include_timeframes": ["15m"], -+ "include_corr_pairlist": ["BTC/USDT"], -+ "label_period_candles": 10, -+ "include_shifted_candles": 1, -+ "DI_threshold": 0.7, - "weight_factor": 0.9, - "principal_component_analysis": false, - "use_SVM_to_remove_outliers": false, -- "indicator_periods_candles": [ -- 10, -- 20, -- 50 -- ], -- "plot_feature_importances": 0 -+ "indicator_periods_candles": [14], - }, - "data_split_parameters": { - "test_size": 0.2 - }, -- "model_training_parameters": { -- "n_estimators": 100, -- "learning_rate": 0.05, -- "max_depth": 5, -- "num_leaves": 31 -- } -+ "model_training_parameters": { -+ "n_estimators": 100, -+ "learning_rate": 0.05, -+ "max_depth": 5 -+ } - }, - "api_server": { - "enabled": true, -diff --git a/docker-compose.yml b/docker-compose.yml -index defe81e..aeb31e6 100644 ---- a/docker-compose.yml -+++ b/docker-compose.yml -@@ -64,7 +64,7 @@ services: - command: > - backtesting - --logfile /freqtrade/user_data/logs/freqtrade.log -- --freqaimodel LightGBMRegressor -+ --freqaimodel XGBoostRegressor - --config /freqtrade/config_examples/config_freqai.okx.json - --config /freqtrade/templates/FreqaiExampleStrategy.json - --strategy-path /freqtrade/templates -diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py -index 688e644..e27e17b 100644 ---- a/freqtrade/templates/FreqaiExampleStrategy.py -+++ b/freqtrade/templates/FreqaiExampleStrategy.py -@@ -30,12 +30,12 @@ class FreqaiExampleStrategy(IStrategy): - - # FreqAI 配置 - freqai_info = { -- "model": "XGBoostRegressor", # 改用XGBoost -+ "model": "CatboostClassifier", # 与config保持一致 - "feature_parameters": { -- "include_timeframes": ["5m", "15m", "1h"], -- "include_corr_pairlist": [], -- "label_period_candles": 12, -- "include_shifted_candles": 3, -+ "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, -@@ -72,54 +72,26 @@ class FreqaiExampleStrategy(IStrategy): - } - - def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: -- # 添加更多技术指标 -+ # 保留关键的技术指标 - dataframe["%-rsi"] = ta.RSI(dataframe, timeperiod=14) -- dataframe["%-mfi"] = ta.MFI(dataframe, timeperiod=14) -- dataframe["%-sma"] = ta.SMA(dataframe, timeperiod=20) -- dataframe["%-ema"] = ta.EMA(dataframe, timeperiod=20) -- dataframe["%-adx"] = ta.ADX(dataframe, timeperiod=14) -- dataframe["%-atr"] = ta.ATR(dataframe, timeperiod=14) -- dataframe["%-obv"] = ta.OBV(dataframe) -- dataframe["%-cci"] = ta.CCI(dataframe, timeperiod=20) -- dataframe["%-stoch"] = ta.STOCH(dataframe)['slowk'] -- dataframe["%-macd"] = ta.MACD(dataframe)['macd'] -- dataframe["%-macdsignal"] = ta.MACD(dataframe)['macdsignal'] -- dataframe["%-macdhist"] = ta.MACD(dataframe)['macdhist'] -- dataframe["%-willr"] = ta.WILLR(dataframe, timeperiod=14) -- dataframe["%-ultosc"] = ta.ULTOSC(dataframe) -- dataframe["%-trix"] = ta.TRIX(dataframe, timeperiod=14) -- dataframe["%-ad"] = ta.ADOSC(dataframe) -- dataframe["%-mom"] = ta.MOM(dataframe, timeperiod=10) -- dataframe["%-roc"] = ta.ROC(dataframe, timeperiod=10) -- -- # 添加布林带相关特征 -+ macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) -+ dataframe["%-macd"] = macd["macd"] -+ dataframe["%-macdsignal"] = macd["macdsignal"] -+ -+ # 保留布林带相关特征 - 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["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"] - dataframe["bb_pct"] = (dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) -- -- # 添加成交量相关特征 -+ -+ # 保留成交量相关特征 - dataframe["volume_ma"] = dataframe["volume"].rolling(window=20).mean() - dataframe["volume_roc"] = dataframe["volume"].pct_change(periods=10) -- dataframe["volume_obv"] = ta.OBV(dataframe) -- -- # 添加价格相关特征 -- dataframe["close_ma"] = dataframe["close"].rolling(window=20).mean() -+ -+ # 保留价格变化率 - dataframe["close_roc"] = dataframe["close"].pct_change(periods=10) -- dataframe["close_log_ret"] = np.log(dataframe["close"]).diff() -- dataframe["close_zscore"] = (dataframe["close"] - dataframe["close"].rolling(window=20).mean()) / dataframe["close"].rolling(window=20).std() -- -- # 添加时间相关特征 -- dataframe["hour"] = dataframe["date"].dt.hour -- dataframe["day_of_week"] = dataframe["date"].dt.dayofweek -- dataframe["is_weekend"] = dataframe["day_of_week"].isin([5, 6]).astype(int) -- -- # 添加波动率相关特征 -- dataframe["volatility"] = dataframe["close"].pct_change().rolling(window=20).std() -- dataframe["volatility_ma"] = dataframe["volatility"].rolling(window=20).mean() -- dataframe["volatility_roc"] = dataframe["volatility"].pct_change(periods=10) - - # 改进数据清理 - for col in dataframe.columns: -@@ -170,16 +142,23 @@ class FreqaiExampleStrategy(IStrategy): - - try: - label_period = self.freqai_info["feature_parameters"]["label_period_candles"] -+ -+ # 生成更复杂的目标变量 up_or_down -+ dataframe["up_or_down"] = np.where( -+ dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0 -+ ) -+ # 确保目标变量是二维数组 -+ if dataframe["up_or_down"].ndim == 1: -+ dataframe["up_or_down"] = dataframe["up_or_down"].values.reshape(-1, 1) -+ - # 生成 %-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"]: -+ 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') -@@ -187,19 +166,13 @@ class FreqaiExampleStrategy(IStrategy): - if dataframe[col].isna().any(): - logger.warning(f"目标列 {col} 仍包含 NaN,填充为默认值") - -- # 数据清理 -- for col in ["&-buy_rsi", "%-volatility"]: -- # 使用直接操作避免链式赋值 -- dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0) -- dataframe[col] = dataframe[col].ffill() # 替代 fillna(method='ffill') -- dataframe[col] = dataframe[col].fillna(0) -- if dataframe[col].isna().any(): -- logger.warning(f"目标列 {col} 仍包含 NaN,检查数据生成逻辑") - except Exception as e: - logger.error(f"创建 FreqAI 目标失败:{str(e)}") - raise - -- logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}") -+ # 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: -@@ -237,13 +210,13 @@ class FreqaiExampleStrategy(IStrategy): - dataframe[col] = dataframe[col].fillna(-0.1 if col == "&-stoploss" else 0) - - # 简化动态参数生成逻辑 --# 简化 buy_rsi 和 sell_rsi 的生成逻辑 -+ # 放松 buy_rsi 和 sell_rsi 的生成逻辑 - # 计算 buy_rsi_pred 并清理 NaN 值 -- dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(window=10).mean().clip(20, 40) -+ dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(window=10).mean().clip(30, 50) - dataframe["buy_rsi_pred"] = dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean()) - - # 计算 sell_rsi_pred 并清理 NaN 值 -- dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 20 -+ dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 40 - dataframe["sell_rsi_pred"] = dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].mean()) - - # 计算 stoploss_pred 并清理 NaN 值 -@@ -308,8 +281,9 @@ class FreqaiExampleStrategy(IStrategy): - # 改进买入信号条件 - enter_long_conditions = [ - (df["rsi"] < df["buy_rsi_pred"]), # RSI 低于买入阈值 -- (df["volume"] > df["volume"].rolling(window=10).mean()), # 成交量高于近期均值 -- (df["close"] > df["bb_middleband"]) # 价格高于布林带中轨 -+ (df["volume"] > df["volume"].rolling(window=10).mean() * 1.2), # 成交量高于近期均值20% -+ (df["close"] > df["bb_middleband"]), # 价格高于布林带中轨 -+ (df["do_predict"] == 1) # 确保模型预测为买入 - ] - if enter_long_conditions: - df.loc[ diff --git a/4. **清理缓存**: b/4. **清理缓存**: deleted file mode 100644 index 3bb76716..00000000 --- a/4. **清理缓存**: +++ /dev/null @@ -1,5 +0,0 @@ - -<<<<<<< HEAD -======= - rm -rf /freqtrade/user_data/models/test62/ ->>>>>>> Snippet diff --git a/5. **重新训练**: b/5. **重新训练**: deleted file mode 100644 index 8a18d3d4..00000000 --- a/5. **重新训练**: +++ /dev/null @@ -1,5 +0,0 @@ - -<<<<<<< HEAD -======= - freqtrade trade --config config_examples/config_freqai.okx.json --strategy FreqaiExampleStrategy ->>>>>>> Snippet diff --git a/e6402d0.diff b/e6402d0.diff deleted file mode 100644 index 06a60c49..00000000 --- a/e6402d0.diff +++ /dev/null @@ -1,407 +0,0 @@ -diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py -index 00ff1d2..40a1adc 100644 ---- a/freqtrade/templates/FreqaiExampleStrategy.py -+++ b/freqtrade/templates/FreqaiExampleStrategy.py -@@ -9,44 +9,47 @@ 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 -+ minimal_roi = {} # populate_indicators 中动态设定 -+ stoploss = -0.15 # 默认固定止损 - trailing_stop = True -+ trailing_stop_positive = 0.05 -+ trailing_stop_positive_offset = 0.1 - 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.35, high=-0.1, default=-0.182, space="stoploss", optimize=True, load=True -+ ) - - # FreqAI 配置 - freqai_info = { -- "model": "CatboostClassifier", # 与config保持一致 -+ "model": "CatboostClassifier", - "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一致 -+ "include_timeframes": ["3m", "15m", "1h"], -+ "include_corr_pairlist": ["BTC/USDT", "SOL/USDT"], -+ "label_period_candles": 20, -+ "include_shifted_candles": 2, - }, - "data_split_parameters": { - "test_size": 0.2, -- "shuffle": True, # 启用shuffle -+ "shuffle": True, - }, - "model_training_parameters": { -- "n_estimators": 100, # 减少树的数量 -- "learning_rate": 0.1, # 提高学习率 -- "max_depth": 6, # 限制树深度 -- "subsample": 0.8, # 添加子采样 -- "colsample_bytree": 0.8, # 添加特征采样 -+ "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, -@@ -54,8 +57,8 @@ class FreqaiExampleStrategy(IStrategy): - }, - "data_kitchen": { - "feature_parameters": { -- "DI_threshold": 1.5, # 降低异常值过滤阈值 -- "use_DBSCAN_to_remove_outliers": False # 禁用DBSCAN -+ "DI_threshold": 1.5, -+ "use_DBSCAN_to_remove_outliers": False - } - } - } -@@ -72,265 +75,113 @@ class FreqaiExampleStrategy(IStrategy): - } - - def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: -- # 保留关键的技术指标 -+ # RSI 计算 - dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) - -- # 确保 MACD 列被正确计算并保留 -+ # MACD 计算并容错 - try: - macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) - dataframe["macd"] = macd["macd"] - dataframe["macdsignal"] = macd["macdsignal"] - except Exception as e: -- logger.error(f"计算 MACD 列时出错:{str(e)}") -+ logger.error(f"MACD 计算失败: {e}") - dataframe["macd"] = np.nan - dataframe["macdsignal"] = np.nan - -- # 检查 MACD 列是否存在 -- if "macd" not in dataframe.columns or "macdsignal" not in dataframe.columns: -- 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)}") -+ dataframe[col] = dataframe[col].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"] --# 数据清理逻辑 -+ -+ # 数据清理 - 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 -+ dataframe[col] = dataframe[col].ffill().fillna(0) - -- # 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) -+ def set_freqai_targets(self, df: DataFrame, metadata: dict) -> DataFrame: -+ """定义标签""" -+ df["&-up_or_down"] = np.where(df["close"].shift(-20) > df["close"], 1, 0) -+ return df - -- # 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()) -+ def populate_indicators(self, df: DataFrame, metadata: dict) -> DataFrame: -+ # 特征工程调用 -+ df = self.feature_engineering_expand_all(df, period=14, metadata=metadata) -+ df = self.feature_engineering_expand_basic(df, metadata=metadata) -+ df = self.set_freqai_targets(df, metadata) -+ -+ # 动态参数预测 -+ df["buy_rsi_pred"] = df["rsi"].rolling(window=10).median().clip(20, 45) -+ df["sell_rsi_pred"] = df["buy_rsi_pred"] + 20 -+ df["stoploss_pred"] = -0.1 - (df["%-pct-change"].abs().rolling(20).std() * 10).clip(0.05, 0.25) -+ df["roi_0_pred"] = self.roi_0.value * 1.2 -+ # 添加 do_predict 列(示例:每5个周期中使用3个进行预测) -+ df['do_predict'] = 0 -+ df.loc[df.index % 5 <= 2, 'do_predict'] = 1 # 每5根K线中前3根设为1 -+ -+ df.fillna(0, inplace=True) -+ -+ # 更新策略参数 -+ self.buy_rsi.value = float(df["buy_rsi_pred"].iloc[-1]) -+ self.sell_rsi.value = float(df["sell_rsi_pred"].iloc[-1]) -+ self.stoploss = float(df["stoploss_pred"].iloc[-1]) -+ -+ self.minimal_roi = { -+ 0: float(self.roi_0.value), -+ 15: float(self.roi_15.value), -+ 30: float(self.roi_30.value), -+ 60: 0 -+ } - -- # 计算 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()) -+ return df - -- # 计算 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()) -+ def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: -+ conditions = [ -+ (df["rsi"] < df["buy_rsi_pred"]), -+ (df["volume"] > df["volume_ma"] * 1.2), -+ (df["close"] > df["bb_middleband"]), -+ (df["macd"] > df["macdsignal"]), -+ (df["do_predict"] == 1), -+ ] - -- # 计算 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 # 追踪止损偏移量 -- -- 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.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 -+ df.loc[reduce(lambda x, y: x & y, conditions), ['enter_long', 'enter_tag']] = (1, 'long_entry') -+ return df - - def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: --# 改进卖出信号条件 -- exit_long_conditions = [ -- (df["rsi"] > df["sell_rsi_pred"]), # RSI 高于卖出阈值 -- (df["volume"] > df["volume"].rolling(window=10).mean()), # 成交量高于近期均值 -- (df["close"] < df["bb_middleband"]) # 价格低于布林带中轨 -+ conditions = [ -+ (df["rsi"] > df["sell_rsi_pred"]), -+ (df["close"] < df["bb_middleband"]), -+ (df["do_predict"] == 0), - ] -- if exit_long_conditions: -- df.loc[ -- reduce(lambda x, y: x & y, exit_long_conditions), -- "exit_long" -- ] = 1 -+ df.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1 - return df -- def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: -- # 改进买入信号条件 -- # 检查 MACD 列是否存在 -- if "macd" not in df.columns or "macdsignal" not in df.columns: -- logger.error("MACD 或 MACD 信号列缺失,无法生成买入信号。尝试重新计算 MACD 列。") -- -- try: -- macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9) -- df["macd"] = macd["macd"] -- df["macdsignal"] = macd["macdsignal"] -- logger.info("MACD 列已成功重新计算。") -- except Exception as e: -- logger.error(f"重新计算 MACD 列时出错:{str(e)}") -- raise ValueError("DataFrame 缺少必要的 MACD 列且无法重新计算。") - -- enter_long_conditions = [ -- (df["rsi"] < df["buy_rsi_pred"]), # RSI 低于买入阈值 -- (df["volume"] > df["volume"].rolling(window=10).mean() * 1.2), # 成交量高于近期均值20% -- (df["close"] > df["bb_middleband"]) # 价格高于布林带中轨 -- ] -- -- # 如果 MACD 列存在,则添加 MACD 金叉条件 -- if "macd" in df.columns and "macdsignal" in df.columns: -- enter_long_conditions.append((df["macd"] > df["macdsignal"])) -- -- # 确保模型预测为买入 -- enter_long_conditions.append((df["do_predict"] == 1)) -- if enter_long_conditions: -- df.loc[ -- reduce(lambda x, y: x & y, enter_long_conditions), -- ["enter_long", "enter_tag"] -- ] = (1, "long") -- return df - def confirm_trade_entry( - self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time, entry_tag, side: str, **kwargs - ) -> bool: - df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) -- last_candle = df.iloc[-1].squeeze() -+ last_candle = df.iloc[-1] - if side == "long": -- if rate > (last_candle["close"] * (1 + 0.0025)): -+ if rate > (last_candle["close"] * 1.0025): # 价格超过最新价 0.25% 则拒绝下单 - return False - return True diff --git a/freqtrade/abc.py b/freqtrade/abc.py deleted file mode 100644 index a1d8d2c5..00000000 --- a/freqtrade/abc.py +++ /dev/null @@ -1,336 +0,0 @@ -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 feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: - # 保留关键的技术指标 - dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) - - # 确保 MACD 列被正确计算并保留 - try: - macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) - dataframe["macd"] = macd["macd"] - dataframe["macdsignal"] = macd["macdsignal"] - except Exception as e: - logger.error(f"计算 MACD 列时出错:{str(e)}") - dataframe["macd"] = np.nan - dataframe["macdsignal"] = np.nan - - # 检查 MACD 列是否存在 - if "macd" not in dataframe.columns or "macdsignal" not in dataframe.columns: - 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 # 追踪止损偏移量 - - 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.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, df: DataFrame, metadata: dict) -> DataFrame: -# 改进卖出信号条件 - exit_long_conditions = [ - (df["rsi"] > df["sell_rsi_pred"]), # RSI 高于卖出阈值 - (df["volume"] > df["volume"].rolling(window=10).mean()), # 成交量高于近期均值 - (df["close"] < df["bb_middleband"]) # 价格低于布林带中轨 - ] - if exit_long_conditions: - df.loc[ - reduce(lambda x, y: x & y, exit_long_conditions), - "exit_long" - ] = 1 - return df - def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - # 改进买入信号条件 - # 检查 MACD 列是否存在 - if "macd" not in df.columns or "macdsignal" not in df.columns: - logger.error("MACD 或 MACD 信号列缺失,无法生成买入信号。尝试重新计算 MACD 列。") - - try: - macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9) - df["macd"] = macd["macd"] - df["macdsignal"] = macd["macdsignal"] - logger.info("MACD 列已成功重新计算。") - except Exception as e: - logger.error(f"重新计算 MACD 列时出错:{str(e)}") - raise ValueError("DataFrame 缺少必要的 MACD 列且无法重新计算。") - - enter_long_conditions = [ - (df["rsi"] < df["buy_rsi_pred"]), # RSI 低于买入阈值 - (df["volume"] > df["volume"].rolling(window=10).mean() * 1.2), # 成交量高于近期均值20% - (df["close"] > df["bb_middleband"]) # 价格高于布林带中轨 - ] - - # 如果 MACD 列存在,则添加 MACD 金叉条件 - if "macd" in df.columns and "macdsignal" in df.columns: - enter_long_conditions.append((df["macd"] > df["macdsignal"])) - - # 确保模型预测为买入 - enter_long_conditions.append((df["do_predict"] == 1)) - if enter_long_conditions: - df.loc[ - reduce(lambda x, y: x & y, enter_long_conditions), - ["enter_long", "enter_tag"] - ] = (1, "long") - return df - def confirm_trade_entry( - self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time, entry_tag, side: str, **kwargs - ) -> bool: - 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)): - return False - return True diff --git a/freqtrade/new.py b/freqtrade/new.py deleted file mode 100644 index 6c92c207..00000000 --- a/freqtrade/new.py +++ /dev/null @@ -1,295 +0,0 @@ -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 - - # 可训练参数(用于 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) - - 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", - "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, - }, - "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 - } - } - } - - 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 feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: - # RSI 计算 - dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) - - # MACD 计算并容错 - try: - macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) - dataframe["macd"] = macd["macd"] - dataframe["macdsignal"] = macd["macdsignal"] - except Exception as e: - logger.error(f"MACD 计算失败: {e}") - dataframe["macd"] = np.nan - dataframe["macdsignal"] = np.nan - - # 检查 MACD 列是否存在 - if "macd" not in dataframe.columns or "macdsignal" not in dataframe.columns: - 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) - - 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 - - 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 # 追踪止损偏移量 - - 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.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, df: DataFrame, metadata: dict) -> DataFrame: -# 改进卖出信号条件 - exit_long_conditions = [ - (df["rsi"] > df["sell_rsi_pred"]), # RSI 高于卖出阈值 - (df["volume"] > df["volume"].rolling(window=10).mean()), # 成交量高于近期均值 - (df["close"] < df["bb_middleband"]) # 价格低于布林带中轨 - df.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1 - return df - - def confirm_trade_entry( - self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time, entry_tag, side: str, **kwargs - ) -> bool: - df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - last_candle = df.iloc[-1] - if side == "long": - if rate > (last_candle["close"] * 1.0025): # 价格超过最新价 0.25% 则拒绝下单 - return False - return True diff --git a/freqtrade/old.py b/freqtrade/old.py deleted file mode 100644 index a1d8d2c5..00000000 --- a/freqtrade/old.py +++ /dev/null @@ -1,336 +0,0 @@ -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 feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: - # 保留关键的技术指标 - dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) - - # 确保 MACD 列被正确计算并保留 - try: - macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) - dataframe["macd"] = macd["macd"] - dataframe["macdsignal"] = macd["macdsignal"] - except Exception as e: - logger.error(f"计算 MACD 列时出错:{str(e)}") - dataframe["macd"] = np.nan - dataframe["macdsignal"] = np.nan - - # 检查 MACD 列是否存在 - if "macd" not in dataframe.columns or "macdsignal" not in dataframe.columns: - 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 # 追踪止损偏移量 - - 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.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, df: DataFrame, metadata: dict) -> DataFrame: -# 改进卖出信号条件 - exit_long_conditions = [ - (df["rsi"] > df["sell_rsi_pred"]), # RSI 高于卖出阈值 - (df["volume"] > df["volume"].rolling(window=10).mean()), # 成交量高于近期均值 - (df["close"] < df["bb_middleband"]) # 价格低于布林带中轨 - ] - if exit_long_conditions: - df.loc[ - reduce(lambda x, y: x & y, exit_long_conditions), - "exit_long" - ] = 1 - return df - def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - # 改进买入信号条件 - # 检查 MACD 列是否存在 - if "macd" not in df.columns or "macdsignal" not in df.columns: - logger.error("MACD 或 MACD 信号列缺失,无法生成买入信号。尝试重新计算 MACD 列。") - - try: - macd = ta.MACD(df, fastperiod=12, slowperiod=26, signalperiod=9) - df["macd"] = macd["macd"] - df["macdsignal"] = macd["macdsignal"] - logger.info("MACD 列已成功重新计算。") - except Exception as e: - logger.error(f"重新计算 MACD 列时出错:{str(e)}") - raise ValueError("DataFrame 缺少必要的 MACD 列且无法重新计算。") - - enter_long_conditions = [ - (df["rsi"] < df["buy_rsi_pred"]), # RSI 低于买入阈值 - (df["volume"] > df["volume"].rolling(window=10).mean() * 1.2), # 成交量高于近期均值20% - (df["close"] > df["bb_middleband"]) # 价格高于布林带中轨 - ] - - # 如果 MACD 列存在,则添加 MACD 金叉条件 - if "macd" in df.columns and "macdsignal" in df.columns: - enter_long_conditions.append((df["macd"] > df["macdsignal"])) - - # 确保模型预测为买入 - enter_long_conditions.append((df["do_predict"] == 1)) - if enter_long_conditions: - df.loc[ - reduce(lambda x, y: x & y, enter_long_conditions), - ["enter_long", "enter_tag"] - ] = (1, "long") - return df - def confirm_trade_entry( - self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time, entry_tag, side: str, **kwargs - ) -> bool: - 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)): - return False - return True diff --git a/result/backtest-result-2025-05-01_04-27-37.zip b/result/backtest-result-2025-05-01_04-27-37.zip deleted file mode 100644 index 0ce12870..00000000 Binary files a/result/backtest-result-2025-05-01_04-27-37.zip and /dev/null differ diff --git a/run.sh b/run.sh index 94d0ff0a..7f755de8 100755 --- a/run.sh +++ b/run.sh @@ -18,6 +18,7 @@ zip_files=(*.zip) if [ ${#zip_files[@]} -eq 1 ]; then # 解压缩该 zip 文件到当前目录 unzip "${zip_files[0]}" + rm *.zip else echo "当前目录下没有 zip 文件或者有多个 zip 文件,无法操作。" fi diff --git a/请啊! b/请啊! deleted file mode 100644 index acdbba3c..00000000 --- a/请啊! +++ /dev/null @@ -1,233 +0,0 @@ -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": "LightGBMRegressor", - "feature_parameters": { - "include_timeframes": ["5m", "15m", "1h"], - "include_corr_pairlist": [], - "label_period_candles": 12, - "include_shifted_candles": 3, - }, - "data_split_parameters": { - "test_size": 0.2, - "shuffle": False, - }, - "model_training_parameters": { - "n_estimators": 100, - "learning_rate": 0.1, - "num_leaves": 31, - "verbose": -1, - }, - } - - 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 feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: - dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) - dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period) - dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period) - dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period) - dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period) - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2) - dataframe["bb_lowerband-period"] = bollinger["lower"] - dataframe["bb_middleband-period"] = bollinger["mid"] - dataframe["bb_upperband-period"] = bollinger["upper"] - dataframe["%-bb_width-period"] = ( - dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"] - ) / dataframe["bb_middleband-period"] - dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"] - dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period) - 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) - 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) - 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) - 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"] - # 生成 %-volatility 特征 - dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() - - # 单一回归目标 - # 移除对未来的数据依赖 - dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14) - - # 数据清理 - 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) - if dataframe[col].isna().any(): - logger.warning(f"目标列 {col} 仍包含 NaN,检查数据生成逻辑") - except Exception as e: - logger.error(f"创建 FreqAI 目标失败:{str(e)}") - raise - - logger.info(f"目标列预览:\n{dataframe[['&-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 信号 - dataframe["up_or_down"] = np.where( - dataframe["close"] > dataframe["close"].shift(1), 1, 0 - ) - - # 动态设置参数 - if "&-buy_rsi" in dataframe.columns: - # 派生其他目标 - dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30 - dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() - dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25) - dataframe["&-roi_0"] = (dataframe["close"] / dataframe["close"].shift(label_period) - 1).clip(0, 0.2) - - # 简化动态参数生成逻辑 - dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].clip(10, 50) - dataframe["sell_rsi_pred"] = dataframe["&-buy_rsi"] + 30 - dataframe["stoploss_pred"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25) - dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2) - - # 检查预测值 - 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) - - # 更保守的止损和止盈设置 - 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 = 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 - } - 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"动态参数: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) - - 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_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - enter_long_conditions = [ - qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]), - df["volume"] > 0 - ] - if enter_long_conditions: - df.loc[ - reduce(lambda x, y: x & y, enter_long_conditions), - ["enter_long", "enter_tag"] - ] = (1, "long") - return df - - def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - exit_long_conditions = [ - qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]), - df["volume"] > 0 - ] - if exit_long_conditions: - df.loc[ - reduce(lambda x, y: x & y, exit_long_conditions), - "exit_long" - ] = 1 - return df - - def confirm_trade_entry( - self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time, entry_tag, side: str, **kwargs - ) -> bool: - 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)): - return False - return True