Compare commits

...

10 Commits

Author SHA1 Message Date
zhangkun9038@dingtalk.com
c3364852c1 up
Some checks failed
Devcontainer Pre-Build / build-and-push (push) Has been cancelled
Pre-commit auto-update / auto-update (push) Has been cancelled
Binance Leverage tiers update / auto-update (push) Has been cancelled
2025-04-26 13:30:20 +08:00
zhangkun9038@dingtalk.com
7a1993a2e7 去掉一个币对试试 2025-04-26 12:26:11 +08:00
zhangkun9038@dingtalk.com
4e7e5f2363 币对设置调整, 参考:https://x.com/i/grok/share/jjdwTZTEKX6Ye1ASGpUd3P335 2025-04-26 08:23:34 +08:00
zhangkun9038@dingtalk.com
c2ebcca3c6 sol/btc/eth 2025-04-26 00:17:59 +08:00
zhangkun9038@dingtalk.com
ee2926e6a4 昨天 经过grok优化的代码跑的很流畅, 没在backtesting里充分验证 就 去 gry-run了, 今天下午backtesting结果大幅跑输,一个月后本金还剩77%, 回退到上一个版本 逐行验证, 动态进退的AI逻辑被改动了, 不知道是grok改的还是qwen-max改的, 然后我改回来了, 现在好了, 以后代码验证机制,流程一定要整明白! 2025-04-25 19:40:49 +08:00
zhangkun9038@dingtalk.com
05122af764 解决了一个ccxt的bug, okx接口返回内容有baseCcy或者quoteCcy为null, 挂载了一个新的文件替换了容器内的版本, 算是临时解决了,以后要考虑如何持续集成的问题 2025-04-25 15:23:24 +08:00
zhangkun9038@dingtalk.com
f815ee89ee dry-run时表现稳定, hyperopt表现也20% profit, 不错, 需要长时间运行看看了 2025-04-24 19:43:06 +08:00
zhangkun9038@dingtalk.com
a32c505cff dry run ok with my Strategy ok 2025-04-24 13:37:09 +08:00
zhangkun9038@dingtalk.com
928bc012d8 策略使用了 利润高的配置, 符合损失函数 评估标准的配置被注释
Some checks failed
Binance Leverage tiers update / auto-update (push) Has been cancelled
2025-04-24 00:22:00 +08:00
zhangkun9038@dingtalk.com
b41320a809 79% 年华 2025-04-23 23:31:32 +08:00
11 changed files with 9052 additions and 199 deletions

48
catch.sh Executable file
View File

@ -0,0 +1,48 @@
#!/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

