backtest hyperopt也加入utc-8时区
This commit is contained in:
parent
7e604f8a91
commit
9de38a7958
286
doc/changelog_timezone_fix.md
Normal file
286
doc/changelog_timezone_fix.md
Normal file
@ -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)
|
||||
@ -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"
|
||||
|
||||
184
docs/changelog_head15.md
Normal file
184
docs/changelog_head15.md
Normal file
@ -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. **入场诊断日志误导**:已在本轮修复,拆分为数据新鲜度和信号时间两个独立日志
|
||||
Loading…
x
Reference in New Issue
Block a user