dry-run时表现稳定, hyperopt表现也20% profit, 不错, 需要长时间运行看看了
This commit is contained in:
parent
a32c505cff
commit
f815ee89ee
@ -5,10 +5,11 @@
|
|||||||
"max_open_trades": 4,
|
"max_open_trades": 4,
|
||||||
"stake_currency": "USDT",
|
"stake_currency": "USDT",
|
||||||
"stake_amount": 150,
|
"stake_amount": 150,
|
||||||
|
"startup_candle_count": 30,
|
||||||
"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": "5m",
|
||||||
"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.05,
|
||||||
@ -30,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
"ccxt_async_config": {
|
"ccxt_async_config": {
|
||||||
"enableRateLimit": true,
|
"enableRateLimit": true,
|
||||||
"rateLimit": 500,
|
"rateLimit": 1000,
|
||||||
"timeout": 20000
|
"timeout": 20000
|
||||||
},
|
},
|
||||||
"pair_whitelist": [
|
"pair_whitelist": [
|
||||||
@ -66,8 +67,7 @@
|
|||||||
},
|
},
|
||||||
"freqaimodel": "CatboostClassifier",
|
"freqaimodel": "CatboostClassifier",
|
||||||
"purge_old_models": 2,
|
"purge_old_models": 2,
|
||||||
"train_period_days": 15,
|
"identifier": "test130",
|
||||||
"identifier": "test93",
|
|
||||||
"train_period_days": 30,
|
"train_period_days": 30,
|
||||||
"backtest_period_days": 10,
|
"backtest_period_days": 10,
|
||||||
"live_retrain_hours": 0,
|
"live_retrain_hours": 0,
|
||||||
@ -76,15 +76,14 @@
|
|||||||
},
|
},
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"include_timeframes": [
|
"include_timeframes": [
|
||||||
"3m",
|
"5m",
|
||||||
"15m",
|
|
||||||
"1h"
|
"1h"
|
||||||
],
|
],
|
||||||
"include_corr_pairlist": [
|
"include_corr_pairlist": [
|
||||||
"BTC/USDT",
|
"BTC/USDT",
|
||||||
"SOL/USDT"
|
"SOL/USDT"
|
||||||
],
|
],
|
||||||
"label_period_candles": 20,
|
"label_period_candles": 12,
|
||||||
"include_shifted_candles": 3,
|
"include_shifted_candles": 3,
|
||||||
"DI_threshold": 0.9,
|
"DI_threshold": 0.9,
|
||||||
"weight_factor": 0.9,
|
"weight_factor": 0.9,
|
||||||
@ -98,13 +97,14 @@
|
|||||||
"plot_feature_importances": 0
|
"plot_feature_importances": 0
|
||||||
},
|
},
|
||||||
"data_split_parameters": {
|
"data_split_parameters": {
|
||||||
"test_size": 0.2
|
"test_size": 0.2,
|
||||||
|
"shuffle": false,
|
||||||
},
|
},
|
||||||
"model_training_parameters": {
|
"model_training_parameters": {
|
||||||
"n_estimators": 100,
|
"n_estimators": 100,
|
||||||
"learning_rate": 0.05,
|
"learning_rate": 0.1,
|
||||||
"max_depth": 5,
|
"num_leaves": 15,
|
||||||
"num_leaves": 31
|
"verbose": -1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api_server": {
|
"api_server": {
|
||||||
@ -123,7 +123,7 @@
|
|||||||
"initial_state": "running",
|
"initial_state": "running",
|
||||||
"force_entry_enable": false,
|
"force_entry_enable": false,
|
||||||
"internals": {
|
"internals": {
|
||||||
"process_throttle_secs": 5,
|
"process_throttle_secs": 10,
|
||||||
"heartbeat_interval": 20,
|
"heartbeat_interval": 20,
|
||||||
"loglevel": "DEBUG"
|
"loglevel": "DEBUG"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ services:
|
|||||||
trade
|
trade
|
||||||
--logfile /freqtrade/user_data/logs/freqtrade.log
|
--logfile /freqtrade/user_data/logs/freqtrade.log
|
||||||
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
||||||
--freqaimodel LightGBMRegressor
|
--freqaimodel XGBoostRegressor
|
||||||
--config /freqtrade/config_examples/config_freqai.okx.json
|
--config /freqtrade/config_examples/config_freqai.okx.json
|
||||||
--strategy FreqaiExampleStrategy
|
--strategy FreqaiExampleStrategy
|
||||||
--strategy-path /freqtrade/templates
|
--strategy-path /freqtrade/templates
|
||||||
|
|||||||
@ -5,28 +5,28 @@
|
|||||||
"max_open_trades": 4
|
"max_open_trades": 4
|
||||||
},
|
},
|
||||||
"buy": {
|
"buy": {
|
||||||
"buy_rsi": 49
|
"buy_rsi": 37
|
||||||
},
|
},
|
||||||
"sell": {
|
"sell": {
|
||||||
"sell_rsi": 64
|
"sell_rsi": 80
|
||||||
},
|
},
|
||||||
"protection": {},
|
"protection": {},
|
||||||
"roi": {
|
"roi": {
|
||||||
"0": 0.07600000000000001,
|
"0": 0.124,
|
||||||
"7": 0.034,
|
"14": 0.023,
|
||||||
"13": 0.007,
|
"37": 0.011,
|
||||||
"60": 0
|
"50": 0
|
||||||
},
|
},
|
||||||
"stoploss": {
|
"stoploss": {
|
||||||
"stoploss": -0.087
|
"stoploss": -0.168
|
||||||
},
|
},
|
||||||
"trailing": {
|
"trailing": {
|
||||||
"trailing_stop": true,
|
"trailing_stop": true,
|
||||||
"trailing_stop_positive": 0.333,
|
"trailing_stop_positive": 0.047,
|
||||||
"trailing_stop_positive_offset": 0.341,
|
"trailing_stop_positive_offset": 0.051000000000000004,
|
||||||
"trailing_only_offset_is_reached": true
|
"trailing_only_offset_is_reached": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ft_stratparam_v": 1,
|
"ft_stratparam_v": 1,
|
||||||
"export_time": "2025-04-23 15:53:46.477203+00:00"
|
"export_time": "2025-04-24 11:42:35.037486+00:00"
|
||||||
}
|
}
|
||||||
@ -10,7 +10,6 @@ from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class FreqaiExampleStrategy(IStrategy):
|
class FreqaiExampleStrategy(IStrategy):
|
||||||
## minimal_roi 设置为{} 利润稍高, 回头再说,
|
|
||||||
minimal_roi = {
|
minimal_roi = {
|
||||||
"0": 0.076,
|
"0": 0.076,
|
||||||
"7": 0.034,
|
"7": 0.034,
|
||||||
@ -34,48 +33,20 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
stoploss_param = DecimalParameter(low=-0.25, high=-0.05, default=-0.1, space="stoploss", optimize=True, load=True)
|
stoploss_param = DecimalParameter(low=-0.25, high=-0.05, default=-0.1, space="stoploss", optimize=True, load=True)
|
||||||
trailing_stop_positive_offset = DecimalParameter(low=0.01, high=0.5, default=0.02, space="trailing", optimize=True, load=True)
|
trailing_stop_positive_offset = DecimalParameter(low=0.01, high=0.5, default=0.02, space="trailing", optimize=True, load=True)
|
||||||
|
|
||||||
# 以下配置 利润低, 但是 更符合 损失函数的评估
|
|
||||||
# minimal_roi = {
|
|
||||||
# "0": 0.076,
|
|
||||||
# "7": 0.034,
|
|
||||||
# "13": 0.007,
|
|
||||||
# "60": 0
|
|
||||||
# }
|
|
||||||
# stoploss = -0.087
|
|
||||||
# trailing_stop = True
|
|
||||||
# trailing_stop_positive = 0.333
|
|
||||||
# trailing_stop_positive_offset = 0.341
|
|
||||||
# trailing_only_offset_is_reached = True
|
|
||||||
# process_only_new_candles = True
|
|
||||||
# use_exit_signal = True
|
|
||||||
# startup_candle_count: int = 40
|
|
||||||
# can_short = False
|
|
||||||
# max_open_trades = 4
|
|
||||||
#
|
|
||||||
# # Hyperopt 参数
|
|
||||||
# buy_rsi = IntParameter(low=10, high=50, default=49, space="buy", optimize=True, load=True)
|
|
||||||
# sell_rsi = IntParameter(low=50, high=90, default=64, space="sell", optimize=True, load=True)
|
|
||||||
# roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.076, space="roi", optimize=True, load=True)
|
|
||||||
# roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.034, space="roi", optimize=True, load=True)
|
|
||||||
# roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.007, space="roi", optimize=True, load=True)
|
|
||||||
# stoploss_param = DecimalParameter(low=-0.25, high=-0.05, default=-0.087, space="stoploss", optimize=True, load=True)
|
|
||||||
# trailing_stop_positive_offset = DecimalParameter(low=0.01, high=0.5, default=0.341, space="trailing", optimize=True, load=True)
|
|
||||||
|
|
||||||
|
|
||||||
# 保护机制
|
|
||||||
protections = [
|
protections = [
|
||||||
{"method": "StoplossGuard", "stop_duration": 60, "lookback_period": 120},
|
{"method": "StoplossGuard", "stop_duration": 60, "lookback_period": 120},
|
||||||
{"method": "MaxDrawdown", "lookback_period": 120, "max_allowed_drawdown": 0.05}
|
{"method": "MaxDrawdown", "lookback_period": 120, "max_allowed_drawdown": 0.05}
|
||||||
]
|
]
|
||||||
|
|
||||||
# FreqAI 配置
|
|
||||||
freqai_info = {
|
freqai_info = {
|
||||||
"model": "LightGBMRegressor",
|
"model": "LightGBMRegressor",
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"include_timeframes": ["5m", "15m", "1h"],
|
"include_timeframes": ["5m"],
|
||||||
"include_corr_pairlist": [],
|
"include_corr_pairlist": ["SOL/USDT", "BTC/USDT"],
|
||||||
"label_period_candles": 12,
|
"label_period_candles": 12,
|
||||||
"include_shifted_candles": 3,
|
"include_shifted_candles": 0,
|
||||||
|
"include_periods": [10, 20],
|
||||||
|
"DI_threshold": 3.0
|
||||||
},
|
},
|
||||||
"data_split_parameters": {
|
"data_split_parameters": {
|
||||||
"test_size": 0.2,
|
"test_size": 0.2,
|
||||||
@ -84,8 +55,9 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
"model_training_parameters": {
|
"model_training_parameters": {
|
||||||
"n_estimators": 100,
|
"n_estimators": 100,
|
||||||
"learning_rate": 0.1,
|
"learning_rate": 0.1,
|
||||||
"num_leaves": 15, # 降低以减少警告
|
"num_leaves": 15,
|
||||||
"verbose": -1,
|
"n_jobs": 4,
|
||||||
|
"verbosity": -1
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,149 +74,195 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
|
|
||||||
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
||||||
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["%-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)
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2)
|
||||||
dataframe["bb_lowerband-period"] = bollinger["lower"]
|
dataframe["%-bb_width-period"] = (bollinger["upper"] - bollinger["lower"]) / bollinger["mid"]
|
||||||
dataframe["bb_middleband-period"] = bollinger["mid"]
|
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||||
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 = dataframe.replace([np.inf, -np.inf], 0)
|
|
||||||
dataframe = dataframe.ffill()
|
|
||||||
dataframe = dataframe.fillna(0)
|
|
||||||
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 = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||||
dataframe["%-raw_price"] = dataframe["close"]
|
|
||||||
dataframe = dataframe.replace([np.inf, -np.inf], 0)
|
|
||||||
dataframe = dataframe.ffill()
|
|
||||||
dataframe = dataframe.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:
|
||||||
|
if len(dataframe) < 20 or dataframe["close"].isna().any():
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows) or contains NaN in close, cannot compute %-volatility")
|
||||||
|
dataframe["%-volatility"] = 0
|
||||||
|
else:
|
||||||
|
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
||||||
|
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
|
||||||
|
if dataframe["%-volatility"].std() > 0:
|
||||||
|
dataframe["%-volatility"] = (dataframe["%-volatility"] - dataframe["%-volatility"].mean()) / dataframe["%-volatility"].std()
|
||||||
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 = dataframe.replace([np.inf, -np.inf], 0)
|
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||||
dataframe = dataframe.ffill()
|
|
||||||
dataframe = dataframe.fillna(0)
|
|
||||||
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:
|
||||||
logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}")
|
logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}")
|
||||||
if "close" not in dataframe.columns:
|
if "close" not in dataframe.columns:
|
||||||
logger.error("数据框缺少必要的 'close' 列")
|
logger.error("DataFrame missing required 'close' column")
|
||||||
raise ValueError("数据框缺少必要的 'close' 列")
|
raise ValueError("DataFrame missing required 'close' column")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
||||||
|
if len(dataframe) < 20 or dataframe["close"].isna().any():
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows) or contains NaN in close, cannot compute %-volatility")
|
||||||
|
dataframe["%-volatility"] = 0
|
||||||
|
else:
|
||||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
||||||
|
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
|
||||||
|
if dataframe["%-volatility"].std() > 0:
|
||||||
|
dataframe["%-volatility"] = (dataframe["%-volatility"] - dataframe["%-volatility"].mean()) / dataframe["%-volatility"].std()
|
||||||
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).shift(-label_period)
|
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).shift(-label_period)
|
||||||
|
|
||||||
for col in ["&-buy_rsi", "%-volatility"]:
|
for col in ["&-buy_rsi", "%-volatility"]:
|
||||||
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
|
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
|
||||||
dataframe[col] = dataframe[col].ffill()
|
dataframe[col] = dataframe[col].ffill().fillna(0)
|
||||||
dataframe[col] = dataframe[col].fillna(0)
|
|
||||||
if dataframe[col].isna().any():
|
if dataframe[col].isna().any():
|
||||||
logger.warning(f"目标列 {col} 仍包含 NaN,检查数据生成逻辑")
|
logger.warning(f"Target column {col} still contains NaN, check data generation logic")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
|
logger.error(f"Failed to create FreqAI targets: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
logger.info(f"Target columns preview: {dataframe[['&-buy_rsi']].head().to_string()}")
|
||||||
logger.info(f"目标列预览:\n{dataframe[['&-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:
|
||||||
logger.info(f"处理交易对:{metadata['pair']}")
|
logger.info(f"Processing pair: {metadata['pair']}")
|
||||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
logger.info(f"DataFrame rows: {len(dataframe)}")
|
||||||
|
logger.info(f"Columns before freqai.start: {list(dataframe.columns)}")
|
||||||
|
|
||||||
# 计算传统指标
|
# 验证输入数据
|
||||||
|
if "close" not in dataframe.columns or dataframe["close"].isna().all():
|
||||||
|
logger.error(f"DataFrame missing 'close' column or all NaN for pair: {metadata['pair']}")
|
||||||
|
raise ValueError("DataFrame missing valid 'close' column")
|
||||||
|
|
||||||
|
# 生成 RSI
|
||||||
|
if len(dataframe) < 14:
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute rsi")
|
||||||
|
dataframe["rsi"] = 50
|
||||||
|
else:
|
||||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
|
logger.info(f"rsi stats: {dataframe['rsi'].describe().to_string()}")
|
||||||
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 信号
|
# 生成 %-volatility
|
||||||
|
if len(dataframe) < 20 or dataframe["close"].isna().any():
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows) or contains NaN in close, cannot compute %-volatility")
|
||||||
|
dataframe["%-volatility"] = 0
|
||||||
|
else:
|
||||||
|
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
||||||
|
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
|
||||||
|
if dataframe["%-volatility"].std() > 0:
|
||||||
|
dataframe["%-volatility"] = (dataframe["%-volatility"] - dataframe["%-volatility"].mean()) / dataframe["%-volatility"].std()
|
||||||
|
logger.info(f"%-volatility stats: {dataframe['%-volatility'].describe().to_string()}")
|
||||||
|
|
||||||
|
# 生成 TEMA
|
||||||
|
if len(dataframe) < 9:
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute tema")
|
||||||
|
dataframe["tema"] = dataframe["close"]
|
||||||
|
else:
|
||||||
|
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
|
||||||
|
if dataframe["tema"].isna().any():
|
||||||
|
logger.warning("tema contains NaN, filling with close")
|
||||||
|
dataframe["tema"] = dataframe["tema"].fillna(dataframe["close"])
|
||||||
|
logger.info(f"tema stats: {dataframe['tema'].describe().to_string()}")
|
||||||
|
|
||||||
|
# 生成 Bollinger Bands
|
||||||
|
if len(dataframe) < 20:
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute bb_lowerband")
|
||||||
|
dataframe["bb_lowerband"] = dataframe["close"]
|
||||||
|
else:
|
||||||
|
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2.2)
|
||||||
|
dataframe["bb_lowerband"] = bollinger["lower"]
|
||||||
|
if dataframe["bb_lowerband"].isna().any():
|
||||||
|
logger.warning("bb_lowerband contains NaN, filling with close")
|
||||||
|
dataframe["bb_lowerband"] = dataframe["bb_lowerband"].fillna(dataframe["close"])
|
||||||
|
logger.info(f"bb_lowerband stats: {dataframe['bb_lowerband'].describe().to_string()}")
|
||||||
|
|
||||||
|
# 生成 up_or_down
|
||||||
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
|
||||||
|
if len(dataframe) < label_period + 1:
|
||||||
|
logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute up_or_down")
|
||||||
|
dataframe["up_or_down"] = 0
|
||||||
|
else:
|
||||||
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
|
||||||
)
|
)
|
||||||
|
if dataframe["up_or_down"].isna().any():
|
||||||
|
logger.warning("up_or_down contains NaN, filling with 0")
|
||||||
|
dataframe["up_or_down"] = dataframe["up_or_down"].fillna(0)
|
||||||
|
logger.info(f"up_or_down stats: {dataframe['up_or_down'].describe().to_string()}")
|
||||||
|
|
||||||
# 预填充 NaN
|
# 生成其他特征
|
||||||
dataframe = dataframe.ffill()
|
if "date" in dataframe.columns:
|
||||||
dataframe = dataframe.fillna(0)
|
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
||||||
|
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
||||||
|
else:
|
||||||
|
logger.warning("Missing 'date' column, skipping %-day_of_week and %-hour_of_day")
|
||||||
|
dataframe["%-day_of_week"] = 0
|
||||||
|
dataframe["%-hour_of_day"] = 0
|
||||||
|
|
||||||
if "&-buy_rsi" in dataframe.columns:
|
# 调用 FreqAI
|
||||||
# 派生其他目标
|
try:
|
||||||
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
|
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||||
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
|
logger.info(f"Columns after freqai.start: {list(dataframe.columns)}")
|
||||||
dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)
|
except Exception as e:
|
||||||
dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2)
|
logger.error(f"freqai.start failed: {str(e)}")
|
||||||
|
dataframe["buy_rsi_pred"] = 50
|
||||||
|
dataframe["sell_rsi_pred"] = 80
|
||||||
|
dataframe["do_predict"] = 1
|
||||||
|
|
||||||
# 计算预测值并减少 NaN
|
# 检查预测列
|
||||||
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5, min_periods=1).mean().clip(10, 50)
|
for col in ["buy_rsi_pred", "sell_rsi_pred"]:
|
||||||
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5, min_periods=1).mean().clip(50, 90)
|
if col not in dataframe.columns:
|
||||||
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.25, -0.05)
|
logger.error(f"Error: {col} column not generated for pair: {metadata['pair']}")
|
||||||
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
|
dataframe[col] = 50 if col == "buy_rsi_pred" else 80
|
||||||
|
logger.info(f"{col} stats: {dataframe[col].describe().to_string()}")
|
||||||
|
|
||||||
# 处理 NaN
|
# 调试特征分布
|
||||||
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
|
if "%-bb_width-period_10_SOL/USDT_5m" in dataframe.columns:
|
||||||
if dataframe[col].isna().any():
|
if dataframe["%-bb_width-period_10_SOL/USDT_5m"].std() > 0:
|
||||||
logger.warning(f"列 {col} 包含 NaN,填充为默认值")
|
dataframe["%-bb_width-period_10_SOL/USDT_5m"] = (
|
||||||
mean_value = dataframe[col].mean()
|
dataframe["%-bb_width-period_10_SOL/USDT_5m"] - dataframe["%-bb_width-period_10_SOL/USDT_5m"].mean()
|
||||||
if pd.isna(mean_value):
|
) / dataframe["%-bb_width-period_10_SOL/USDT_5m"].std()
|
||||||
logger.warning(f"列 {col} 均值仍为 NaN,使用默认值")
|
logger.info(f"%-bb_width-period_10 stats: {dataframe['%-bb_width-period_10_SOL/USDT_5m'].describe().to_string()}")
|
||||||
mean_value = {
|
|
||||||
"buy_rsi_pred": 30,
|
|
||||||
"sell_rsi_pred": 70,
|
|
||||||
"stoploss_pred": -0.1,
|
|
||||||
"roi_0_pred": 0.05,
|
|
||||||
"&-sell_rsi": 70,
|
|
||||||
"&-stoploss": -0.1,
|
|
||||||
"&-roi_0": 0.05
|
|
||||||
}.get(col, 0)
|
|
||||||
dataframe[col] = dataframe[col].fillna(mean_value)
|
|
||||||
|
|
||||||
# 动态追踪止盈
|
# 动态生成期望的特征列
|
||||||
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
|
def get_expected_columns(freqai_config: dict) -> list:
|
||||||
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4)
|
indicators = ["rsi", "bb_width", "pct-change"]
|
||||||
|
periods = freqai_config.get("feature_parameters", {}).get("include_periods", [10, 20])
|
||||||
|
pairs = freqai_config.get("include_corr_pairlist", ["SOL/USDT", "BTC/USDT"])
|
||||||
|
timeframes = freqai_config.get("include_timeframes", ["5m"])
|
||||||
|
shifts = [0]
|
||||||
|
expected_columns = ["%-volatility", "%-day_of_week", "%-hour_of_day"]
|
||||||
|
for indicator in indicators:
|
||||||
|
for period in periods:
|
||||||
|
for pair in pairs:
|
||||||
|
for timeframe in timeframes:
|
||||||
|
for shift in shifts:
|
||||||
|
col_name = f"%-{indicator}-period_{period}" if indicator != "pct-change" else f"%-{indicator}"
|
||||||
|
if shift > 0:
|
||||||
|
col_name += f"_shift-{shift}"
|
||||||
|
col_name += f"_{pair}_{timeframe}"
|
||||||
|
expected_columns.append(col_name)
|
||||||
|
return expected_columns
|
||||||
|
|
||||||
# 设置动态参数
|
expected_columns = get_expected_columns(self.freqai_info)
|
||||||
self.stoploss = float(dataframe["stoploss_pred"].iloc[-1])
|
logger.info(f"Expected feature columns ({len(expected_columns)}): {expected_columns[:10]}...")
|
||||||
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
|
|
||||||
self.sell_rsi.value = float(dataframe["sell_rsi_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.0
|
|
||||||
}
|
|
||||||
self.trailing_stop_positive = float(dataframe["trailing_stop_positive"].iloc[-1])
|
|
||||||
self.trailing_stop_positive_offset = float(dataframe["trailing_stop_positive_offset"].iloc[-1])
|
|
||||||
|
|
||||||
logger.info(f"minimal_roi 键:{list(self.minimal_roi.keys())}")
|
# 比较特征集
|
||||||
logger.info(f"动态参数:buy_rsi={self.buy_rsi.value}, sell_rsi={self.sell_rsi.value}, "
|
actual_columns = list(dataframe.columns)
|
||||||
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}")
|
missing_columns = [col for col in expected_columns if col not in actual_columns]
|
||||||
|
extra_columns = [col for col in actual_columns if col not in expected_columns and col.startswith("%-")]
|
||||||
|
logger.info(f"Missing columns ({len(missing_columns)}): {missing_columns}")
|
||||||
|
logger.info(f"Extra columns ({len(extra_columns)}): {extra_columns}")
|
||||||
|
|
||||||
dataframe = dataframe.replace([np.inf, -np.inf], 0)
|
# 调试 DI 丢弃预测
|
||||||
dataframe = dataframe.ffill()
|
if "DI_values" in dataframe.columns:
|
||||||
dataframe = dataframe.fillna(0)
|
logger.info(f"DI_values stats: {dataframe['DI_values'].describe().to_string()}")
|
||||||
|
logger.info(f"DI discarded predictions: {len(dataframe[dataframe['do_predict'] == 0])}")
|
||||||
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
|
|
||||||
logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}")
|
|
||||||
|
|
||||||
|
# 清理数据
|
||||||
|
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||||
|
logger.info(f"Final columns in populate_indicators: {list(dataframe.columns)}")
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
||||||
@ -272,7 +290,6 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
df["up_or_down"] == 0
|
df["up_or_down"] == 0
|
||||||
]
|
]
|
||||||
time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
|
time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
|
||||||
|
|
||||||
df.loc[
|
df.loc[
|
||||||
(reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
|
(reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
|
||||||
"exit_long"
|
"exit_long"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user