8325
ccxt/async_support/okx.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,11 @@
"max_open_trades": 4, "max_open_trades": 4,
"stake_currency": "USDT", "stake_currency": "USDT",
"stake_amount": 150, "stake_amount": 150,
"startup_candle_count": 30,
"tradable_balance_ratio": 1, "tradable_balance_ratio": 1,
"fiat_display_currency": "USD", "fiat_display_currency": "USD",
"dry_run": true, "dry_run": true,
"timeframe": "3m", "timeframe": "5m",
"dry_run_wallet": 1000, "dry_run_wallet": 1000,
"cancel_open_orders_on_exit": true, "cancel_open_orders_on_exit": true,
"stoploss": -0.05, "stoploss": -0.05,
@ -23,21 +24,20 @@
"enable_ws": false, "enable_ws": false,
"ccxt_config": { "ccxt_config": {
"enableRateLimit": true, "enableRateLimit": true,
"rateLimit": 500, "rateLimit": 800,
"options": { "options": {
"defaultType": "spot" "defaultType": "spot"
} }
}, },
"ccxt_async_config": { "ccxt_async_config": {
"enableRateLimit": true, "enableRateLimit": true,
"rateLimit": 500, "rateLimit": 800,
"timeout": 20000 "timeout": 20000
}, },
"pair_whitelist": [ "pair_whitelist": [
"BTC/USDT", "BTC/USDT",
"SOL/USDT" "SOL/USDT"
], ]
"pair_blacklist": []
}, },
"entry_pricing": { "entry_pricing": {
"price_side": "same", "price_side": "same",
@ -66,8 +66,7 @@
}, },
"freqaimodel": "CatboostClassifier", "freqaimodel": "CatboostClassifier",
"purge_old_models": 2, "purge_old_models": 2,
"train_period_days": 15, "identifier": "test178",
"identifier": "test62",
"train_period_days": 30, "train_period_days": 30,
"backtest_period_days": 10, "backtest_period_days": 10,
"live_retrain_hours": 0, "live_retrain_hours": 0,
@ -76,16 +75,15 @@
}, },
"feature_parameters": { "feature_parameters": {
"include_timeframes": [ "include_timeframes": [
"3m", "5m",
"15m",
"1h" "1h"
], ],
"include_corr_pairlist": [ "include_corr_pairlist": [
"BTC/USDT", "BTC/USDT",
"SOL/USDT" "SOL/USDT"
], ],
"label_period_candles": 20, "label_period_candles": 12,
"include_shifted_candles": 2, "include_shifted_candles": 3,
"DI_threshold": 0.9, "DI_threshold": 0.9,
"weight_factor": 0.9, "weight_factor": 0.9,
"principal_component_analysis": false, "principal_component_analysis": false,
@ -98,13 +96,14 @@
"plot_feature_importances": 0 "plot_feature_importances": 0
}, },
"data_split_parameters": { "data_split_parameters": {
"test_size": 0.2 "test_size": 0.2,
"shuffle": false,
}, },
"model_training_parameters": { "model_training_parameters": {
"n_estimators": 100, "n_estimators": 100,
"learning_rate": 0.05, "learning_rate": 0.1,
"max_depth": 5, "num_leaves": 15,
"num_leaves": 31 "verbose": -1
} }
}, },
"api_server": { "api_server": {
@ -123,7 +122,7 @@
"initial_state": "running", "initial_state": "running",
"force_entry_enable": false, "force_entry_enable": false,
"internals": { "internals": {
"process_throttle_secs": 5, "process_throttle_secs": 10,
"heartbeat_interval": 20, "heartbeat_interval": 20,
"loglevel": "DEBUG" "loglevel": "DEBUG"
} }

View File

@ -23,6 +23,7 @@ services:
- "./config_examples:/freqtrade/config_examples" - "./config_examples:/freqtrade/config_examples"
- "./freqtrade/templates:/freqtrade/templates" - "./freqtrade/templates:/freqtrade/templates"
- "./freqtrade/exchange/:/freqtrade/exchange" - "./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) # Expose api on port 8080 (localhost only)
# Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation # Please read the https://www.freqtrade.io/en/stable/rest-api/ documentation
# for more information. # for more information.
@ -31,15 +32,15 @@ services:
# Default command used when running `docker compose up` # Default command used when running `docker compose up`
# --freqaimodel XGBoostRegressor # --freqaimodel XGBoostRegressor
# commangd: > command: >
# # trade trade
# --logfile /freqtrade/user_data/logs/freqtrade.log --logfile /freqtrade/user_data/logs/freqtrade.log
# --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite --db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
# --freqaimodel LightGBMRegressor --freqaimodel XGBoostRegressor
# --config /freqtrade/config_examples/config_freqai.okx.json --config /freqtrade/config_examples/config_freqai.okx.json
# --strategy FreqaiExampleStrategy --strategy FreqaiExampleStrategy
# --strategy FreqaiExampleHybridStrategy --strategy-path /freqtrade/templates
# --strategy-path /freqtrade/templates --fee 0.0008
# command: > # command: >
# backtesting # backtesting
# --logfile /freqtrade/user_data/logs/freqtrade.log # --logfile /freqtrade/user_data/logs/freqtrade.log
@ -60,13 +61,34 @@ services:
# --hyperopt-loss SharpeHyperOptLoss # --hyperopt-loss SharpeHyperOptLoss
# --spaces roi stoploss # --spaces roi stoploss
# -e 200 # -e 200
# --config /freqtrade/templates/FreqaiExampleStrategy.json
command: > # command: >
backtesting # backtesting
--logfile /freqtrade/user_data/logs/freqtrade.log # --logfile /freqtrade/user_data/logs/freqtrade.log
--freqaimodel LightGBMRegressor # --freqaimodel XGBoostRegressor
--config /freqtrade/config_examples/config_freqai.okx.json # --config /freqtrade/config_examples/config_freqai.okx.json
--config /freqtrade/templates/FreqaiExampleStrategy.json # --strategy-path /freqtrade/templates
--strategy-path /freqtrade/templates # --strategy FreqaiExampleStrategy
--strategy FreqaiExampleStrategy # --timerange 20250101-20250420
--timerange 20240920-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

View File

@ -0,0 +1,262 @@
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

View File

@ -1,32 +1,32 @@
{ {
"strategy_name": "FreqaiExampleStrategy", "strategy_name": "FreqaiExampleStrategy",
"params": { "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": {
"max_open_trades": 4 "max_open_trades": 4
}, },
"buy": { "buy": {
"buy_rsi": 39.92672300850069 "buy_rsi": 37
}, },
"sell": { "sell": {
"sell_rsi": 69.92672300850067 "sell_rsi": 80
}, },
"protection": {}, "protection": {},
"roi": { "roi": {
"0": 0.132, "0": 0.124,
"8": 0.047, "14": 0.023,
"14": 0.007, "37": 0.011,
"60": 0 "50": 0
}, },
"stoploss": { "stoploss": {
"stoploss": -0.322 "stoploss": -0.168
},
"trailing": {
"trailing_stop": true,
"trailing_stop_positive": 0.047,
"trailing_stop_positive_offset": 0.051000000000000004,
"trailing_only_offset_is_reached": true
} }
}, },
"ft_stratparam_v": 1, "ft_stratparam_v": 1,
"export_time": "2025-04-23 12:30:05.550433+00:00" "export_time": "2025-04-24 11:42:35.037486+00:00"
} }

