Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84344f7f60 | ||
|
|
ec8447e116 | ||
|
|
bc39868c5e | ||
|
|
42781656b4 | ||
|
|
d6fe9f8573 | ||
|
|
ebd83c9374 | ||
|
|
ae6330a89e | ||
|
|
51d4de29ae | ||
|
|
7564bac37a | ||
|
|
9f681ae122 | ||
|
|
2b6f010cd5 | ||
|
|
a416b4347d | ||
|
|
36a7563923 | ||
|
|
6c634f9137 | ||
|
|
de13e2e144 | ||
|
|
e10821a4fc | ||
|
|
bb7a4af33a | ||
|
|
4b2fd054b5 | ||
|
|
5ef9dd3d61 | ||
|
|
9acebf9952 | ||
|
|
1a9a0932c5 | ||
|
|
8f4c374e6b | ||
|
|
ff7aff8ee7 | ||
|
|
e5cc226c01 | ||
|
|
63c1f07f06 | ||
|
|
b20684b5b1 | ||
|
|
96b76ffcc0 | ||
|
|
0fa0866370 | ||
|
|
887c4778b4 | ||
|
|
8777726441 | ||
|
|
b6b9a62c35 | ||
|
|
3fafbff8c3 | ||
|
|
619de5cede | ||
|
|
8535b10cea | ||
|
|
7da603fd08 | ||
|
|
144615b7a8 | ||
|
|
8448ab40f5 | ||
|
|
6916e49479 | ||
|
|
29fd0941a0 | ||
|
|
ec7d7c2842 | ||
|
|
ea707fe104 | ||
|
|
2f4a06d505 | ||
|
|
65116ab7b4 | ||
|
|
fbd72745cb | ||
|
|
8477bf07c7 | ||
|
|
03a54a5b0a | ||
|
|
e3ffdd92e0 | ||
|
|
c3e4a73eb3 | ||
|
|
21e4c2f2ea | ||
|
|
86e9a2ab61 | ||
|
|
05b65162a1 | ||
|
|
64e2edfa4e | ||
|
|
328769e0e1 | ||
|
|
82ed0e90e9 | ||
|
|
244b91ebd3 | ||
|
|
e0884d4349 | ||
|
|
456ae1fde0 | ||
|
|
b72587ed6a | ||
|
|
c145d5d452 | ||
|
|
872bfe4078 | ||
|
|
6e4e54b9c8 |
5
### **4. 测试优化后的策略**
Normal file
5
### **4. 测试优化后的策略**
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
freqtrade backtesting --strategy FreqaiExampleStrategy --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --timerange 20230101-20230401
|
||||||
|
>>>>>>> Snippet
|
||||||
5
4. **清理缓存**:
Normal file
5
4. **清理缓存**:
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
rm -rf /freqtrade/user_data/models/test62/
|
||||||
|
>>>>>>> Snippet
|
||||||
5
5. **重新训练**:
Normal file
5
5. **重新训练**:
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
freqtrade trade --config config_examples/config_freqai.okx.json --strategy FreqaiExampleStrategy
|
||||||
|
>>>>>>> Snippet
|
||||||
@ -2,16 +2,16 @@
|
|||||||
"$schema": "https://schema.freqtrade.io/schema.json",
|
"$schema": "https://schema.freqtrade.io/schema.json",
|
||||||
"trading_mode": "spot",
|
"trading_mode": "spot",
|
||||||
"margin_mode": "isolated",
|
"margin_mode": "isolated",
|
||||||
"max_open_trades": 4,
|
"max_open_trades": 3,
|
||||||
"stake_currency": "USDT",
|
"stake_currency": "USDT",
|
||||||
"stake_amount": 150,
|
"stake_amount": 100,
|
||||||
"tradable_balance_ratio": 1,
|
"tradable_balance_ratio": 1,
|
||||||
"fiat_display_currency": "USD",
|
"fiat_display_currency": "USD",
|
||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"timeframe": "3m",
|
"timeframe": "15m",
|
||||||
"dry_run_wallet": 1000,
|
"dry_run_wallet": 1000,
|
||||||
"cancel_open_orders_on_exit": true,
|
"cancel_open_orders_on_exit": true,
|
||||||
"stoploss": -0.05,
|
"stoploss": -0.1,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"entry": 5,
|
"entry": 5,
|
||||||
"exit": 15
|
"exit": 15
|
||||||
@ -30,12 +30,13 @@
|
|||||||
},
|
},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": true,
|
"enableRateLimit": true,
|
||||||
"rateLimit": 500,
|
"rateLimit": 500,
|
||||||
"timeout": 20000
|
"timeout": 20000
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
"BTC/USDT",
|
"BTC/USDT",
|
||||||
"SOL/USDT"
|
"SOL/USDT",
|
||||||
|
"ETH/USDT"
|
||||||
],
|
],
|
||||||
"pair_blacklist": []
|
"pair_blacklist": []
|
||||||
},
|
},
|
||||||
@ -64,47 +65,40 @@
|
|||||||
"data_kitchen": {
|
"data_kitchen": {
|
||||||
"fillna": "ffill"
|
"fillna": "ffill"
|
||||||
},
|
},
|
||||||
"freqaimodel": "CatboostClassifier",
|
"freqaimodel": "XGBoostRegressor",
|
||||||
"purge_old_models": 2,
|
"model_training_parameters": {
|
||||||
"train_period_days": 15,
|
"n_estimators": 200,
|
||||||
"identifier": "test62",
|
"learning_rate": 0.05,
|
||||||
"train_period_days": 30,
|
"max_depth": 6,
|
||||||
"backtest_period_days": 10,
|
"subsample": 0.8,
|
||||||
|
"colsample_bytree": 0.8,
|
||||||
|
"reg_lambda": 1.0,
|
||||||
|
"objective": "reg:squarederror",
|
||||||
|
"eval_metric": "rmse",
|
||||||
|
"early_stopping_rounds": 20
|
||||||
|
},
|
||||||
|
"train_period_days": 365,
|
||||||
|
"backtest_period_days": 90,
|
||||||
"live_retrain_hours": 0,
|
"live_retrain_hours": 0,
|
||||||
"feature_selection": {
|
"feature_selection": {
|
||||||
"method": "recursive_elimination"
|
"method": "recursive_elimination"
|
||||||
},
|
},
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"include_timeframes": [
|
"include_timeframes": ["15m", "1h", "4h"],
|
||||||
"3m",
|
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT"],
|
||||||
"15m",
|
"label_period_candles": 10,
|
||||||
"1h"
|
|
||||||
],
|
|
||||||
"include_corr_pairlist": [
|
|
||||||
"BTC/USDT",
|
|
||||||
"SOL/USDT"
|
|
||||||
],
|
|
||||||
"label_period_candles": 20,
|
|
||||||
"include_shifted_candles": 2,
|
"include_shifted_candles": 2,
|
||||||
"DI_threshold": 0.9,
|
|
||||||
"weight_factor": 0.9,
|
"weight_factor": 0.9,
|
||||||
"principal_component_analysis": false,
|
"principal_component_analysis": false,
|
||||||
"use_SVM_to_remove_outliers": false,
|
"use_SVM_to_remove_outliers": true,
|
||||||
"indicator_periods_candles": [
|
"SVM_parameters": {
|
||||||
10,
|
"nu": 0.1
|
||||||
20,
|
},
|
||||||
50
|
"indicator_periods_candles": [14, 20]
|
||||||
],
|
|
||||||
"plot_feature_importances": 0
|
|
||||||
},
|
},
|
||||||
"data_split_parameters": {
|
"data_split_parameters": {
|
||||||
"test_size": 0.2
|
"test_size": 0.2,
|
||||||
},
|
"shuffle": true
|
||||||
"model_training_parameters": {
|
|
||||||
"n_estimators": 100,
|
|
||||||
"learning_rate": 0.05,
|
|
||||||
"max_depth": 5,
|
|
||||||
"num_leaves": 31
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_server": {
|
"api_server": {
|
||||||
@ -128,3 +122,5 @@
|
|||||||
"loglevel": "DEBUG"
|
"loglevel": "DEBUG"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -64,9 +64,10 @@ services:
|
|||||||
command: >
|
command: >
|
||||||
backtesting
|
backtesting
|
||||||
--logfile /freqtrade/user_data/logs/freqtrade.log
|
--logfile /freqtrade/user_data/logs/freqtrade.log
|
||||||
--freqaimodel LightGBMRegressor
|
--freqaimodel XGBoostRegressor
|
||||||
--config /freqtrade/config_examples/config_freqai.okx.json
|
--config /freqtrade/config_examples/config_freqai.okx.json
|
||||||
--config /freqtrade/templates/FreqaiExampleStrategy.json
|
--config /freqtrade/templates/FreqaiExampleStrategy.json
|
||||||
--strategy-path /freqtrade/templates
|
--strategy-path /freqtrade/templates
|
||||||
--strategy FreqaiExampleStrategy
|
--strategy FreqaiExampleStrategy
|
||||||
--timerange 20240920-20250420
|
--timerange 20250320-20250420
|
||||||
|
--cache none
|
||||||
|
|||||||
@ -52,7 +52,9 @@ class FreqaiExampleHybridStrategy(IStrategy):
|
|||||||
"random_state": 1
|
"random_state": 1
|
||||||
},
|
},
|
||||||
"model_training_parameters": {
|
"model_training_parameters": {
|
||||||
"n_estimators": 800
|
"n_estimators": 200,
|
||||||
|
"max_depth": 5,
|
||||||
|
"learning_rate": 0.05
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -122,13 +124,11 @@ class FreqaiExampleHybridStrategy(IStrategy):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
|
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
|
||||||
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
|
|
||||||
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
|
|
||||||
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
|
|
||||||
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
|
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
|
||||||
|
|
||||||
bollinger = qtpylib.bollinger_bands(
|
bollinger = qtpylib.bollinger_bands(
|
||||||
qtpylib.typical_price(dataframe), window=period, stds=2.2
|
qtpylib.typical_price(dataframe), window=period, stds=2
|
||||||
)
|
)
|
||||||
dataframe["bb_lowerband-period"] = bollinger["lower"]
|
dataframe["bb_lowerband-period"] = bollinger["lower"]
|
||||||
dataframe["bb_middleband-period"] = bollinger["mid"]
|
dataframe["bb_middleband-period"] = bollinger["mid"]
|
||||||
@ -137,13 +137,6 @@ class FreqaiExampleHybridStrategy(IStrategy):
|
|||||||
dataframe["%-bb_width-period"] = (
|
dataframe["%-bb_width-period"] = (
|
||||||
dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
|
dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
|
||||||
) / dataframe["bb_middleband-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()
|
|
||||||
)
|
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@ -177,8 +170,7 @@ class FreqaiExampleHybridStrategy(IStrategy):
|
|||||||
dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
|
dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
|
||||||
"""
|
"""
|
||||||
dataframe["%-pct-change"] = dataframe["close"].pct_change()
|
dataframe["%-pct-change"] = dataframe["close"].pct_change()
|
||||||
dataframe["%-raw_volume"] = dataframe["volume"]
|
|
||||||
dataframe["%-raw_price"] = dataframe["close"]
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def feature_engineering_standard(
|
def feature_engineering_standard(
|
||||||
@ -209,10 +201,10 @@ class FreqaiExampleHybridStrategy(IStrategy):
|
|||||||
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
||||||
return dataframe
|
return dataframe
|
||||||
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||||
|
"""
|
||||||
|
Redefined target variable to predict whether the price will increase or decrease in the future.
|
||||||
|
"""
|
||||||
logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}")
|
logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}")
|
||||||
logger.info(f"DataFrame shape: {dataframe.shape}")
|
|
||||||
logger.info(f"Available columns: {list(dataframe.columns)}")
|
|
||||||
logger.info(f"First few rows:\n{dataframe[['date', 'close']].head().to_string()}")
|
|
||||||
|
|
||||||
if "close" not in dataframe.columns:
|
if "close" not in dataframe.columns:
|
||||||
logger.error("Required 'close' column missing in dataframe")
|
logger.error("Required 'close' column missing in dataframe")
|
||||||
@ -223,22 +215,16 @@ class FreqaiExampleHybridStrategy(IStrategy):
|
|||||||
raise ValueError("Insufficient data for target calculation")
|
raise ValueError("Insufficient data for target calculation")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 生成数值型标签:1 表示上涨,0 表示下跌
|
# Define target variable: 1 for price increase, 0 for price decrease
|
||||||
dataframe["&-up_or_down"] = np.where(
|
dataframe["&-up_or_down"] = np.where(
|
||||||
dataframe["close"].shift(-50) > dataframe["close"],
|
dataframe["close"].shift(-50) > dataframe["close"], 1, 0
|
||||||
1.0, # 数值型标签
|
|
||||||
0.0
|
|
||||||
)
|
)
|
||||||
|
# Ensure target variable is a 2D array
|
||||||
|
dataframe["&-up_or_down"] = dataframe["&-up_or_down"].values.reshape(-1, 1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to create &-up_or_down column: {str(e)}")
|
logger.error(f"Failed to create &-up_or_down column: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
logger.info(f"Target column head:\n{dataframe[['&-up_or_down']].head().to_string()}")
|
|
||||||
|
|
||||||
if "&-up_or_down" not in dataframe.columns:
|
|
||||||
logger.error("FreqAI failed to generate the &-up_or_down column")
|
|
||||||
raise KeyError("FreqAI failed to generate the &-up_or_down column")
|
|
||||||
|
|
||||||
logger.info("FreqAI targets set successfully")
|
logger.info("FreqAI targets set successfully")
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|||||||
@ -30,23 +30,34 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
|
|
||||||
# FreqAI 配置
|
# FreqAI 配置
|
||||||
freqai_info = {
|
freqai_info = {
|
||||||
"model": "LightGBMRegressor",
|
"model": "CatboostClassifier", # 与config保持一致
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"include_timeframes": ["5m", "15m", "1h"],
|
"include_timeframes": ["3m", "15m", "1h"], # 与config一致
|
||||||
"include_corr_pairlist": [],
|
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT"], # 添加相关交易对
|
||||||
"label_period_candles": 12,
|
"label_period_candles": 20, # 与config一致
|
||||||
"include_shifted_candles": 3,
|
"include_shifted_candles": 2, # 与config一致
|
||||||
},
|
},
|
||||||
"data_split_parameters": {
|
"data_split_parameters": {
|
||||||
"test_size": 0.2,
|
"test_size": 0.2,
|
||||||
"shuffle": False,
|
"shuffle": True, # 启用shuffle
|
||||||
},
|
},
|
||||||
"model_training_parameters": {
|
"model_training_parameters": {
|
||||||
"n_estimators": 100,
|
"n_estimators": 100, # 减少树的数量
|
||||||
"learning_rate": 0.1,
|
"learning_rate": 0.1, # 提高学习率
|
||||||
"num_leaves": 31,
|
"max_depth": 6, # 限制树深度
|
||||||
"verbose": -1,
|
"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 = {
|
plot_config = {
|
||||||
@ -60,43 +71,93 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
def featcaure_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["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||||
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
|
|
||||||
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
|
def calculate_macd(self, dataframe: DataFrame) -> DataFrame:
|
||||||
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
|
"""
|
||||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2)
|
Centralized method to calculate MACD and ensure proper assignment to the dataframe.
|
||||||
dataframe["bb_lowerband-period"] = bollinger["lower"]
|
"""
|
||||||
dataframe["bb_middleband-period"] = bollinger["mid"]
|
try:
|
||||||
dataframe["bb_upperband-period"] = bollinger["upper"]
|
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
|
||||||
dataframe["%-bb_width-period"] = (
|
dataframe["macd"] = macd["macd"]
|
||||||
dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
|
dataframe["macdsignal"] = macd["macdsignal"]
|
||||||
) / dataframe["bb_middleband-period"]
|
|
||||||
dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"]
|
# Ensure no NaN or infinite values in MACD columns
|
||||||
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
|
dataframe["macd"] = dataframe["macd"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
|
||||||
dataframe["%-relative_volume-period"] = (
|
dataframe["macdsignal"] = dataframe["macdsignal"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
|
||||||
dataframe["volume"] / dataframe["volume"].rolling(period).mean()
|
|
||||||
)
|
logger.info("MACD 列已成功计算并验证。")
|
||||||
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
|
except Exception as e:
|
||||||
dataframe.ffill(inplace=True)
|
logger.error(f"计算 MACD 列时出错:{str(e)}")
|
||||||
dataframe.fillna(0, inplace=True)
|
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
|
return dataframe
|
||||||
|
|
||||||
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||||
dataframe["%-pct-change"] = dataframe["close"].pct_change()
|
dataframe["%-pct-change"] = dataframe["close"].pct_change()
|
||||||
dataframe["%-raw_volume"] = dataframe["volume"]
|
dataframe["%-raw_volume"] = dataframe["volume"]
|
||||||
dataframe["%-raw_price"] = dataframe["close"]
|
dataframe["%-raw_price"] = dataframe["close"]
|
||||||
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
|
# 数据清理逻辑
|
||||||
dataframe.fillna(method='ffill', inplace=True)
|
for col in dataframe.columns:
|
||||||
dataframe.fillna(0, inplace=True)
|
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
|
return dataframe
|
||||||
|
|
||||||
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||||
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
||||||
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
||||||
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
|
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
|
||||||
dataframe.fillna(method='ffill', inplace=True)
|
dataframe.ffill(inplace=True)
|
||||||
dataframe.fillna(0, inplace=True)
|
dataframe.fillna(0, inplace=True)
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@ -105,27 +166,49 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
if "close" not in dataframe.columns:
|
if "close" not in dataframe.columns:
|
||||||
logger.error("数据框缺少必要的 'close' 列")
|
logger.error("数据框缺少必要的 'close' 列")
|
||||||
raise ValueError("数据框缺少必要的 'close' 列")
|
raise ValueError("数据框缺少必要的 'close' 列")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
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 特征
|
# 生成 %-volatility 特征
|
||||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
||||||
|
|
||||||
# 单一回归目标
|
# 确保 &-buy_rsi 列的值计算正确
|
||||||
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).shift(-label_period)
|
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
|
||||||
# 数据清理
|
# 数据清理
|
||||||
for col in ["&-buy_rsi", "%-volatility"]:
|
for col in ["&-buy_rsi", "up_or_down", "%-volatility"]:
|
||||||
dataframe[col].replace([np.inf, -np.inf], 0, inplace=True)
|
# 使用直接操作避免链式赋值
|
||||||
dataframe[col].fillna(method='ffill', inplace=True)
|
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan)
|
||||||
dataframe[col].fillna(0, inplace=True)
|
dataframe[col] = dataframe[col].ffill() # 替代 fillna(method='ffill')
|
||||||
|
dataframe[col] = dataframe[col].fillna(dataframe[col].mean()) # 使用均值填充 NaN 值
|
||||||
if dataframe[col].isna().any():
|
if dataframe[col].isna().any():
|
||||||
logger.warning(f"目标列 {col} 仍包含 NaN,检查数据生成逻辑")
|
logger.warning(f"目标列 {col} 仍包含 NaN,填充为默认值")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
|
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
|
||||||
raise
|
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
|
return dataframe
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
@ -142,6 +225,8 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
|
|
||||||
# 生成 up_or_down 信号(非 FreqAI 目标)
|
# 生成 up_or_down 信号(非 FreqAI 目标)
|
||||||
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
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["up_or_down"] = np.where(
|
||||||
dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0
|
dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0
|
||||||
)
|
)
|
||||||
@ -151,55 +236,66 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
# 派生其他目标
|
# 派生其他目标
|
||||||
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
|
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
|
||||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
||||||
dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)
|
# Ensure proper calculation and handle potential NaN values
|
||||||
dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2)
|
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)
|
||||||
|
|
||||||
# 限制预测值,添加平滑
|
# 简化动态参数生成逻辑
|
||||||
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50)
|
# 放松 buy_rsi 和 sell_rsi 的生成逻辑
|
||||||
dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean(), inplace=True)
|
# 计算 buy_rsi_pred 并清理 NaN 值
|
||||||
if dataframe["buy_rsi_pred"].isna().any():
|
dataframe["buy_rsi_pred"] = dataframe["rsi"].rolling(window=10).mean().clip(30, 50)
|
||||||
logger.warning("buy_rsi_pred 列包含 NaN,已填充为默认值")
|
dataframe["buy_rsi_pred"] = dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].median())
|
||||||
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)
|
# 计算 sell_rsi_pred 并清理 NaN 值
|
||||||
if dataframe["sell_rsi_pred"].isna().any():
|
dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 20
|
||||||
logger.warning("sell_rsi_pred 列包含 NaN,已填充为默认值")
|
dataframe["sell_rsi_pred"] = dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].median())
|
||||||
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1)
|
|
||||||
dataframe["stoploss_pred"].fillna(dataframe["stoploss_pred"].mean(), inplace=True)
|
# 计算 stoploss_pred 并清理 NaN 值
|
||||||
if dataframe["stoploss_pred"].isna().any():
|
dataframe["stoploss_pred"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)
|
||||||
logger.warning("stoploss_pred 列包含 NaN,已填充为默认值")
|
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"].clip(0.01, 0.2)
|
||||||
dataframe["roi_0_pred"].fillna(dataframe["roi_0_pred"].mean(), inplace=True)
|
dataframe["roi_0_pred"] = dataframe["roi_0_pred"].fillna(dataframe["roi_0_pred"].mean())
|
||||||
if dataframe["roi_0_pred"].isna().any():
|
|
||||||
logger.warning("roi_0_pred 列包含 NaN,已填充为默认值")
|
|
||||||
|
|
||||||
# 检查预测值
|
# 检查预测值
|
||||||
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
|
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
|
||||||
if dataframe[col].isna().any():
|
if dataframe[col].isna().any():
|
||||||
logger.warning(f"列 {col} 包含 NaN,填充为默认值")
|
logger.warning(f"列 {col} 包含 NaN,填充为默认值")
|
||||||
dataframe[col].fillna(dataframe[col].mean(), inplace=True)
|
dataframe[col] = dataframe[col].fillna(dataframe[col].mean())
|
||||||
|
|
||||||
# 动态追踪止盈
|
# 更保守的止损和止盈设置
|
||||||
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
|
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.75).clip(0.02, 0.4)
|
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.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
|
||||||
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
|
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
|
||||||
self.stoploss = float(self.stoploss_param.value)
|
# 更保守的止损设置
|
||||||
|
self.stoploss = -0.15 # 固定止损 15%
|
||||||
self.minimal_roi = {
|
self.minimal_roi = {
|
||||||
0: float(self.roi_0.value),
|
0: float(self.roi_0.value),
|
||||||
15: float(self.roi_15.value),
|
15: float(self.roi_15.value),
|
||||||
30: float(self.roi_30.value),
|
30: float(self.roi_30.value),
|
||||||
60: 0
|
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])
|
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}, "
|
exit_long_conditions = [
|
||||||
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}")
|
(dataframe["rsi"] > dataframe["sell_rsi_pred"]), # RSI 高于卖出阈值
|
||||||
|
(dataframe["volume"] > dataframe["volume"].rolling(window=10).mean()), # 成交量高于近期均值
|
||||||
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
|
(dataframe["close"] < dataframe["bb_middleband"]), # 价格低于布林带中轨
|
||||||
dataframe.fillna(method='ffill', inplace=True)
|
(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)
|
dataframe.fillna(0, inplace=True)
|
||||||
|
|
||||||
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
|
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
|
||||||
@ -207,36 +303,54 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
enter_long_conditions = [
|
# 改进卖出信号条件
|
||||||
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
|
|
||||||
df["tema"] > df["tema"].shift(1),
|
|
||||||
df["volume"] > 0,
|
|
||||||
df["do_predict"] == 1,
|
|
||||||
df["up_or_down"] == 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 populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
exit_long_conditions = [
|
exit_long_conditions = [
|
||||||
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
|
(dataframe["rsi"] > dataframe["sell_rsi_pred"]), # RSI 高于卖出阈值
|
||||||
(df["close"] < df["close"].shift(1) * 0.97),
|
(
|
||||||
df["volume"] > 0,
|
(dataframe["volume"] > dataframe["volume"].rolling(window=10).mean()) | # 成交量高于近期均值
|
||||||
df["do_predict"] == 1,
|
(dataframe["volume"] < dataframe["volume"].shift(1) * 0.9) # 当前成交量低于上一周期的10%
|
||||||
df["up_or_down"] == 0
|
),
|
||||||
|
(
|
||||||
|
(dataframe["close"] < dataframe["bb_middleband"]) | # 价格低于布林带中轨
|
||||||
|
(dataframe["close"] < dataframe["bb_lowerband"].shift(1)) # 当前价格低于上一周期的布林带下轨
|
||||||
|
)
|
||||||
]
|
]
|
||||||
if exit_long_conditions:
|
if exit_long_conditions:
|
||||||
df.loc[
|
dataframe.loc[
|
||||||
reduce(lambda x, y: x & y, exit_long_conditions),
|
reduce(lambda x, y: x & y, exit_long_conditions),
|
||||||
"exit_long"
|
"exit_long"
|
||||||
] = 1
|
] = 1
|
||||||
return df
|
return dataframe
|
||||||
|
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
# 改进买入信号条件
|
||||||
|
# 确保使用 centralized 方法计算 MACD
|
||||||
|
dataframe = self.calculate_macd(dataframe)
|
||||||
|
|
||||||
|
enter_long_conditions = [
|
||||||
|
(dataframe["rsi"] < dataframe["buy_rsi_pred"]), # RSI 低于买入阈值
|
||||||
|
(
|
||||||
|
(dataframe["volume"] > dataframe["volume"].rolling(window=10).mean() * 1.2) | # 成交量高于近期均值20%
|
||||||
|
(dataframe["volume"] > dataframe["volume"].shift(1) * 1.1) # 当前成交量高于上一周期的10%
|
||||||
|
),
|
||||||
|
(
|
||||||
|
(dataframe["close"] > dataframe["bb_middleband"]) | # 价格高于布林带中轨
|
||||||
|
(dataframe["close"] > dataframe["bb_upperband"].shift(1)) # 当前价格高于上一周期的布林带上轨
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# 如果 MACD 列存在,则添加 MACD 金叉条件
|
||||||
|
if "macd" in dataframe.columns and "macdsignal" in dataframe.columns:
|
||||||
|
enter_long_conditions.append((dataframe["macd"] > dataframe["macdsignal"]))
|
||||||
|
|
||||||
|
# 确保模型预测为买入
|
||||||
|
enter_long_conditions.append((dataframe["do_predict"] == 1))
|
||||||
|
if enter_long_conditions:
|
||||||
|
dataframe.loc[
|
||||||
|
reduce(lambda x, y: x & y, enter_long_conditions),
|
||||||
|
["enter_long", "enter_tag"]
|
||||||
|
] = (1, "long")
|
||||||
|
return dataframe
|
||||||
def confirm_trade_entry(
|
def confirm_trade_entry(
|
||||||
self, pair: str, order_type: str, amount: float, rate: float,
|
self, pair: str, order_type: str, amount: float, rate: float,
|
||||||
time_in_force: str, current_time, entry_tag, side: str, **kwargs
|
time_in_force: str, current_time, entry_tag, side: str, **kwargs
|
||||||
@ -247,3 +361,4 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
if rate > (last_candle["close"] * (1 + 0.0025)):
|
if rate > (last_candle["close"] * (1 + 0.0025)):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
233
请啊!
Normal file
233
请啊!
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
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
|
||||||
Loading…
x
Reference in New Issue
Block a user