Compare commits

...

65 Commits

Author SHA1 Message Date
zhangkun9038@dingtalk.com
28fd0a80c9 stable1 14 2025-04-28 21:08:33 +08:00
zhangkun9038@dingtalk.com
9bb14377ed stable1 14 2025-04-28 21:07:51 +08:00
zhangkun9038@dingtalk.com
eceb04d22d stable1 13 2025-04-28 21:01:58 +08:00
zhangkun9038@dingtalk.com
a46895526c stable1 11 2025-04-28 20:51:06 +08:00
zhangkun9038@dingtalk.com
bba64d33cb stable1 11 2025-04-28 20:49:31 +08:00
zhangkun9038@dingtalk.com
ec8447e116 stable1 10 2025-04-28 20:38:42 +08:00
zhangkun9038@dingtalk.com
bc39868c5e stable1 10 2025-04-28 20:37:16 +08:00
zhangkun9038@dingtalk.com
42781656b4 stable1 7 2025-04-28 18:30:02 +08:00
zhangkun9038@dingtalk.com
d6fe9f8573 stable1 6 2025-04-28 18:26:32 +08:00
zhangkun9038@dingtalk.com
ebd83c9374 stable1 6 2025-04-28 18:25:54 +08:00
zhangkun9038@dingtalk.com
ae6330a89e stable1 6 2025-04-28 18:24:44 +08:00
zhangkun9038@dingtalk.com
51d4de29ae stable1 6 2025-04-28 18:22:50 +08:00
zhangkun9038@dingtalk.com
7564bac37a stable1 6 2025-04-28 18:18:53 +08:00
zhangkun9038@dingtalk.com
9f681ae122 stable1 6 2025-04-28 18:17:27 +08:00
zhangkun9038@dingtalk.com
2b6f010cd5 stable1 6 2025-04-28 18:15:53 +08:00
zhangkun9038@dingtalk.com
a416b4347d stable1 6 2025-04-28 18:14:42 +08:00
zhangkun9038@dingtalk.com
36a7563923 stable1 6 2025-04-28 18:13:34 +08:00
zhangkun9038@dingtalk.com
6c634f9137 stable1 6 2025-04-28 18:02:40 +08:00
zhangkun9038@dingtalk.com
de13e2e144 stable1 6 2025-04-28 18:02:14 +08:00
zhangkun9038@dingtalk.com
e10821a4fc stable1 6 2025-04-28 17:58:03 +08:00
zhangkun9038@dingtalk.com
bb7a4af33a stable1 5 2025-04-28 17:55:14 +08:00
zhangkun9038@dingtalk.com
4b2fd054b5 stable1 4 2025-04-28 17:54:40 +08:00
zhangkun9038@dingtalk.com
5ef9dd3d61 stable1 4 2025-04-28 17:54:01 +08:00
zhangkun9038@dingtalk.com
9acebf9952 stable1 3 2025-04-28 17:53:27 +08:00
zhangkun9038@dingtalk.com
1a9a0932c5 stable1 1 2025-04-28 17:51:42 +08:00
zhangkun9038@dingtalk.com
8f4c374e6b 检查 MACD 列是否存在
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 17:01:30 +08:00
zhangkun9038@dingtalk.com
ff7aff8ee7 检查 MACD 列是否存在
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:51:18 +08:00
zhangkun9038@dingtalk.com
e5cc226c01 检查 MACD 列是否存在
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:48:53 +08:00
zhangkun9038@dingtalk.com
63c1f07f06 确保是二维数组
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:46:26 +08:00
zhangkun9038@dingtalk.com
b20684b5b1 up3
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:12:02 +08:00
zhangkun9038@dingtalk.com
96b76ffcc0 up3
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:09:50 +08:00
zhangkun9038@dingtalk.com
0fa0866370 up3
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:09:02 +08:00
zhangkun9038@dingtalk.com
887c4778b4 up3
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 16:07:03 +08:00
zhangkun9038@dingtalk.com
8777726441 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 15:49:02 +08:00
zhangkun9038@dingtalk.com
b6b9a62c35 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 15:47:10 +08:00
zhangkun9038@dingtalk.com
3fafbff8c3 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 15:44:22 +08:00
zhangkun9038@dingtalk.com
619de5cede 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 15:32:01 +08:00
zhangkun9038@dingtalk.com
8535b10cea 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 15:15:37 +08:00
zhangkun9038@dingtalk.com
7da603fd08 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 15:14:09 +08:00
zhangkun9038@dingtalk.com
144615b7a8 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 14:13:16 +08:00
zhangkun9038@dingtalk.com
8448ab40f5 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 14:11:36 +08:00
zhangkun9038@dingtalk.com
6916e49479 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 14:10:54 +08:00
zhangkun9038@dingtalk.com
29fd0941a0 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 14:08:34 +08:00
zhangkun9038@dingtalk.com
ec7d7c2842 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 14:07:32 +08:00
zhangkun9038@dingtalk.com
ea707fe104 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 14:06:49 +08:00
zhangkun9038@dingtalk.com
2f4a06d505 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 13:59:16 +08:00
zhangkun9038@dingtalk.com
65116ab7b4 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 13:49:35 +08:00
zhangkun9038@dingtalk.com
fbd72745cb 跟特征数量没关系
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 13:34:56 +08:00
zhangkun9038@dingtalk.com
8477bf07c7 增加特征
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 13:23:49 +08:00
zhangkun9038@dingtalk.com
03a54a5b0a 添加更多技术指标
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 13:13:19 +08:00
zhangkun9038@dingtalk.com
e3ffdd92e0 :生成的特征可能不够稳定,导致新数据与训练数据差异过大
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 13:01:27 +08:00
zhangkun9038@dingtalk.com
c3e4a73eb3 缩进乱了
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:49:02 +08:00
zhangkun9038@dingtalk.com
21e4c2f2ea 数据清理
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:45:19 +08:00
zhangkun9038@dingtalk.com
86e9a2ab61 确保 &-buy_rsi 列的值计算正确
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:42:12 +08:00
zhangkun9038@dingtalk.com
05b65162a1 Additional check to ensure no NaN values remain
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:34:47 +08:00
zhangkun9038@dingtalk.com
64e2edfa4e 计算 buy_rsi_pred 并清理 NaN 值
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:31:10 +08:00
zhangkun9038@dingtalk.com
328769e0e1 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:25:20 +08:00
zhangkun9038@dingtalk.com
82ed0e90e9 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:21:53 +08:00
zhangkun9038@dingtalk.com
244b91ebd3 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:20:59 +08:00
zhangkun9038@dingtalk.com
e0884d4349 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:16:22 +08:00
zhangkun9038@dingtalk.com
456ae1fde0 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 12:10:17 +08:00
zhangkun9038@dingtalk.com
b72587ed6a up 2025-04-28 11:54:42 +08:00
zhangkun9038@dingtalk.com
c145d5d452 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 11:52:45 +08:00
zhangkun9038@dingtalk.com
872bfe4078 up
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 11:38:27 +08:00
zhangkun9038@dingtalk.com
6e4e54b9c8 去掉看底牌代码
Some checks are pending
Update Docker Hub Description / dockerHubDescription (push) Waiting to run
2025-04-28 11:34:17 +08:00
9 changed files with 538 additions and 266 deletions

