From 9de38a7958f256e1535e72e907e01f91e7288b98 Mon Sep 17 00:00:00 2001 From: "zhangkun9038@dingtalk.com" Date: Mon, 9 Feb 2026 19:44:31 +0800 Subject: [PATCH] =?UTF-8?q?backtest=20hyperopt=E4=B9=9F=E5=8A=A0=E5=85=A5u?= =?UTF-8?q?tc-8=E6=97=B6=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/changelog_timezone_fix.md | 286 ++++++++++++++++++++++++++++++++++ docker-compose.yml | 3 + docs/changelog_head15.md | 184 ++++++++++++++++++++++ 3 files changed, 473 insertions(+) create mode 100644 doc/changelog_timezone_fix.md create mode 100644 docs/changelog_head15.md diff --git a/doc/changelog_timezone_fix.md b/doc/changelog_timezone_fix.md new file mode 100644 index 00000000..78034a7c --- /dev/null +++ b/doc/changelog_timezone_fix.md @@ -0,0 +1,286 @@ +# FreqAI 时区修复与数据新鲜度监控优化 + +**分支**: `freq-ai-ae475be4d932e` +**时间范围**: 2026-02-08 ~ 2026-02-09 +**提交范围**: `e22c6d8a..7e604f8a`(共 15 次提交) + +--- + +## 一、变更概览 + +| 提交 | 日期 | 说明 | +|------|------|------| +| `7e604f8a` | 02-09 08:47 | 移除入场诊断相关log,澄清数据新鲜度问题 | +| `fa4014ec` | 02-09 08:36 | debug数据新鲜度问题 | +| `06838982` | 02-09 03:10 | 币对变更 | +| `531d630b` | 02-08 21:46 | 准备放到kiko里运行,币对列表增加到15个 | +| `4b765949` | 02-08 18:02 | fix: 修复波动系数计算中未定义的 get_current_time | +| `15a76730` | 02-08 17:47 | fix: 冷启动保护时区兼容 - 框架current_time是offset-aware | +| `8331a2e7` | 02-08 17:28 | fix: 日志显示时间也转换为UTC+8 | +| `7ad77f60` | 02-08 17:19 | fix: 修复时区转换 - FreqAI dataframe date列是UTC | +| `ec3395d0` | 02-08 17:08 | fix: 设置容器时区为 UTC+8 (Asia/Shanghai) | +| `424577fa` | 02-08 16:48 | 无论虚机系统时区是什么,代码都统一使用 UTC+8 | +| `2d20aaf7` | 02-08 15:38 | 时区问题导致数据不新鲜问题被放大 | +| `9e007086` | 02-08 13:37 | live减少训练量,币对改成8个 | +| `7cf91070` | 02-08 12:49 | 应该不再出现 KeyError: 'sma' 错误 | +| `e28bcbb1` | 02-08 04:22 | ML数据新鲜度问题+3 | +| `e22c6d8a` | 02-08 04:04 | ML数据新鲜度问题+2 | + +--- + +## 二、核心问题与解决方案 + +### 2.1 时区混乱导致的虚假告警 + +**问题描述**: + +数据新鲜度监控日志显示延迟 500+ 分钟(约 8 小时),实际上是时区问题导致的虚假告警。 + +**根因分析**: + +1. **Docker 容器时区默认 UTC+0**:即使宿主机是 UTC+8,容器内 `datetime.now()` 返回 UTC 时间 +2. **FreqAI dataframe 的 date 列是 UTC**:框架存储的 K 线时间戳是 UTC +3. **时区混合比较**:UTC+8 的 `datetime.now()` 与 UTC 的 dataframe date 列直接相减,导致 8 小时偏差 + +**解决方案**: + +#### a. 设置容器时区(`tools/live.sh` / `dryrun.sh`) + +```bash +docker run -d --restart=always \ + -e TZ=Asia/Shanghai \ + -v /etc/localtime:/etc/localtime:ro \ + ... +``` + +#### b. 代码统一时区管理(`freqaiprimer.py`) + +```python +from datetime import datetime, timezone, timedelta + +# 全局时区常量 +UTC_PLUS_8 = timezone(timedelta(hours=8)) + +# FreqAI dataframe 的 date 列转换 +if data_timestamp.tzinfo is None: + data_timestamp = data_timestamp.replace(tzinfo=timezone.utc) +# 转换为 UTC+8 并移除时区信息 +data_timestamp = data_timestamp.astimezone(UTC_PLUS_8).replace(tzinfo=None) +``` + +**效果**:延迟从 **600+ 分钟** 恢复到 **4-16 分钟**。 + +--- + +### 2.2 offset-naive 与 offset-aware 兼容性 + +**问题描述**: + +``` +TypeError: can't subtract offset-naive and offset-aware datetimes +``` + +**根因**: + +- 框架传入的 `current_time` 是 **offset-aware**(带时区信息 `+00:00`) +- 策略内部的 `_strategy_start_time = datetime.now()` 是 **offset-naive**(无时区信息) + +**解决方案**: + +```python +# 冷启动保护时区兼容 +if current_time.tzinfo is not None: + local_current_time = current_time.astimezone(UTC_PLUS_8).replace(tzinfo=None) +else: + local_current_time = current_time +elapsed_minutes = (local_current_time - self._strategy_start_time).total_seconds() / 60.0 +``` + +--- + +### 2.3 入场诊断日志的误导性 + +**问题描述**: + +旧版日志输出: +``` +[入场诊断] XRP/USDT | 数据时间: 22:57:00 | 延迟: ❌ 561min +``` + +用户误以为"数据停在 22:57,延迟 561 分钟",实际上这是 **最后一次入场信号的时间**,不是当前数据时间。 + +**解决方案**: + +拆分为两个独立日志: + +1. **数据新鲜度日志**(独立显示): +```python +latest_data_date = dataframe['date'].iloc[-1] # dataframe 最后一行 +self.strategy_log( + f"[{pair}] 数据新鲜度: ✅ 4.7min | 最新K线: 08:36:00 | 总行数: 299" +) +``` + +2. **入场信号日志**(仅在有信号时显示): +```python +# 移除了旧的 entry_signals 遍历日志 +# 只在 confirm_trade_entry 中输出实际触发的入场诊断 +``` + +--- + +### 2.4 undefined `get_current_time` 错误 + +**问题描述**: + +``` +NameError: name 'get_current_time' is not defined +``` + +**原因**:之前定义的辅助函数被删除,但波动系数计算中还在使用。 + +**解决方案**: + +```python +# 旧代码 +current_time = get_current_time() + +# 新代码 +current_time = datetime.now().timestamp() +``` + +--- + +### 2.5 `KeyError: 'sma'` 防御性修复 + +**问题描述**: + +在 `custom_roi` 中访问 `dataframe['sma']` 时可能不存在。 + +**解决方案**: + +```python +# 旧代码 +current_state = dataframe['market_state'].iloc[-1] if 'market_state' in dataframe.columns else ( + 'strong_bull' if dataframe['sma'].diff().iloc[-1] > 0.01 else ... +) + +# 新代码 +if 'market_state' in dataframe.columns: + current_state = dataframe['market_state'].iloc[-1] +elif 'sma' in dataframe.columns and len(dataframe) > 1: + sma_diff = dataframe['sma'].diff().iloc[-1] + current_state = 'strong_bull' if sma_diff > 0.01 else ... +else: + current_state = 'neutral' +``` + +--- + +## 三、配置变更 + +### 3.1 币对白名单调整 + +**dryrun.json** 和 **live.json**: + +- **移除**:AAVE、BCH、CHZ、ETC、FIL、ICP、PENGU、PUMP、SHIB、SNX、WIF、XAUT、KAITO、MASK、OKB、PAXG、PI、STETH、STRK、USDG、XPL +- **保留/新增**:ADA、AVAX、BNB、BTC、DOGE、DOT、ETH、LINK、LTC、PEPE、SOL、SUI、TON、TRB、TRUMP、TRX、UNI、WLD、XLM、XRP + +**live.sh**: + +- 默认白名单从 15 个币对调整为 15 个(实际列表变更) +- **移除远程币对获取**:注释掉 `remote_pairs=$(get_remote_pairs ...)` +- 只合并两个来源:数据库 + 默认白名单 + +--- + +## 四、代码架构优化 + +### 4.1 时区管理统一化 + +**全局常量**: +```python +UTC_PLUS_8 = timezone(timedelta(hours=8)) +``` + +**统一转换逻辑**: +- FreqAI dataframe date 列:UTC → UTC+8 +- 日志显示时间:UTC → UTC+8 +- 冷启动保护时间比较:offset-aware → naive + +### 4.2 import 优化 + +**旧代码**: +```python +import datetime +current_time = datetime.datetime.now() +``` + +**新代码**: +```python +from datetime import datetime, timezone, timedelta +current_time = datetime.now() +``` + +--- + +## 五、效果验证 + +### 5.1 数据新鲜度恢复正常 + +**修复前**: +``` +[入场诊断] XRP/USDT | 数据时间: 22:57:00 | 延迟: ❌ 561min +``` + +**修复后**: +``` +[XRP/USDT] 数据新鲜度: ✅ 4.7min | 最新K线: 08:36:00 | 总行数: 299 +[入场诊断] XRP/USDT | 信号时间: 22:57:00 | 延迟: ❌ 561min | ... +``` + +现在用户可以清楚区分: +- **数据新鲜度**:4.7 分钟(✅ 正常) +- **信号时间**:22:57 是历史信号,不代表数据过期 + +### 5.2 时区错误消除 + +不再出现 `TypeError: can't subtract offset-naive and offset-aware datetimes`。 + +### 5.3 KeyError 防御 + +不再因为 `sma` 列不存在而崩溃,改为安全的回退逻辑。 + +--- + +## 六、部署注意事项 + +### 6.1 容器重启必要性 + +修改了容器时区设置(`-e TZ=Asia/Shanghai`),需要重新部署容器才能生效。 + +### 6.2 时区一致性要求 + +- **宿主机**:可以是任意时区 +- **容器**:强制 UTC+8(通过环境变量) +- **代码**:统一使用 UTC+8(通过 `UTC_PLUS_8` 常量) + +### 6.3 币对数量限制 + +当前配置 15 个币对,在高性能硬件(如 kiko 的 Ryzen 9 7945HX)上,单币对训练约 15-25 秒,总训练时间约 4-6 分钟,远低于 2 小时重训周期。 + +--- + +## 七、后续优化方向 + +1. **数据新鲜度告警阈值配置化**:当前硬编码 10/30 分钟阈值,可改为策略参数 +2. **时区配置灵活化**:支持通过环境变量或配置文件指定目标时区 +3. **入场信号日志精简**:考虑完全移除历史信号统计日志,只保留实时入场诊断 +4. **币对白名单管理**:从配置文件中彻底移除黑名单币对,避免混淆 + +--- + +## 八、相关文档 + +- 上一轮变更总结:[`docs/changelog_head15.md`](../docs/changelog_head15.md) +- 策略改进文档:[`doc/strategy_improvements.md`](./strategy_improvements.md) diff --git a/docker-compose.yml b/docker-compose.yml index 9cc01a8b..f3a9caeb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,11 +17,14 @@ services: # context: . # dockerfile: "./docker/Dockerfile.custom" restart: always + environment: + - TZ=Asia/Shanghai extra_hosts: - "www.okx.com:104.18.43.174" # 域名:IP 格式 - "api.okx.com:104.18.43.174" # 域名:IP 格式 container_name: freqtrade volumes: + - "/etc/localtime:/etc/localtime:ro" - "./user_data:/freqtrade/user_data" - "./config_examples:/freqtrade/config_examples" - "./freqtrade/templates:/freqtrade/templates" diff --git a/docs/changelog_head15.md b/docs/changelog_head15.md new file mode 100644 index 00000000..5265684a --- /dev/null +++ b/docs/changelog_head15.md @@ -0,0 +1,184 @@ +# 最近 15 次提交变更总结 + +**时间范围**: 2026-02-02 ~ 2026-02-08 +**提交范围**: `45c3c0f1..3d3cc817`(共 15 次提交) +**涉及文件**: 13 个核心文件(不含自动生成的 analysis 文件) + +--- + +## 一、变更概览 + +| 提交 | 日期 | 说明 | +|------|------|------| +| `3d3cc817` | 02-08 | ML数据新鲜度问题修复(续) | +| `f2858280` | 02-08 | 重大发现:ML数据新鲜度监控 BUG | +| `4c51aa5c` | 02-08 | debug identifier 配置(续) | +| `a1fdcd67` | 02-08 | debug identifier 配置 | +| `3c2c30b7` | 02-08 | dryrun/live 分别设置各自的 identifier | +| `29cd9278` | 02-08 | 不再删除训练数据,复用已有模型(续) | +| `5f00d355` | 02-08 | 不再删除训练数据,复用已有模型 | +| `51c30588` | 02-07 | FreqAI 训练模型名字按环境区分 | +| `f47f558f` | 02-07 | 更新 trailing stop 参数 | +| `bb64aaa6` | 02-03 | OKX API rate limit 调整为 1000ms | +| `c861f08b` | 02-02 | 杂项更新 | +| `15321936` | 02-02 | 入场 tag 分类 + 差异化退出策略 | +| `3ee798c2` | 02-02 | live 配置调整 | +| `c8616c31` | 02-02 | 全面的量价关系指标 | +| `45c3c0f1` | 02-02 | 趋势判断逻辑全面改进 | + +--- + +## 二、核心策略变更 (`freqtrade/templates/freqaiprimer.py`) + +共新增约 **1050 行**代码,是本轮变更的主体。 + +### 2.1 趋势判断逻辑改进 + +**旧逻辑**:单纯使用 EMA50/EMA200 金叉死叉判断趋势。 + +**新逻辑**:多维度投票机制(三者投票,≥2票为趋势确认): + +| 维度 | 3m | 15m | 1h | +|------|-----|------|-----| +| EMA 趋势 | EMA50 > EMA200 | EMA50 > EMA200 | EMA50 > EMA200 | +| 价格动量 | close > close.shift(5) | close > close.shift(5) | close > close.shift(5) | +| EMA 斜率 | > 0.1% | > 0.2% | > 0.3% | + +**趋势强度**:引入 EMA 斜率作为趋势一致性权重,`market_score` 计算从简单的 0/1 变为连续值。 + +新增辅助指标: +- `macd_trend_strength`:MACD 趋势强度 +- `rsi_trend_strength`:RSI 偏离中性区域程度 + +### 2.2 成交量与量价关系指标(全新模块) + +**成交量趋势指标**: +- `volume_change`:成交量变化率 +- `volume_ma_ratio`:成交量/MA 比值 +- `volume_std` / `volume_cv`:成交量标准差和变异系数 +- `volume_zscore`:Z 分数(检测异常成交量) +- `volume_trend`:成交量趋势方向 +- `volume_ema_fast/slow`:成交量 EMA 快慢线趋势 + +**量价关系指标**: +- `volume_price_confirmation`:量价确认度(同向变动) +- `volume_price_ratio`:量价比率 +- `bearish_divergence`:顶背离(价格新高但量未新高) +- `bullish_divergence`:底背离(价格新低但量未新低) +- `obv`:On Balance Volume 平衡成交量 +- `volume_price_corr_short/medium`:量价相关系数(10/20 窗口) + +### 2.3 入场类型识别与标签系统(全新模块) + +新增 `identify_entry_type()` 函数,根据技术指标特征组合识别 7 种入场类型: + +| 类型 | 名称 | 识别特征 | 建议止盈 | +|------|------|---------|---------| +| 0 | 快进快出 | 高波动 + 极端RSI + 动量背离 | 2% | +| 1 | 短期波段 | 中等波动 + 边界RSI | 4% | +| 2 | 中期趋势 | 中等波动 + 中性RSI | 6% | +| 3 | 长期持有 | 强趋势 + 低波动 + 多头排列 | 10% | +| 4 | 套利机会 | 均值回归区域 + 低波动 | 3% | +| 5 | 突破追涨 | 巨量 + 强动量 | 8% | +| 6 | 震荡套利 | 震荡区间 + 低波动 | 2.5% | + +**标签格式**:`type:X,duration:Y,risk:Z,confidence:W,name:NAME,market_state:S,trend_strength:T` + +### 2.4 差异化退出策略 + +**`custom_roi()`(全新函数)**:根据入场类型和市场状态动态调整 ROI 目标。 + +**`confirm_trade_exit()` 增强**: +- 获取高级市场状态(`get_advanced_market_state`) +- 在熊市中更积极保护利润: + - 降低出场阈值(趋势强度越大,阈值越低) + - 缩短持仓容忍时间(下跌趋势最大 24 小时) + - 持仓超 1 小时 + 小幅盈利 → 考虑提前出场 + +**`custom_stoploss()` 增强**: +- 根据入场类型、市场状态、趋势强度调整止损策略 +- 熊市中使用更紧的止损乘数 +- 解析 `entry_tag` 获取入场类型和风险等级 + +**`confirm_trade_entry()` 增强**: +- 价格保护逻辑:在强熊市中要求更高折扣才能入场 +- 折扣幅度随趋势强度和可信度动态调整 +- 入场时生成完整标签并传递给后续回调 + +### 2.5 市场环境综合管理(全新模块) + +新增三个核心函数: + +| 函数 | 功能 | +|------|------| +| `get_advanced_market_state()` | 返回 state/strength/confidence 三元组 | +| `get_comprehensive_market_context()` | 整合市场形态标签和趋势信息 | +| `identify_entry_type()` | 根据指标组合识别入场类型 | + +### 2.6 数据新鲜度监控系统 + +新增 `_freshness_stats` 统计字典和相关方法: + +| 方法 | 功能 | +|------|------| +| `update_freshness_stats()` | 更新每个币对的延迟统计 | +| `report_freshness_stats()` | 每小时输出统计报告 | + +**数据新鲜度日志**:在 `populate_entry_trend` 中新增独立的数据新鲜度检查,显示 dataframe 最后一行的时间和延迟。 + +**BUG 修复**:之前的入场诊断日志误用"最近一次入场信号的时间"作为"数据时间",导致无入场信号时虚假报告数百分钟延迟。已拆分为独立的数据新鲜度日志和入场信号日志。 + +### 2.7 Trailing Stop 参数调整 + +| 参数 | 旧值 | 新值 | +|------|------|------| +| `trailing_stop_positive` | 0.0125 | 0.0075 | +| `trailing_stop_positive_offset` | 0.045 | 0.0225 | + +更紧的追踪止损,更快锁定利润。 + +--- + +## 三、配置变更 + +### 3.1 FreqAI identifier 环境隔离 + +- `dryrun.json` 新增 `"identifier": "dryrun-freqai"` +- `live.json` 使用独立的 `"identifier": "live-freqai"`(在 live.sh 中设置) +- `freqaiprimer.json` 移除硬编码的 identifier,由部署脚本注入 + +**目的**:防止 dryrun 和 live 共享模型文件导致冲突。 + +### 3.2 OKX API Rate Limit + +`live.json`: `rateLimit` 从 500ms 调整为 **1000ms**,降低 API 请求频率,避免限流。 + +### 3.3 币对白名单更新 + +`live.json` 和 `dryrun.json` 的 `pair_whitelist` 均有调整,移除部分币对,新增多个币对。 + +--- + +## 四、部署脚本变更 + +### 4.1 保留训练模型(`live.sh` / `dryrun.sh`) + +**关键变更**:注释掉 `rm -rf user_data/models/*`,不再在每次部署时删除已训练的模型。 + +**效果**:容器重启后可复用已有模型,从数小时的冷启动缩短到秒级推理。 + +### 4.2 默认配置文件 + +`live.sh` 和 `dryrun.sh` 的默认配置从 `basic.json` 改为 `freqaiprimer.json`。 + +### 4.3 backtest.sh 清理简化 + +移除 `rm -rf user_data/models/*` 和 `rm -fr ./user_data/dryrun_results/*`,只保留回测结果清理。 + +--- + +## 五、已知问题与后续 + +1. **时区问题**(在后续会话中修复):Docker 容器默认 UTC+0,导致数据新鲜度计算虚假告警 +2. **OKB/USDT 数据缺失**:OKB 在 OKX 现货无数据,会阻塞训练流水线 +3. **入场诊断日志误导**:已在本轮修复,拆分为数据新鲜度和信号时间两个独立日志