Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28fd0a80c9 | ||
|
|
9bb14377ed | ||
|
|
eceb04d22d | ||
|
|
a46895526c | ||
|
|
bba64d33cb | ||
|
|
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
|
||||
48
catch.sh
48
catch.sh
@ -1,48 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 脚本名称: filter_freqtrade_logs.sh
|
||||
# 功能: 实时过滤 Freqtrade 容器日志,捕获包含 "but got Index" 的日志及上下文,输出到文件和终端
|
||||
|
||||
# 配置
|
||||
CONTAINER_NAME="freqtrade_freqtrade_run_ef258891294d" # 容器名称或 ID
|
||||
OUTPUT_FILE="freqtrade_error_logs.txt" # 输出日志文件
|
||||
SEARCH_PATTERN="but got Index" # 过滤的关键字
|
||||
CONTEXT_LINES=5 # 匹配行后的上下文行数
|
||||
|
||||
# 检查容器是否存在
|
||||
if ! docker ps -a --format '{{.Names}}' | grep -q "$CONTAINER_NAME"; then
|
||||
echo "错误: 容器 $CONTAINER_NAME 不存在。请检查容器名称或 ID。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查容器是否正在运行
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "$CONTAINER_NAME"; then
|
||||
echo "警告: 容器 $CONTAINER_NAME 未运行,将获取历史日志。"
|
||||
RUNNING=false
|
||||
else
|
||||
RUNNING=true
|
||||
fi
|
||||
|
||||
# 初始化输出文件
|
||||
echo "开始过滤日志,输出到 $OUTPUT_FILE ..."
|
||||
>"$OUTPUT_FILE" # 清空或创建输出文件
|
||||
|
||||
# 实时过滤日志
|
||||
if [ "$RUNNING" = true ]; then
|
||||
echo "实时监控 $CONTAINER_NAME 的日志,过滤 '$SEARCH_PATTERN'..."
|
||||
docker logs -f "$CONTAINER_NAME" 2>/dev/null |
|
||||
stdbuf -oL grep --line-buffered -i -A "$CONTEXT_LINES" "$SEARCH_PATTERN" |
|
||||
tee -a "$OUTPUT_FILE"
|
||||
else
|
||||
echo "获取 $CONTAINER_NAME 的历史日志,过滤 '$SEARCH_PATTERN'..."
|
||||
docker logs "$CONTAINER_NAME" 2>/dev/null |
|
||||
grep -i -A "$CONTEXT_LINES" "$SEARCH_PATTERN" |
|
||||
tee -a "$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
# 检查是否捕获到日志
|
||||
if [ -s "$OUTPUT_FILE" ]; then
|
||||
echo "已捕获日志,保存在 $OUTPUT_FILE"
|
||||
else
|
||||
echo "未捕获到包含 '$SEARCH_PATTERN' 的日志,文件 $OUTPUT_FILE 为空"
|
||||
fi
|
||||
File diff suppressed because it is too large
Load Diff
@ -2,17 +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,
|
||||
"startup_candle_count": 30,
|
||||
"stake_amount": 100,
|
||||
"tradable_balance_ratio": 1,
|
||||
"fiat_display_currency": "USD",
|
||||
"dry_run": true,
|
||||
"timeframe": "5m",
|
||||
"timeframe": "15m",
|
||||
"dry_run_wallet": 1000,
|
||||
"cancel_open_orders_on_exit": true,
|
||||
"stoploss": -0.05,
|
||||
"stoploss": -0.1,
|
||||
"unfilledtimeout": {
|
||||
"entry": 5,
|
||||
"exit": 15
|
||||
@ -24,20 +23,22 @@
|
||||
"enable_ws": false,
|
||||
"ccxt_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 800,
|
||||
"rateLimit": 500,
|
||||
"options": {
|
||||
"defaultType": "spot"
|
||||
}
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 800,
|
||||
"rateLimit": 500,
|
||||
"timeout": 20000
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"BTC/USDT",
|
||||
"SOL/USDT"
|
||||
]
|
||||
"SOL/USDT",
|
||||
"ETH/USDT"
|
||||
],
|
||||
"pair_blacklist": []
|
||||
},
|
||||
"entry_pricing": {
|
||||
"price_side": "same",
|
||||
@ -61,49 +62,45 @@
|
||||
],
|
||||
"freqai": {
|
||||
"enabled": true,
|
||||
"model_path": "/freqtrade/user_data/models",
|
||||
"data_kitchen": {
|
||||
"fillna": "ffill"
|
||||
},
|
||||
"freqaimodel": "CatboostClassifier",
|
||||
"purge_old_models": 2,
|
||||
"identifier": "test178",
|
||||
"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": [
|
||||
"5m",
|
||||
"1h"
|
||||
],
|
||||
"include_corr_pairlist": [
|
||||
"BTC/USDT",
|
||||
"SOL/USDT"
|
||||
],
|
||||
"label_period_candles": 12,
|
||||
"include_shifted_candles": 3,
|
||||
"DI_threshold": 0.9,
|
||||
"include_timeframes": ["15m", "1h", "4h"],
|
||||
"include_corr_pairlist": ["BTC/USDT", "SOL/USDT", "ETH/USDT"],
|
||||
"label_period_candles": 60,
|
||||
"include_shifted_candles": 2,
|
||||
"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,
|
||||
"shuffle": false,
|
||||
},
|
||||
"model_training_parameters": {
|
||||
"n_estimators": 100,
|
||||
"learning_rate": 0.1,
|
||||
"num_leaves": 15,
|
||||
"verbose": -1
|
||||
"shuffle": true
|
||||
}
|
||||
},
|
||||
"api_server": {
|
||||
@ -122,8 +119,9 @@
|
||||
"initial_state": "running",
|
||||
"force_entry_enable": false,
|
||||
"internals": {
|
||||
"process_throttle_secs": 10,
|
||||
"process_throttle_secs": 5,
|
||||
"heartbeat_interval": 20,
|
||||
"loglevel": "DEBUG"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@ services:
|
||||
- "./config_examples:/freqtrade/config_examples"
|
||||
- "./freqtrade/templates:/freqtrade/templates"
|
||||
- "./freqtrade/exchange/:/freqtrade/exchange"
|
||||
- "./ccxt/async_support/okx.py:/home/ftuser/.local/lib/python3.12/site-packages/ccxt/async_support/okx.py"
|
||||
# Expose api on port 8080 (localhost only)
|
||||
# Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation
|
||||
# for more information.
|
||||
@ -32,15 +31,15 @@ services:
|
||||
# Default command used when running `docker compose up`
|
||||
|
||||
# --freqaimodel XGBoostRegressor
|
||||
command: >
|
||||
trade
|
||||
--logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
||||
--freqaimodel XGBoostRegressor
|
||||
--config /freqtrade/config_examples/config_freqai.okx.json
|
||||
--strategy FreqaiExampleStrategy
|
||||
--strategy-path /freqtrade/templates
|
||||
--fee 0.0008
|
||||
# commangd: >
|
||||
# # trade
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
||||
# --freqaimodel LightGBMRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --strategy FreqaiExampleHybridStrategy
|
||||
# --strategy-path /freqtrade/templates
|
||||
# command: >
|
||||
# backtesting
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
@ -61,34 +60,14 @@ services:
|
||||
# --hyperopt-loss SharpeHyperOptLoss
|
||||
# --spaces roi stoploss
|
||||
# -e 200
|
||||
# --config /freqtrade/templates/FreqaiExampleStrategy.json
|
||||
|
||||
# command: >
|
||||
# backtesting
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --freqaimodel XGBoostRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy-path /freqtrade/templates
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --timerange 20250101-20250420
|
||||
# --fee 0.0008
|
||||
# command: >
|
||||
# download-data
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --exchange okx
|
||||
# --pairs DOT/USDT
|
||||
# --timeframe 1h 5m
|
||||
# --timerange 20240101-20250420
|
||||
#
|
||||
# command: >
|
||||
# hyperopt
|
||||
# --logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
# --freqaimodel LightGBMRegressor
|
||||
# --config /freqtrade/config_examples/config_freqai.okx.json
|
||||
# --strategy-path /freqtrade/templates
|
||||
# --strategy FreqaiExampleStrategy
|
||||
# --timerange 20250301-20250420
|
||||
# --hyperopt-loss SharpeHyperOptLoss
|
||||
# --spaces buy sell roi stoploss trailing
|
||||
# --fee 0.001
|
||||
# -e 200
|
||||
command: >
|
||||
backtesting
|
||||
--logfile /freqtrade/user_data/logs/freqtrade.log
|
||||
--freqaimodel XGBoostRegressor
|
||||
--config /freqtrade/config_examples/config_freqai.okx.json
|
||||
--config /freqtrade/templates/FreqaiExampleStrategy.json
|
||||
--strategy-path /freqtrade/templates
|
||||
--strategy FreqaiExampleStrategy
|
||||
--timerange 20250320-20250420
|
||||
--cache none
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -1,262 +0,0 @@
|
||||
diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py
|
||||
index 343c073..1d7ed33 100644
|
||||
--- a/freqtrade/templates/FreqaiExampleStrategy.py
|
||||
+++ b/freqtrade/templates/FreqaiExampleStrategy.py
|
||||
@@ -11,9 +11,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class FreqaiExampleStrategy(IStrategy):
|
||||
minimal_roi = {
|
||||
- "0": 0.076,
|
||||
- "7": 0.034,
|
||||
- "13": 0.007,
|
||||
+ "0": 0.02,
|
||||
+ "7": 0.01,
|
||||
+ "13": 0.005,
|
||||
"60": 0
|
||||
}
|
||||
|
||||
@@ -24,29 +24,25 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
startup_candle_count: int = 40
|
||||
can_short = False
|
||||
|
||||
- # Hyperopt 参数
|
||||
buy_rsi = IntParameter(low=10, high=50, default=27, space="buy", optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=90, default=59, space="sell", optimize=True, load=True)
|
||||
roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.038, space="roi", optimize=True, load=True)
|
||||
roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.027, space="roi", optimize=True, load=True)
|
||||
roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.009, space="roi", optimize=True, load=True)
|
||||
stoploss_param = DecimalParameter(low=-0.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.005, high=0.5, default=0.01, space="trailing", optimize=True, load=True)
|
||||
|
||||
- protections = [
|
||||
- {"method": "StoplossGuard", "stop_duration": 60, "lookback_period": 120},
|
||||
- {"method": "MaxDrawdown", "lookback_period": 120, "max_allowed_drawdown": 0.05}
|
||||
- ]
|
||||
+ protections = []
|
||||
|
||||
freqai_info = {
|
||||
"model": "LightGBMRegressor",
|
||||
"feature_parameters": {
|
||||
"include_timeframes": ["5m"],
|
||||
- "include_corr_pairlist": ["SOL/USDT", "BTC/USDT"],
|
||||
+ "include_corr_pairlist": ["SOL/USDT"],
|
||||
"label_period_candles": 12,
|
||||
"include_shifted_candles": 0,
|
||||
- "include_periods": [10, 20],
|
||||
- "DI_threshold": 3.0
|
||||
+ "include_periods": [20],
|
||||
+ "DI_threshold": 5.0
|
||||
},
|
||||
"data_split_parameters": {
|
||||
"test_size": 0.2,
|
||||
@@ -62,14 +58,20 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
}
|
||||
|
||||
plot_config = {
|
||||
- "main_plot": {},
|
||||
+ "main_plot": {
|
||||
+ "close": {"color": "blue"},
|
||||
+ "bb_lowerband": {"color": "purple"}
|
||||
+ },
|
||||
"subplots": {
|
||||
"&-buy_rsi": {"&-buy_rsi": {"color": "green"}},
|
||||
"&-sell_rsi": {"&-sell_rsi": {"color": "red"}},
|
||||
- "&-stoploss": {"&-stoploss": {"color": "purple"}},
|
||||
- "&-roi_0": {"&-roi_0": {"color": "orange"}},
|
||||
+ "rsi": {"rsi": {"color": "black"}},
|
||||
"do_predict": {"do_predict": {"color": "brown"}},
|
||||
- },
|
||||
+ "trade_signals": {
|
||||
+ "enter_long": {"color": "green", "type": "scatter"},
|
||||
+ "exit_long": {"color": "red", "type": "scatter"}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
|
||||
@@ -130,12 +132,10 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
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
|
||||
@@ -143,7 +143,6 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14)
|
||||
logger.info(f"rsi stats: {dataframe['rsi'].describe().to_string()}")
|
||||
|
||||
- # 生成 %-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
|
||||
@@ -154,7 +153,6 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
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"]
|
||||
@@ -165,7 +163,6 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
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"]
|
||||
@@ -177,21 +174,6 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
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"]
|
||||
- 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["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()}")
|
||||
-
|
||||
- # 生成其他特征
|
||||
if "date" in dataframe.columns:
|
||||
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
||||
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
||||
@@ -200,7 +182,6 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
dataframe["%-day_of_week"] = 0
|
||||
dataframe["%-hour_of_day"] = 0
|
||||
|
||||
- # 调用 FreqAI
|
||||
try:
|
||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||
logger.info(f"Columns after freqai.start: {list(dataframe.columns)}")
|
||||
@@ -210,26 +191,23 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
dataframe["sell_rsi_pred"] = 80
|
||||
dataframe["do_predict"] = 1
|
||||
|
||||
- # 检查预测列
|
||||
for col in ["buy_rsi_pred", "sell_rsi_pred"]:
|
||||
if col not in dataframe.columns:
|
||||
logger.error(f"Error: {col} column not generated for pair: {metadata['pair']}")
|
||||
dataframe[col] = 50 if col == "buy_rsi_pred" else 80
|
||||
logger.info(f"{col} stats: {dataframe[col].describe().to_string()}")
|
||||
|
||||
- # 调试特征分布
|
||||
- if "%-bb_width-period_10_SOL/USDT_5m" in dataframe.columns:
|
||||
- if dataframe["%-bb_width-period_10_SOL/USDT_5m"].std() > 0:
|
||||
- dataframe["%-bb_width-period_10_SOL/USDT_5m"] = (
|
||||
- dataframe["%-bb_width-period_10_SOL/USDT_5m"] - dataframe["%-bb_width-period_10_SOL/USDT_5m"].mean()
|
||||
- ) / dataframe["%-bb_width-period_10_SOL/USDT_5m"].std()
|
||||
- logger.info(f"%-bb_width-period_10 stats: {dataframe['%-bb_width-period_10_SOL/USDT_5m'].describe().to_string()}")
|
||||
+ if "%-bb_width-period_20_SOL/USDT_5m" in dataframe.columns:
|
||||
+ if dataframe["%-bb_width-period_20_SOL/USDT_5m"].std() > 0:
|
||||
+ dataframe["%-bb_width-period_20_SOL/USDT_5m"] = (
|
||||
+ dataframe["%-bb_width-period_20_SOL/USDT_5m"] - dataframe["%-bb_width-period_20_SOL/USDT_5m"].mean()
|
||||
+ ) / dataframe["%-bb_width-period_20_SOL/USDT_5m"].std()
|
||||
+ logger.info(f"%-bb_width-period_20 stats: {dataframe['%-bb_width-period_20_SOL/USDT_5m'].describe().to_string()}")
|
||||
|
||||
- # 动态生成期望的特征列
|
||||
def get_expected_columns(freqai_config: dict) -> list:
|
||||
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"])
|
||||
+ periods = freqai_config.get("feature_parameters", {}).get("include_periods", [20])
|
||||
+ pairs = freqai_config.get("include_corr_pairlist", ["SOL/USDT"])
|
||||
timeframes = freqai_config.get("include_timeframes", ["5m"])
|
||||
shifts = [0]
|
||||
expected_columns = ["%-volatility", "%-day_of_week", "%-hour_of_day"]
|
||||
@@ -248,50 +226,47 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
expected_columns = get_expected_columns(self.freqai_info)
|
||||
logger.info(f"Expected feature columns ({len(expected_columns)}): {expected_columns[:10]}...")
|
||||
|
||||
- # 比较特征集
|
||||
actual_columns = list(dataframe.columns)
|
||||
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}")
|
||||
|
||||
- # 调试 DI 丢弃预测
|
||||
if "DI_values" in dataframe.columns:
|
||||
logger.info(f"DI_values stats: {dataframe['DI_values'].describe().to_string()}")
|
||||
logger.info(f"DI discarded predictions: {len(dataframe[dataframe['do_predict'] == 0])}")
|
||||
|
||||
- # 清理数据
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
logger.info(f"Final columns in populate_indicators: {list(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"] + (5 if metadata["pair"] == "BTC/USDT" else 0)),
|
||||
+ 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
|
||||
+ df["do_predict"] == 1
|
||||
]
|
||||
- if enter_long_conditions:
|
||||
- df.loc[
|
||||
- reduce(lambda x, y: x & y, enter_long_conditions),
|
||||
- ["enter_long", "enter_tag"]
|
||||
- ] = (1, "long")
|
||||
+ df["entry_signal"] = reduce(lambda x, y: x & y, enter_long_conditions)
|
||||
+ df["entry_signal"] = df["entry_signal"].rolling(window=2, min_periods=1).max().astype(bool)
|
||||
+ df.loc[
|
||||
+ df["entry_signal"],
|
||||
+ ["enter_long", "enter_tag"]
|
||||
+ ] = (1, "long")
|
||||
+ if df["entry_signal"].iloc[-1]:
|
||||
+ logger.info(f"Entry signal triggered for {metadata['pair']}: rsi={df['rsi'].iloc[-1]}, buy_rsi_pred={df['buy_rsi_pred'].iloc[-1]}, do_predict={df['do_predict'].iloc[-1]}")
|
||||
return df
|
||||
|
||||
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
||||
exit_long_conditions = [
|
||||
- (qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"])) |
|
||||
+ (qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"] - 5)) |
|
||||
(df["close"] < df["close"].shift(1) * 0.98) |
|
||||
(df["close"] < df["bb_lowerband"]),
|
||||
df["volume"] > 0,
|
||||
- df["do_predict"] == 1,
|
||||
- df["up_or_down"] == 0
|
||||
+ df["do_predict"] == 1
|
||||
]
|
||||
- time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
|
||||
df.loc[
|
||||
- (reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
|
||||
+ reduce(lambda x, y: x & y, exit_long_conditions),
|
||||
"exit_long"
|
||||
] = 1
|
||||
return df
|
||||
@@ -300,9 +275,16 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
self, pair: str, order_type: str, amount: float, rate: float,
|
||||
time_in_force: str, current_time, entry_tag, side: str, **kwargs
|
||||
) -> bool:
|
||||
+ logger.info(f"Confirming trade entry for {pair}, order_type: {order_type}, rate: {rate}, current_time: {current_time}")
|
||||
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.001)):
|
||||
- return False
|
||||
+ if order_type == "market":
|
||||
+ logger.info(f"Order confirmed for {pair}, rate: {rate} (market order)")
|
||||
+ return True
|
||||
+ if rate <= (last_candle["close"] * (1 + 0.01)):
|
||||
+ logger.info(f"Order confirmed for {pair}, rate: {rate}")
|
||||
+ return True
|
||||
+ logger.info(f"Order rejected: rate {rate} exceeds threshold {last_candle['close'] * 1.01}")
|
||||
+ return False
|
||||
return True
|
||||
@ -1,32 +1,32 @@
|
||||
{
|
||||
"strategy_name": "FreqaiExampleStrategy",
|
||||
"params": {
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.01,
|
||||
"trailing_stop_positive_offset": 0.02,
|
||||
"trailing_only_offset_is_reached": false
|
||||
},
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 4
|
||||
},
|
||||
"buy": {
|
||||
"buy_rsi": 37
|
||||
"buy_rsi": 39.92672300850069
|
||||
},
|
||||
"sell": {
|
||||
"sell_rsi": 80
|
||||
"sell_rsi": 69.92672300850067
|
||||
},
|
||||
"protection": {},
|
||||
"roi": {
|
||||
"0": 0.124,
|
||||
"14": 0.023,
|
||||
"37": 0.011,
|
||||
"50": 0
|
||||
"0": 0.132,
|
||||
"8": 0.047,
|
||||
"14": 0.007,
|
||||
"60": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.168
|
||||
},
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.047,
|
||||
"trailing_stop_positive_offset": 0.051000000000000004,
|
||||
"trailing_only_offset_is_reached": true
|
||||
"stoploss": -0.322
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2025-04-24 11:42:35.037486+00:00"
|
||||
"export_time": "2025-04-23 12:30:05.550433+00:00"
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
{
|
||||
"strategy_name": "FreqaiExampleStrategy",
|
||||
"params": {
|
||||
"trailing": {
|
||||
"trailing_stop": true,
|
||||
"trailing_stop_positive": 0.01,
|
||||
"trailing_stop_positive_offset": 0.02,
|
||||
"trailing_only_offset_is_reached": false
|
||||
},
|
||||
"max_open_trades": {
|
||||
"max_open_trades": 4
|
||||
},
|
||||
"buy": {
|
||||
"buy_rsi": 39.92672300850069
|
||||
},
|
||||
"sell": {
|
||||
"sell_rsi": 69.92672300850067
|
||||
},
|
||||
"protection": {},
|
||||
"roi": {
|
||||
"0": 0.132,
|
||||
"8": 0.047,
|
||||
"14": 0.007,
|
||||
"60": 0
|
||||
},
|
||||
"stoploss": {
|
||||
"stoploss": -0.322
|
||||
}
|
||||
},
|
||||
"ft_stratparam_v": 1,
|
||||
"export_time": "2025-04-23 12:30:05.550433+00:00"
|
||||
}
|
||||
@ -10,296 +10,280 @@ from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FreqaiExampleStrategy(IStrategy):
|
||||
minimal_roi = {
|
||||
"0": 0.02,
|
||||
"7": 0.01,
|
||||
"13": 0.005,
|
||||
"60": 0
|
||||
}
|
||||
"""
|
||||
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"
|
||||
|
||||
buy_rsi = IntParameter(low=10, high=40, default=20, space="buy", optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=90, default=59, space="sell", optimize=True, load=True)
|
||||
roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.038, space="roi", optimize=True, load=True)
|
||||
roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.027, space="roi", optimize=True, load=True)
|
||||
roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.009, space="roi", optimize=True, load=True)
|
||||
stoploss_param = DecimalParameter(low=-0.25, high=-0.05, default=-0.1, space="stoploss", optimize=True, load=True)
|
||||
trailing_stop_positive_offset = DecimalParameter(low=0.005, high=0.5, default=0.01, space="trailing", optimize=True, load=True)
|
||||
|
||||
protections = []
|
||||
# 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)
|
||||
|
||||
# FreqAI configuration
|
||||
freqai_info = {
|
||||
"model": "LightGBMRegressor",
|
||||
"model": "XGBoostRegressor",
|
||||
"return_type": "raw", # Use raw regression predictions
|
||||
"feature_parameters": {
|
||||
"include_timeframes": ["5m"],
|
||||
"include_corr_pairlist": ["OKB/USDT", "SOL/USDT"], # 与白名单一致
|
||||
"label_period_candles": 12,
|
||||
"include_shifted_candles": 0,
|
||||
"include_periods": [20],
|
||||
"DI_threshold": 1.5 # 提高以减少丢弃预测
|
||||
"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": 15,
|
||||
"n_jobs": 4,
|
||||
"verbosity": -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,
|
||||
},
|
||||
}
|
||||
|
||||
plot_config = {
|
||||
"main_plot": {
|
||||
"close": {"color": "blue"},
|
||||
"bb_lowerband": {"color": "purple"}
|
||||
},
|
||||
"subplots": {
|
||||
"&-buy_rsi": {"&-buy_rsi": {"color": "green"}},
|
||||
"&-sell_rsi": {"&-sell_rsi": {"color": "red"}},
|
||||
"rsi": {"rsi": {"color": "black"}},
|
||||
"do_predict": {"do_predict": {"color": "brown"}},
|
||||
"trade_signals": {
|
||||
"enter_long": {"color": "green", "type": "scatter"},
|
||||
"exit_long": {"color": "red", "type": "scatter"}
|
||||
"data_kitchen": {
|
||||
"feature_parameters": {
|
||||
"DI_threshold": 0 # Ensure DI filtering is disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2)
|
||||
dataframe["%-bb_width-period"] = (bollinger["upper"] - bollinger["lower"]) / bollinger["mid"]
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
"""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["bb_width"] = (bollinger["upper"] - bollinger["lower"]) / bollinger["mid"]
|
||||
|
||||
# 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"]
|
||||
|
||||
# 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 feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
|
||||
"""Basic feature engineering for FreqAI."""
|
||||
dataframe["%-pct-change"] = dataframe["close"].pct_change()
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
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 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()
|
||||
"""Standard feature engineering for temporal features."""
|
||||
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
||||
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
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 required 'close' column")
|
||||
raise ValueError("DataFrame missing required 'close' column")
|
||||
logger.error("DataFrame missing 'close' column")
|
||||
raise ValueError("DataFrame missing 'close' column")
|
||||
|
||||
try:
|
||||
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["%-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)
|
||||
for col in ["&-buy_rsi", "%-volatility"]:
|
||||
dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
|
||||
dataframe[col] = dataframe[col].ffill().fillna(0)
|
||||
if dataframe[col].isna().any():
|
||||
logger.warning(f"Target column {col} still contains NaN, check data generation logic")
|
||||
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 columns preview: {dataframe[['&-buy_rsi']].head().to_string()}")
|
||||
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:
|
||||
logger.info(f"populate_indicators Processing pair: {metadata['pair']}")
|
||||
logger.info(f"populate_indicators DataFrame rows: {len(dataframe)}")
|
||||
logger.info(f"populate_indicators Columns before freqai.start: {list(dataframe.columns)}")
|
||||
"""Populate indicators and dynamic strategy parameters."""
|
||||
logger.info(f"Processing pair: {metadata['pair']}")
|
||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||
|
||||
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")
|
||||
# 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)
|
||||
|
||||
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)
|
||||
logger.info(f"rsi stats: {dataframe['rsi'].describe().to_string()}")
|
||||
|
||||
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:
|
||||
# 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["%-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()}")
|
||||
dataframe["&-stoploss"] = -2 * dataframe["atr"] / dataframe["close"]
|
||||
dataframe["&-roi_0"] = (dataframe["close"] / dataframe["close"].shift(10) - 1).clip(0, 0.1).fillna(0)
|
||||
|
||||
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()}")
|
||||
# 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())
|
||||
|
||||
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()}")
|
||||
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())
|
||||
|
||||
if "date" in dataframe.columns:
|
||||
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
|
||||
# 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])
|
||||
|
||||
try:
|
||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||
logger.info(f"Columns after freqai.start: {list(dataframe.columns)}")
|
||||
except Exception as e:
|
||||
logger.error(f"freqai.start failed: {str(e)}")
|
||||
dataframe["buy_rsi_pred"] = 50
|
||||
dataframe["sell_rsi_pred"] = 80
|
||||
dataframe["do_predict"] = 1
|
||||
|
||||
for col in ["buy_rsi_pred", "sell_rsi_pred"]:
|
||||
if col not in dataframe.columns:
|
||||
logger.error(f"Error: {col} column not generated for pair: {metadata['pair']}")
|
||||
dataframe[col] = 50 if col == "buy_rsi_pred" else 80
|
||||
logger.info(f"{col} stats: {dataframe[col].describe().to_string()}")
|
||||
|
||||
# 调试特征分布
|
||||
if "%-bb_width-period_10_OKB/USDT_5m" in dataframe.columns:
|
||||
if dataframe["%-bb_width-period_10_OKB/USDT_5m"].std() > 0:
|
||||
dataframe["%-bb_width-period_10_OKB/USDT_5m"] = (
|
||||
dataframe["%-bb_width-period_10_OKB/USDT_5m"] - dataframe["%-bb_width-period_10_OKB/USDT_5m"].mean()
|
||||
) / dataframe["%-bb_width-period_10_OKB/USDT_5m"].std()
|
||||
logger.info(f"%-bb_width-period_10_OKB stats: {dataframe['%-bb_width-period_10_OKB/USDT_5m'].describe().to_string()}")
|
||||
if "%-bb_width-period_10_SOL/USDT_5m" in dataframe.columns:
|
||||
if dataframe["%-bb_width-period_10_SOL/USDT_5m"].std() > 0:
|
||||
dataframe["%-bb_width-period_10_SOL/USDT_5m"] = (
|
||||
dataframe["%-bb_width-period_10_SOL/USDT_5m"] - dataframe["%-bb_width-period_10_SOL/USDT_5m"].mean()
|
||||
) / dataframe["%-bb_width-period_10_SOL/USDT_5m"].std()
|
||||
logger.info(f"%-bb_width-period_10_SOL stats: {dataframe['%-bb_width-period_10_SOL/USDT_5m'].describe().to_string()}")
|
||||
|
||||
def get_expected_columns(freqai_config: dict) -> list:
|
||||
indicators = ["rsi", "bb_width", "pct-change"]
|
||||
periods = freqai_config.get("feature_parameters", {}).get("include_periods", [10, 20])
|
||||
pairs = freqai_config.get("include_corr_pairlist", ["OKB/USDT", "SOL/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)
|
||||
logger.info(f"Expected feature columns ({len(expected_columns)}): {expected_columns[:10]}...")
|
||||
|
||||
actual_columns = list(dataframe.columns)
|
||||
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}")
|
||||
|
||||
if "DI_values" in dataframe.columns:
|
||||
logger.info(f"DI_values stats: {dataframe['DI_values'].describe().to_string()}")
|
||||
logger.info(f"DI discarded predictions: {len(dataframe[dataframe['do_predict'] == 0])}")
|
||||
|
||||
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
|
||||
logger.info(f"Final columns in populate_indicators: {list(dataframe.columns)}")
|
||||
# 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, df: DataFrame, metadata: dict) -> DataFrame:
|
||||
enter_long_conditions = [
|
||||
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
|
||||
df["close"] > df["tema"],
|
||||
df["volume"] > 0,
|
||||
df["do_predict"] == 1
|
||||
]
|
||||
if enter_long_conditions:
|
||||
df.loc[
|
||||
reduce(lambda x, y: x & y, enter_long_conditions),
|
||||
["enter_long", "enter_tag"]
|
||||
] = (1, "long")
|
||||
df["entry_signal"] = reduce(lambda x, y: x & y, enter_long_conditions)
|
||||
df["entry_signal"] = df["entry_signal"].rolling(window=2, min_periods=1).max().astype(bool)
|
||||
df.loc[
|
||||
df["entry_signal"],
|
||||
["enter_long", "enter_tag"]
|
||||
] = (1, "long")
|
||||
if df["entry_signal"].iloc[-1]:
|
||||
logger.info(f"Entry signal triggered for {metadata['pair']}: rsi={df['rsi'].iloc[-1]}, buy_rsi_pred={df['buy_rsi_pred'].iloc[-1]}, do_predict={df['do_predict'].iloc[-1]}, close={df['close'].iloc[-1]}, tema={df['tema'].iloc[-1]}")
|
||||
logger.info(f"Entry conditions: RSI_cross={qtpylib.crossed_above(df['rsi'], df['buy_rsi_pred']).iloc[-1]}, Close_above_tema={df['close'].iloc[-1] > df['tema'].iloc[-1]}, Volume={df['volume'].iloc[-1] > 0}, Do_predict={df['do_predict'].iloc[-1] == 1}")
|
||||
logger.info(f"Last candle: rsi={df['rsi'].iloc[-1]}, buy_rsi_pred={df['buy_rsi_pred'].iloc[-1]}, close={df['close'].iloc[-1]}, tema={df['tema'].iloc[-1]}, do_predict={df['do_predict'].iloc[-1]}")
|
||||
return df
|
||||
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
|
||||
|
||||
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.98) |
|
||||
(df["close"] < df["bb_lowerband"]),
|
||||
df["volume"] > 0,
|
||||
df["do_predict"] == 1
|
||||
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
|
||||
]
|
||||
time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
|
||||
df.loc[
|
||||
(reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
|
||||
"exit_long"
|
||||
] = 1
|
||||
return df
|
||||
# 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:
|
||||
logger.info(f"Confirming trade entry for {pair}, order_type: {order_type}, rate: {rate}, current_time: {current_time}")
|
||||
"""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 order_type == "market":
|
||||
logger.info(f"Order confirmed for {pair}, rate: {rate} (market order)")
|
||||
return True
|
||||
if rate <= (last_candle["close"] * (1 + 0.01)):
|
||||
logger.info(f"Order confirmed for {pair}, rate: {rate}")
|
||||
return True
|
||||
logger.info(f"Order rejected: rate {rate} exceeds threshold {last_candle['close'] * 1.01}")
|
||||
return False
|
||||
if rate > (last_candle["close"] * (1 + 0.0025)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@ -1,104 +0,0 @@
|
||||
diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py
|
||||
index 1d7ed33..245f771 100644
|
||||
--- a/freqtrade/templates/FreqaiExampleStrategy.py
|
||||
+++ b/freqtrade/templates/FreqaiExampleStrategy.py
|
||||
@@ -128,9 +128,9 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
return dataframe
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
- logger.info(f"Processing pair: {metadata['pair']}")
|
||||
- logger.info(f"DataFrame rows: {len(dataframe)}")
|
||||
- logger.info(f"Columns before freqai.start: {list(dataframe.columns)}")
|
||||
+ logger.info(f"populate_indicators Processing pair: {metadata['pair']}")
|
||||
+ logger.info(f"populate_indicators DataFrame rows: {len(dataframe)}")
|
||||
+ logger.info(f"populate_indicators 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']}")
|
||||
@@ -173,6 +173,19 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
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"]
|
||||
+ 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["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()}")
|
||||
|
||||
if "date" in dataframe.columns:
|
||||
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
|
||||
@@ -197,17 +210,18 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
dataframe[col] = 50 if col == "buy_rsi_pred" else 80
|
||||
logger.info(f"{col} stats: {dataframe[col].describe().to_string()}")
|
||||
|
||||
- if "%-bb_width-period_20_SOL/USDT_5m" in dataframe.columns:
|
||||
- if dataframe["%-bb_width-period_20_SOL/USDT_5m"].std() > 0:
|
||||
- dataframe["%-bb_width-period_20_SOL/USDT_5m"] = (
|
||||
- dataframe["%-bb_width-period_20_SOL/USDT_5m"] - dataframe["%-bb_width-period_20_SOL/USDT_5m"].mean()
|
||||
- ) / dataframe["%-bb_width-period_20_SOL/USDT_5m"].std()
|
||||
- logger.info(f"%-bb_width-period_20 stats: {dataframe['%-bb_width-period_20_SOL/USDT_5m'].describe().to_string()}")
|
||||
+ # 调试特征分布
|
||||
+ if "%-bb_width-period_10_SOL/USDT_5m" in dataframe.columns:
|
||||
+ if dataframe["%-bb_width-period_10_SOL/USDT_5m"].std() > 0:
|
||||
+ dataframe["%-bb_width-period_10_SOL/USDT_5m"] = (
|
||||
+ dataframe["%-bb_width-period_10_SOL/USDT_5m"] - dataframe["%-bb_width-period_10_SOL/USDT_5m"].mean()
|
||||
+ ) / dataframe["%-bb_width-period_10_SOL/USDT_5m"].std()
|
||||
+ logger.info(f"%-bb_width-period_10 stats: {dataframe['%-bb_width-period_10_SOL/USDT_5m'].describe().to_string()}")
|
||||
|
||||
def get_expected_columns(freqai_config: dict) -> list:
|
||||
indicators = ["rsi", "bb_width", "pct-change"]
|
||||
- periods = freqai_config.get("feature_parameters", {}).get("include_periods", [20])
|
||||
- pairs = freqai_config.get("include_corr_pairlist", ["SOL/USDT"])
|
||||
+ 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"]
|
||||
@@ -242,11 +256,17 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
|
||||
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
||||
enter_long_conditions = [
|
||||
- qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
|
||||
+ qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"] + (5 if metadata["pair"] == "BTC/USDT" else 0)),
|
||||
df["tema"] > df["tema"].shift(1),
|
||||
df["volume"] > 0,
|
||||
- df["do_predict"] == 1
|
||||
+ 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")
|
||||
df["entry_signal"] = reduce(lambda x, y: x & y, enter_long_conditions)
|
||||
df["entry_signal"] = df["entry_signal"].rolling(window=2, min_periods=1).max().astype(bool)
|
||||
df.loc[
|
||||
@@ -259,14 +279,16 @@ class FreqaiExampleStrategy(IStrategy):
|
||||
|
||||
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
||||
exit_long_conditions = [
|
||||
- (qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"] - 5)) |
|
||||
+ (qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"])) |
|
||||
(df["close"] < df["close"].shift(1) * 0.98) |
|
||||
(df["close"] < df["bb_lowerband"]),
|
||||
df["volume"] > 0,
|
||||
- df["do_predict"] == 1
|
||||
+ df["do_predict"] == 1,
|
||||
+ df["up_or_down"] == 0
|
||||
]
|
||||
+ time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
|
||||
df.loc[
|
||||
- reduce(lambda x, y: x & y, exit_long_conditions),
|
||||
+ (reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
|
||||
"exit_long"
|
||||
] = 1
|
||||
return df
|
||||
@ -1,5 +0,0 @@
|
||||
开始过滤日志,输出到 freqtrade_error_logs.txt ...
|
||||
实时监控 freqtrade_freqtrade_run_ef258891294d 的日志,过滤 'but got Index'...
|
||||
开始过滤日志,输出到 freqtrade_error_logs.txt ...
|
||||
实时监控 freqtrade_freqtrade_run_ef258891294d 的日志,过滤 'but got Index'...
|
||||
未捕获到包含 'but got Index' 的日志,文件 freqtrade_error_logs.txt 为空
|
||||
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