View 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
View File

@ -0,0 +1,5 @@
<<<<<<< HEAD
=======
rm -rf /freqtrade/user_data/models/test62/
>>>>>>> Snippet

5
5. **重新训练**: Normal file
View File

@ -0,0 +1,5 @@
<<<<<<< HEAD
=======
freqtrade trade --config config_examples/config_freqai.okx.json --strategy FreqaiExampleStrategy
>>>>>>> Snippet

0
asdf Normal file
View File

View File

@ -2,16 +2,16 @@
"$schema": "https://schema.freqtrade.io/schema.json",
"trading_mode": "spot",
"margin_mode": "isolated",
"max_open_trades": 4,
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": 150,
"stake_amount": 100,
"tradable_balance_ratio": 1,
"fiat_display_currency": "USD",
"dry_run": true,
"timeframe": "3m",
"timeframe": "15m",
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true,
"stoploss": -0.05,
"stoploss": -0.1,
"unfilledtimeout": {
"entry": 5,
"exit": 15
@ -30,12 +30,13 @@
},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 500,
"rateLimit": 500,
"timeout": 20000
},
"pair_whitelist": [
"BTC/USDT",
"SOL/USDT"
"SOL/USDT",
"ETH/USDT"
],
"pair_blacklist": []
},
@ -61,50 +62,45 @@
],
"freqai": {
"enabled": true,
"model_path": "/freqtrade/user_data/models",
"data_kitchen": {
"fillna": "ffill"
},
"freqaimodel": "CatboostClassifier",
"purge_old_models": 2,
"train_period_days": 15,
"identifier": "test62",
"train_period_days": 30,
"backtest_period_days": 10,
"freqaimodel": "XGBoostRegressor",
"model_training_parameters": {
"n_estimators": 300,
"learning_rate": 0.03,
"max_depth": 6,
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_lambda": 1.0,
"objective": "reg:squarederror",
"eval_metric": "rmse",
"early_stopping_rounds": 20
},
"train_period_days": 730,
"backtest_period_days": 90,
"live_retrain_hours": 0,
"feature_selection": {
"method": "recursive_elimination"
"method": "recursive_elimination"
},
"feature_parameters": {
"include_timeframes": [
"3m",
"15m",
"1h"
],
"include_corr_pairlist": [
"BTC/USDT",
"SOL/USDT"
],
"label_period_candles": 20,
"include_timeframes": ["15m", "1h", "4h"],
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT", "ETH/USDT"],
"label_period_candles": 60,
"include_shifted_candles": 2,
"DI_threshold": 0.9,
"weight_factor": 0.9,
"principal_component_analysis": false,
"use_SVM_to_remove_outliers": false,
"indicator_periods_candles": [
10,
20,
50
],
"plot_feature_importances": 0
"use_SVM_to_remove_outliers": true,
"SVM_parameters": {
"nu": 0.1
},
"DI_threshold": 0,
"indicator_periods_candles": [14, 20]
},
"data_split_parameters": {
"test_size": 0.2
},
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.05,
"max_depth": 5,
"num_leaves": 31
"test_size": 0.2,
"shuffle": true
}
},
"api_server": {
@ -128,3 +124,4 @@
"loglevel": "DEBUG"
}
}