View File

@ -0,0 +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": 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"
}

View File

@ -1,5 +1,6 @@
import logging import logging
import numpy as np import numpy as np
import pandas as pd
from functools import reduce from functools import reduce
import talib.abstract as ta import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame
@ -9,33 +10,39 @@ from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class FreqaiExampleStrategy(IStrategy): class FreqaiExampleStrategy(IStrategy):
# 移除硬编码的 minimal_roi 和 stoploss改为动态适配 minimal_roi = {
minimal_roi = {} # 将在 populate_indicators 中动态生成 "0": 0.02,
stoploss = 0.0 # 将在 populate_indicators 中动态设置 "7": 0.01,
"13": 0.005,
"60": 0
}
stoploss = 0.0
trailing_stop = True trailing_stop = True
process_only_new_candles = True process_only_new_candles = True
use_exit_signal = True use_exit_signal = True
startup_candle_count: int = 40 startup_candle_count: int = 40
can_short = False can_short = False
# 参数定义FreqAI 动态适配 buy_rsi 和 sell_rsi禁用 Hyperopt 优化 buy_rsi = IntParameter(low=10, high=40, default=20, space="buy", optimize=True, load=True)
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=True, 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_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_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) roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.009, space="roi", optimize=True, load=True)
stoploss_param = DecimalParameter(low=-0.35, high=-0.1, default=-0.182, space="stoploss", optimize=True, load=True) stoploss_param = DecimalParameter(low=-0.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 = []
# FreqAI 配置
freqai_info = { freqai_info = {
"model": "LightGBMRegressor", "model": "LightGBMRegressor",
"feature_parameters": { "feature_parameters": {
"include_timeframes": ["5m", "15m", "1h"], "include_timeframes": ["5m"],
"include_corr_pairlist": [], "include_corr_pairlist": ["OKB/USDT", "SOL/USDT"], # 与白名单一致
"label_period_candles": 12, "label_period_candles": 12,
"include_shifted_candles": 3, "include_shifted_candles": 0,
"include_periods": [20],
"DI_threshold": 1.5 # 提高以减少丢弃预测
}, },
"data_split_parameters": { "data_split_parameters": {
"test_size": 0.2, "test_size": 0.2,
@ -44,206 +51,255 @@ class FreqaiExampleStrategy(IStrategy):
"model_training_parameters": { "model_training_parameters": {
"n_estimators": 100, "n_estimators": 100,
"learning_rate": 0.1, "learning_rate": 0.1,
"num_leaves": 31, "num_leaves": 15,
"verbose": -1, "n_jobs": 4,
"verbosity": -1
}, },
} }
plot_config = { plot_config = {
"main_plot": {}, "main_plot": {
"close": {"color": "blue"},
"bb_lowerband": {"color": "purple"}
},
"subplots": { "subplots": {
"&-buy_rsi": {"&-buy_rsi": {"color": "green"}}, "&-buy_rsi": {"&-buy_rsi": {"color": "green"}},
"&-sell_rsi": {"&-sell_rsi": {"color": "red"}}, "&-sell_rsi": {"&-sell_rsi": {"color": "red"}},
"&-stoploss": {"&-stoploss": {"color": "purple"}}, "rsi": {"rsi": {"color": "black"}},
"&-roi_0": {"&-roi_0": {"color": "orange"}},
"do_predict": {"do_predict": {"color": "brown"}}, "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: def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame:
dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period) dataframe["%-rsi-period"] = ta.RSI(dataframe, timeperiod=period)
dataframe["%-mfi-period"] = ta.MFI(dataframe, timeperiod=period)
dataframe["%-sma-period"] = ta.SMA(dataframe, timeperiod=period)
dataframe["%-ema-period"] = ta.EMA(dataframe, timeperiod=period)
dataframe["%-adx-period"] = ta.ADX(dataframe, timeperiod=period)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=period, stds=2.2)
dataframe["bb_lowerband-period"] = bollinger["lower"] dataframe["%-bb_width-period"] = (bollinger["upper"] - bollinger["lower"]) / bollinger["mid"]
dataframe["bb_middleband-period"] = bollinger["mid"] dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
dataframe["bb_upperband-period"] = bollinger["upper"]
dataframe["%-bb_width-period"] = (
dataframe["bb_upperband-period"] - dataframe["bb_lowerband-period"]
) / dataframe["bb_middleband-period"]
dataframe["%-close-bb_lower-period"] = dataframe["close"] / dataframe["bb_lowerband-period"]
dataframe["%-roc-period"] = ta.ROC(dataframe, timeperiod=period)
dataframe["%-relative_volume-period"] = (
dataframe["volume"] / dataframe["volume"].rolling(period).mean()
)
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.ffill(inplace=True)
dataframe.fillna(0, inplace=True)
return dataframe return dataframe
def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
dataframe["%-pct-change"] = dataframe["close"].pct_change() dataframe["%-pct-change"] = dataframe["close"].pct_change()
dataframe["%-raw_volume"] = dataframe["volume"] dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
dataframe["%-raw_price"] = dataframe["close"]
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.fillna(method='ffill', inplace=True)
dataframe.fillna(0, inplace=True)
return dataframe return dataframe
def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: def feature_engineering_standard(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
if len(dataframe) < 20 or dataframe["close"].isna().any():
logger.warning(f"DataFrame too short ({len(dataframe)} rows) or contains NaN in close, cannot compute %-volatility")
dataframe["%-volatility"] = 0
else:
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
if dataframe["%-volatility"].std() > 0:
dataframe["%-volatility"] = (dataframe["%-volatility"] - dataframe["%-volatility"].mean()) / dataframe["%-volatility"].std()
dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
dataframe.replace([np.inf, -np.inf], 0, inplace=True) dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
dataframe.fillna(method='ffill', inplace=True)
dataframe.fillna(0, inplace=True)
return dataframe return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}") logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}")
if "close" not in dataframe.columns: if "close" not in dataframe.columns:
logger.error("数据框缺少必要的 'close'") logger.error("DataFrame missing required 'close' column")
raise ValueError("数据框缺少必要的 'close'") raise ValueError("DataFrame missing required 'close' column")
try: try:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"] label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 生成 %-volatility 特征 if len(dataframe) < 20 or dataframe["close"].isna().any():
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() 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) dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).shift(-label_period)
# 数据清理
for col in ["&-buy_rsi", "%-volatility"]: for col in ["&-buy_rsi", "%-volatility"]:
dataframe[col].replace([np.inf, -np.inf], 0, inplace=True) dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0)
dataframe[col].fillna(method='ffill', inplace=True) dataframe[col] = dataframe[col].ffill().fillna(0)
dataframe[col].fillna(0, inplace=True)
if dataframe[col].isna().any(): if dataframe[col].isna().any():
logger.warning(f"目标列 {col} 仍包含 NaN检查数据生成逻辑") logger.warning(f"Target column {col} still contains NaN, check data generation logic")
except Exception as e: except Exception as e:
logger.error(f"创建 FreqAI 目标失败:{str(e)}") logger.error(f"Failed to create FreqAI targets: {str(e)}")
raise raise
logger.info(f"Target columns preview: {dataframe[['&-buy_rsi']].head().to_string()}")
logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}")
return dataframe return dataframe
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
logger.info(f"处理交易对:{metadata['pair']}") logger.info(f"populate_indicators Processing pair: {metadata['pair']}")
dataframe = self.freqai.start(dataframe, metadata, self) 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():
dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) logger.error(f"DataFrame missing 'close' column or all NaN for pair: {metadata['pair']}")
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) raise ValueError("DataFrame missing valid 'close' column")
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 目标) if len(dataframe) < 14:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"] logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute rsi")
dataframe["up_or_down"] = np.where( dataframe["rsi"] = 50
dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0 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():
if "&-buy_rsi" in dataframe.columns: logger.warning(f"DataFrame too short ({len(dataframe)} rows) or contains NaN in close, cannot compute %-volatility")
# 派生其他目标 dataframe["%-volatility"] = 0
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30 else:
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25) dataframe["%-volatility"] = dataframe["%-volatility"].fillna(0)
dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2) 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()}")
# 限制预测值,添加平滑 if len(dataframe) < 9:
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50) logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute tema")
dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean(), inplace=True) dataframe["tema"] = dataframe["close"]
if dataframe["buy_rsi_pred"].isna().any(): else:
logger.warning("buy_rsi_pred 列包含 NaN已填充为默认值") dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90) if dataframe["tema"].isna().any():
dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].mean(), inplace=True) logger.warning("tema contains NaN, filling with close")
if dataframe["sell_rsi_pred"].isna().any(): dataframe["tema"] = dataframe["tema"].fillna(dataframe["close"])
logger.warning("sell_rsi_pred 列包含 NaN已填充为默认值") logger.info(f"tema stats: {dataframe['tema'].describe().to_string()}")
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已填充为默认值")
# 检查预测值 if len(dataframe) < 20:
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]: logger.warning(f"DataFrame too short ({len(dataframe)} rows), cannot compute bb_lowerband")
if dataframe[col].isna().any(): dataframe["bb_lowerband"] = dataframe["close"]
logger.warning(f"{col} 包含 NaN填充为默认值") else:
dataframe[col].fillna(dataframe[col].mean(), inplace=True) 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()}")
# 动态追踪止盈 if "date" in dataframe.columns:
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3) dataframe["%-day_of_week"] = dataframe["date"].dt.dayofweek
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4) 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
# 设置策略级参数 try:
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1]) dataframe = self.freqai.start(dataframe, metadata, self)
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1]) logger.info(f"Columns after freqai.start: {list(dataframe.columns)}")
self.stoploss = float(self.stoploss_param.value) except Exception as e:
self.minimal_roi = { logger.error(f"freqai.start failed: {str(e)}")
0: float(self.roi_0.value), dataframe["buy_rsi_pred"] = 50
15: float(self.roi_15.value), dataframe["sell_rsi_pred"] = 80
30: float(self.roi_30.value), dataframe["do_predict"] = 1
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}, " for col in ["buy_rsi_pred", "sell_rsi_pred"]:
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}") 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()}")
dataframe.replace([np.inf, -np.inf], 0, inplace=True) # 调试特征分布
dataframe.fillna(method='ffill', inplace=True) if "%-bb_width-period_10_OKB/USDT_5m" in dataframe.columns:
dataframe.fillna(0, inplace=True) 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()}")
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}") def get_expected_columns(freqai_config: dict) -> list:
logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}") 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)}")
return dataframe return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
enter_long_conditions = [ enter_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]), qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
df["tema"] > df["tema"].shift(1), df["close"] > df["tema"],
df["volume"] > 0, df["volume"] > 0,
df["do_predict"] == 1, df["do_predict"] == 1
df["up_or_down"] == 1
] ]
if enter_long_conditions: if enter_long_conditions:
df.loc[ df.loc[
reduce(lambda x, y: x & y, enter_long_conditions), reduce(lambda x, y: x & y, enter_long_conditions),
["enter_long", "enter_tag"] ["enter_long", "enter_tag"]
] = (1, "long") ] = (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 return df
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [ exit_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]), (qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"])) |
(df["close"] < df["close"].shift(1) * 0.97), (df["close"] < df["close"].shift(1) * 0.98) |
(df["close"] < df["bb_lowerband"]),
df["volume"] > 0, df["volume"] > 0,
df["do_predict"] == 1, df["do_predict"] == 1
df["up_or_down"] == 0
] ]
if exit_long_conditions: time_exit = (df["date"] >= df["date"].shift(1) + pd.Timedelta(days=1))
df.loc[ df.loc[
reduce(lambda x, y: x & y, exit_long_conditions), (reduce(lambda x, y: x & y, exit_long_conditions)) | time_exit,
"exit_long" "exit_long"
] = 1 ] = 1
return df return df
def confirm_trade_entry( def confirm_trade_entry(
self, pair: str, order_type: str, amount: float, rate: float, self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time, entry_tag, side: str, **kwargs time_in_force: str, current_time, entry_tag, side: str, **kwargs
) -> bool: ) -> 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) df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = df.iloc[-1].squeeze() last_candle = df.iloc[-1].squeeze()
if side == "long": if side == "long":
if rate > (last_candle["close"] * (1 + 0.0025)): if order_type == "market":
return False 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 return True

View File

@ -0,0 +1,104 @@
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

0
freqtrade_error_logs.txt Normal file
View File

5
nohup.out Normal file
View File

@ -0,0 +1,5 @@
开始过滤日志,输出到 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 为空