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

View File

@ -23,6 +23,7 @@ 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.
@ -31,15 +32,15 @@ services:
# Default command used when running `docker compose up`
# --freqaimodel XGBoostRegressor
# 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: >
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
# command: >
# backtesting
# --logfile /freqtrade/user_data/logs/freqtrade.log
@ -60,13 +61,34 @@ services:
# --hyperopt-loss SharpeHyperOptLoss
# --spaces roi stoploss
# -e 200
# --config /freqtrade/templates/FreqaiExampleStrategy.json
command: >
backtesting
--logfile /freqtrade/user_data/logs/freqtrade.log
--freqaimodel LightGBMRegressor
--config /freqtrade/config_examples/config_freqai.okx.json
--config /freqtrade/templates/FreqaiExampleStrategy.json
--strategy-path /freqtrade/templates
--strategy FreqaiExampleStrategy
--timerange 20240920-20250420
# 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

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",
"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
"buy_rsi": 37
},
"sell": {
"sell_rsi": 69.92672300850067
"sell_rsi": 80
},
"protection": {},
"roi": {
"0": 0.132,
"8": 0.047,
"14": 0.007,
"60": 0
"0": 0.124,
"14": 0.023,
"37": 0.011,
"50": 0
},
"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,
"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 numpy as np
import pandas as pd
from functools import reduce
import talib.abstract as ta
from pandas import DataFrame
@ -9,33 +10,39 @@ 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 中动态设置
minimal_roi = {
"0": 0.02,
"7": 0.01,
"13": 0.005,
"60": 0
}
stoploss = 0.0
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 参数
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.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 = {
"model": "LightGBMRegressor",
"feature_parameters": {
"include_timeframes": ["5m", "15m", "1h"],
"include_corr_pairlist": [],
"include_timeframes": ["5m"],
"include_corr_pairlist": ["OKB/USDT", "SOL/USDT"], # 与白名单一致
"label_period_candles": 12,
"include_shifted_candles": 3,
"include_shifted_candles": 0,
"include_periods": [20],
"DI_threshold": 1.5 # 提高以减少丢弃预测
},
"data_split_parameters": {
"test_size": 0.2,
@ -44,206 +51,255 @@ class FreqaiExampleStrategy(IStrategy):
"model_training_parameters": {
"n_estimators": 100,
"learning_rate": 0.1,
"num_leaves": 31,
"verbose": -1,
"num_leaves": 15,
"n_jobs": 4,
"verbosity": -1
},
}
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:
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)
dataframe["%-bb_width-period"] = (bollinger["upper"] - bollinger["lower"]) / bollinger["mid"]
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
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)
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
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()
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)
dataframe = dataframe.replace([np.inf, -np.inf], 0).ffill().fillna(0)
return 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:
logger.error("数据框缺少必要的 'close'")
raise ValueError("数据框缺少必要的 'close'")
logger.error("DataFrame missing required 'close' column")
raise ValueError("DataFrame missing required 'close' column")
try:
label_period = self.freqai_info["feature_parameters"]["label_period_candles"]
# 生成 %-volatility 特征
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
# 单一回归目标
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].replace([np.inf, -np.inf], 0, inplace=True)
dataframe[col].fillna(method='ffill', inplace=True)
dataframe[col].fillna(0, inplace=True)
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"目标列 {col} 仍包含 NaN检查数据生成逻辑")
logger.warning(f"Target column {col} still contains NaN, check data generation logic")
except Exception as e:
logger.error(f"创建 FreqAI 目标失败:{str(e)}")
logger.error(f"Failed to create FreqAI targets: {str(e)}")
raise
logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}")
logger.info(f"Target columns preview: {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"]
dataframe["up_or_down"] = np.where(
dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0
)
# 动态设置参数
if "&-buy_rsi" in dataframe.columns:
# 派生其他目标
dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30
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']}")
raise ValueError("DataFrame missing valid 'close' column")
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:
dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std()
dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25)
dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2)
# 限制预测值,添加平滑
dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50)
dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean(), inplace=True)
if dataframe["buy_rsi_pred"].isna().any():
logger.warning("buy_rsi_pred 列包含 NaN已填充为默认值")
dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90)
dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].mean(), inplace=True)
if dataframe["sell_rsi_pred"].isna().any():
logger.warning("sell_rsi_pred 列包含 NaN已填充为默认值")
dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1)
dataframe["stoploss_pred"].fillna(dataframe["stoploss_pred"].mean(), inplace=True)
if dataframe["stoploss_pred"].isna().any():
logger.warning("stoploss_pred 列包含 NaN已填充为默认值")
dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2)
dataframe["roi_0_pred"].fillna(dataframe["roi_0_pred"].mean(), inplace=True)
if dataframe["roi_0_pred"].isna().any():
logger.warning("roi_0_pred 列包含 NaN已填充为默认值")
# 检查预测值
for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]:
if dataframe[col].isna().any():
logger.warning(f"{col} 包含 NaN填充为默认值")
dataframe[col].fillna(dataframe[col].mean(), inplace=True)
# 动态追踪止盈
dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3)
dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4)
# 设置策略级参数
self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1])
self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1])
self.stoploss = float(self.stoploss_param.value)
self.minimal_roi = {
0: float(self.roi_0.value),
15: float(self.roi_15.value),
30: float(self.roi_30.value),
60: 0
}
self.trailing_stop_positive = float(dataframe["trailing_stop_positive"].iloc[-1])
self.trailing_stop_positive_offset = float(dataframe["trailing_stop_positive_offset"].iloc[-1])
logger.info(f"动态参数buy_rsi={self.buy_rsi.value}, sell_rsi={self.sell_rsi.value}, "
f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}")
dataframe.replace([np.inf, -np.inf], 0, inplace=True)
dataframe.fillna(method='ffill', inplace=True)
dataframe.fillna(0, inplace=True)
logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}")
logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}")
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()}")
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()}")
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()}")
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
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)}")
return dataframe
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
enter_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]),
df["tema"] > df["tema"].shift(1),
df["close"] > df["tema"],
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]}, 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_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
exit_long_conditions = [
qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]),
(df["close"] < df["close"].shift(1) * 0.97),
(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["up_or_down"] == 0
df["do_predict"] == 1
]
if exit_long_conditions:
df.loc[
reduce(lambda x, y: x & y, exit_long_conditions),
"exit_long"
] = 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,
"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:
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.0025)):
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

@ -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 为空