View File

@ -64,9 +64,10 @@ 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
--strategy FreqaiExampleStrategy
--timerange 20240920-20250420
--timerange 20250320-20250420
--cache none

View File

@ -52,7 +52,9 @@ class FreqaiExampleHybridStrategy(IStrategy):
"random_state": 1
},
"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["%-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)
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_middleband-period"] = bollinger["mid"]
@ -137,13 +137,6 @@ class FreqaiExampleHybridStrategy(IStrategy):
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()
)
return dataframe
@ -177,8 +170,7 @@ class FreqaiExampleHybridStrategy(IStrategy):
dataframe["%-ema-200"] = ta.EMA(dataframe, timeperiod=200)
"""
dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-raw_volume"] = dataframe["volume"]
dataframe["%-raw_price"] = dataframe["close"]
return dataframe
def feature_engineering_standard(
@ -209,10 +201,10 @@ class FreqaiExampleHybridStrategy(IStrategy):
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
return 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"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:
logger.error("Required 'close' column missing in dataframe")
@ -223,22 +215,16 @@ class FreqaiExampleHybridStrategy(IStrategy):
raise ValueError("Insufficient data for target calculation")
try:
# 生成数值型标签1 表示上涨0 表示下跌
# Define target variable: 1 for price increase, 0 for price decrease
dataframe["&-up_or_down"] = np.where(
dataframe["close"].shift(-50) > dataframe["close"],
1.0, # 数值型标签
0.0
dataframe["close"].shift(-50) > dataframe["close"], 1, 0
)
# Ensure target variable is a 2D array
dataframe["&-up_or_down"] = dataframe["&-up_or_down"].values.reshape(-1, 1)
except Exception as e:
logger.error(f"Failed to create &-up_or_down column: {str(e)}")
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")
return dataframe

View File

@ -1,5 +1,6 @@
import logging
import numpy as np
import pandas as pd
from functools import reduce
import talib.abstract as ta
from pandas import DataFrame
@ -9,241 +10,280 @@ 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 中动态设置
"""
FreqAI-based trading strategy using XGBoostRegressor for regression-based price movement prediction.
Optimized for short-term trading on spot markets (BTC/USDT, ETH/USDT, SOL/USDT).
Key improvements:
- Fixed KeyError in populate_entry_trend by using pd.concat for conditions
- Dynamic ATR-based stop-loss and ROI
- Enhanced feature engineering with cross-timeframe indicators
- Standardized and transformed target values to amplify signals
- Disabled DI filtering to resolve datasieve warnings
"""
# Strategy parameters
minimal_roi = {}
stoploss = 0.0
trailing_stop = True
process_only_new_candles = True
use_exit_signal = True
startup_candle_count: int = 40
can_short = False
timeframe = "15m"
# 参数定义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 parameters
buy_rsi = IntParameter(low=10, high=50, default=30, space="buy", optimize=True, load=True)
sell_rsi = IntParameter(low=50, high=90, default=70, space="sell", optimize=True, load=True)
roi_0 = DecimalParameter(low=0.01, high=0.1, default=0.05, space="roi", optimize=True, load=True)
roi_15 = DecimalParameter(low=0.005, high=0.05, default=0.03, space="roi", optimize=True, load=True)
roi_30 = DecimalParameter(low=0.001, high=0.03, default=0.01, space="roi", optimize=True, load=True)
stoploss_param = DecimalParameter(low=-0.2, high=-0.05, default=-0.1, space="stoploss", optimize=True, 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 configuration
freqai_info = {
"model": "LightGBMRegressor",
"model": "XGBoostRegressor",
"return_type": "raw", # Use raw regression predictions
"feature_parameters": {
"include_timeframes": ["5m", "15m", "1h"],
"include_corr_pairlist": [],
"label_period_candles": 12,
"include_shifted_candles": 3,
"include_timeframes": ["15m", "1h", "4h"],
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT", "ETH/USDT"],
"label_period_candles": 60, # Extended prediction horizon
"include_shifted_candles": 2,
"weight_factor": 0.9,
"principal_component_analysis": False,
"use_SVM_to_remove_outliers": True,
"SVM_parameters": {"nu": 0.1},
"DI_threshold": 0, # Disable DI filtering
"indicator_periods_candles": [14, 20]
},
"data_split_parameters": {
"test_size": 0.2,
"shuffle": False,
"shuffle": True,
},
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.1,
"num_leaves": 31,
"verbose": -1,
"n_estimators": 300,
"learning_rate": 0.03,
"max_depth": 6,
"subsample": 0.8,
"colsample_bytree": 0.8,
"reg_lambda": 1.0,
"objective": "reg:squarederror",
"eval_metric": "rmse",
"early_stopping_rounds": 20,
"verbose": 0,
},
"data_kitchen": {
"feature_parameters": {
"DI_threshold": 0 # Ensure DI filtering is disabled
}
}
}
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 calculate_macd(self, dataframe: DataFrame) -> DataFrame:
"""Calculate MACD indicators and handle exceptions."""
try:
macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9)
dataframe["macd"] = macd["macd"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
dataframe["macdsignal"] = macd["macdsignal"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
logger.info("MACD calculated successfully.")
except Exception as e:
logger.error(f"Error calculating MACD: {str(e)}")
dataframe["macd"] = np.nan
dataframe["macdsignal"] = np.nan
return dataframe
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).shift(-label_period)
# 数据清理
for col in ["&-buy_rsi", "%-volatility"]:
dataframe[col].replace([np.inf, -np.inf], 0, inplace=True)
dataframe[col].fillna(method='ffill', inplace=True)
dataframe[col].fillna(0, inplace=True)
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)
# 计算传统指标
"""Enhanced feature engineering with cross-timeframe and momentum indicators."""
# Standard technical indicators
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["sma20"] = ta.SMA(dataframe["close"], timeperiod=20)
dataframe["ema50"] = ta.EMA(dataframe["close"], timeperiod=50)
dataframe["atr"] = ta.ATR(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=14)
dataframe["obv"] = ta.OBV(dataframe["close"], dataframe["volume"])
dataframe["adx"] = ta.ADX(dataframe["high"], dataframe["low"], dataframe["close"], timeperiod=14)
dataframe["momentum"] = ta.MOM(dataframe["close"], timeperiod=14)
dataframe["price_sma_diff"] = (dataframe["close"] - dataframe["sma20"]) / dataframe["sma20"]
# Bollinger Bands
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)
dataframe["bb_width"] = (bollinger["upper"] - bollinger["lower"]) / bollinger["mid"]
# 生成 up_or_down 信号(非 FreqAI 目标)
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
)
# Cross-timeframe features
for tf in ["1h", "4h"]:
tf_data = self.dp.get_pair_dataframe(pair=metadata["pair"], timeframe=tf)
if not tf_data.empty:
dataframe[f"rsi_{tf}"] = ta.RSI(tf_data, timeperiod=14)
bollinger_tf = qtpylib.bollinger_bands(qtpylib.typical_price(tf_data), window=20, stds=2)
dataframe[f"bb_width_{tf}"] = (bollinger_tf["upper"] - bollinger_tf["lower"]) / bollinger_tf["mid"]
# 动态设置参数
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"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2)
# 限制预测值,添加平滑
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50)
dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean(), inplace=True)
if dataframe["buy_rsi_pred"].isna().any():
logger.warning("buy_rsi_pred 列包含 NaN已填充为默认值")
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90)
dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].mean(), inplace=True)
if dataframe["sell_rsi_pred"].isna().any():
logger.warning("sell_rsi_pred 列包含 NaN已填充为默认值")
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1)
dataframe["stoploss_pred"].fillna(dataframe["stoploss_pred"].mean(), inplace=True)
if dataframe["stoploss_pred"].isna().any():
logger.warning("stoploss_pred 列包含 NaN已填充为默认值")
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
dataframe["roi_0_pred"].fillna(dataframe["roi_0_pred"].mean(), inplace=True)
if dataframe["roi_0_pred"].isna().any():
logger.warning("roi_0_pred 列包含 NaN已填充为默认值")
# 检查预测值
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.5).clip(0.01, 0.3)
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4)
# 设置策略级参数
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()}")
# Correlated pair features
if metadata["pair"] == "SOL/USDT":
btc_data = self.dp.get_pair_dataframe(pair="BTC/USDT", timeframe=self.timeframe)
if not btc_data.empty:
dataframe["btc_rsi"] = ta.RSI(btc_data, timeperiod=14)
dataframe["btc_price_change"] = btc_data["close"].pct_change()
dataframe["btc_volatility"] = btc_data["close"].pct_change().rolling(20).std()
# Data cleaning
for col in dataframe.columns:
if dataframe[col].dtype in ["float64", "int64"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan).ffill().fillna(dataframe[col].median())
logger.info(f"Feature engineering completed, features: {len(dataframe.columns)}")
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["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 feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
"""Basic feature engineering for FreqAI."""
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], np.nan).ffill().fillna(dataframe[col].median())
return dataframe
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
(df["close"] < df["close"].shift(1) * 0.97),
df["volume"] > 0,
df["do_predict"] == 1,
df["up_or_down"] == 0
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
"""Standard feature engineering for temporal features."""
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
for col in dataframe.columns:
if dataframe[col].dtype in ["float64", "int64"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan).ffill().fillna(dataframe[col].median())
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
"""Set FreqAI prediction targets with standardized and transformed values."""
logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}")
if "close" not in dataframe.columns:
logger.error("DataFrame missing 'close' column")
raise ValueError("DataFrame missing 'close' column")
try:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
dataframe["&-up_or_down"] = (
dataframe["close"].shift(-label_period) - dataframe["close"]
) / dataframe["close"]
# Standardize target values
dataframe["&-up_or_down"] = (
dataframe["&-up_or_down"] - dataframe["&-up_or_down"].mean()
) / dataframe["&-up_or_down"].std()
# Apply logarithmic transformation to amplify signals
dataframe["&-up_or_down"] = np.log1p(dataframe["&-up_or_down"].abs()) * np.sign(dataframe["&-up_or_down"])
dataframe["&-up_or_down"] = dataframe["&-up_or_down"].replace([np.inf, -np.inf], np.nan).ffill().fillna(0)
dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
for col in ["&-buy_rsi", "&-up_or_down", "%-volatility"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan).ffill().fillna(dataframe[col].median())
except Exception as e:
logger.error(f"Failed to create FreqAI targets: {str(e)}")
raise
logger.info(f"Target column shape: {dataframe['&-up_or_down'].shape}")
logger.info(f"Target preview:\n{dataframe[['&-up_or_down', '&-buy_rsi']].head().to_string()}")
return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Populate indicators and dynamic strategy parameters."""
logger.info(f"Processing pair: {metadata['pair']}")
dataframe = self.freqai.start(dataframe, metadata, self)
# Calculate technical indicators
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
dataframe["atr"] = ta.ATR(dataframe["high"], dataframe["low"], dataframe["close"], 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 = self.calculate_macd(dataframe)
# Dynamic parameter settings
if "&-buy_rsi" in dataframe.columns:
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 20
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
dataframe["&-stoploss"] = -2 * dataframe["atr"] / dataframe["close"]
dataframe["&-roi_0"] = (dataframe["close"] / dataframe["close"].shift(10) - 1).clip(0, 0.1).fillna(0)
# Dynamic predictions
dataframe["buy_rsi_pred"] = dataframe["rsi"].rolling(window=10).mean().clip(20, 50).fillna(dataframe["rsi"].median())
dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 20
dataframe["stoploss_pred"] = -2 * dataframe["atr"] / dataframe["close"].clip(-0.2, -0.05)
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.1).fillna(dataframe["&-roi_0"].mean())
for col in ["&-stoploss", "&-roi_0", "buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred"]:
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan).ffill().fillna(dataframe[col].mean())
# Set strategy parameters
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(dataframe["stoploss_pred"].iloc[-1])
self.minimal_roi = {
0: float(self.roi_0.value) + (dataframe["atr"].iloc[-1] / dataframe["close"].iloc[-1]),
15: float(self.roi_15.value) * 0.8,
30: float(self.roi_30.value) * 0.5,
60: 0
}
# Dynamic trailing stop
self.trailing_stop_positive = float(1.5 * dataframe["atr"].iloc[-1] / dataframe["close"].iloc[-1])
self.trailing_stop_positive_offset = float(2.5 * dataframe["atr"].iloc[-1] / dataframe["close"].iloc[-1])
# Data cleaning
dataframe.ffill(inplace=True)
dataframe.fillna(dataframe.mean(numeric_only=True), inplace=True)
logger.info(f"&-up_or_down stats:\n{dataframe['&-up_or_down'].describe().to_string()}")
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Generate entry signals with relaxed conditions."""
# Validate required columns
required_cols = ["rsi", "buy_rsi_pred", "volume", "bb_middleband", "macd", "macdsignal", "&-up_or_down"]
if not all(col in dataframe.columns for col in required_cols):
logger.error(f"Missing required columns: {set(required_cols) - set(dataframe.columns)}")
return dataframe
dataframe = self.calculate_macd(dataframe)
enter_long_conditions = [
dataframe["rsi"] < dataframe["buy_rsi_pred"],
dataframe["volume"] > dataframe["volume"].rolling(window=10).mean() * 1.0,
dataframe["close"] > dataframe["bb_middleband"],
dataframe["macd"] > dataframe["macdsignal"],
dataframe["&-up_or_down"] > (0.001 if metadata["pair"] == "BTC/USDT" else 0.0015) # Lower threshold for BTC
]
if exit_long_conditions:
df.loc[
reduce(lambda x, y: x & y, exit_long_conditions),
"exit_long"
] = 1
return df
# Combine conditions into a DataFrame
conditions_df = pd.concat(enter_long_conditions, axis=1)
dataframe.loc[conditions_df.sum(axis=1) >= 3, ["enter_long", "enter_tag"]] = (1, "long")
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""Generate exit signals with confirmation."""
# Validate required columns
required_cols = ["rsi", "sell_rsi_pred", "bb_middleband", "macd", "macdsignal", "&-up_or_down"]
if not all(col in dataframe.columns for col in required_cols):
logger.error(f"Missing required columns: {set(required_cols) - set(dataframe.columns)}")
return dataframe
exit_long_conditions = [
dataframe["rsi"] > dataframe["sell_rsi_pred"],
dataframe["close"] < dataframe["bb_middleband"],
dataframe["macd"] < dataframe["macdsignal"],
dataframe["&-up_or_down"] < -0.005
]
# Combine conditions into a DataFrame
conditions_df = pd.concat(exit_long_conditions, axis=1)
# Require confirmation: at least 3 conditions met for two consecutive candles
exit_signal = (conditions_df.sum(axis=1) >= 3) & (conditions_df.shift(1).sum(axis=1) >= 3)
dataframe.loc[exit_signal, "exit_long"] = 1
return dataframe
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:
"""Confirm trade entry to avoid slippage."""
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

233
请啊! Normal file
View 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