8.0 KiB
8.0 KiB
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 小时),实际上是时区问题导致的虚假告警。
根因分析:
- Docker 容器时区默认 UTC+0:即使宿主机是 UTC+8,容器内
datetime.now()返回 UTC 时间 - FreqAI dataframe 的 date 列是 UTC:框架存储的 K 线时间戳是 UTC
- 时区混合比较:UTC+8 的
datetime.now()与 UTC 的 dataframe date 列直接相减,导致 8 小时偏差
解决方案:
a. 设置容器时区(tools/live.sh / dryrun.sh)
docker run -d --restart=always \
-e TZ=Asia/Shanghai \
-v /etc/localtime:/etc/localtime:ro \
...
b. 代码统一时区管理(freqaiprimer.py)
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(无时区信息)
解决方案:
# 冷启动保护时区兼容
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 分钟",实际上这是 最后一次入场信号的时间,不是当前数据时间。
解决方案:
拆分为两个独立日志:
- 数据新鲜度日志(独立显示):
latest_data_date = dataframe['date'].iloc[-1] # dataframe 最后一行
self.strategy_log(
f"[{pair}] 数据新鲜度: ✅ 4.7min | 最新K线: 08:36:00 | 总行数: 299"
)
- 入场信号日志(仅在有信号时显示):
# 移除了旧的 entry_signals 遍历日志
# 只在 confirm_trade_entry 中输出实际触发的入场诊断
2.4 undefined get_current_time 错误
问题描述:
NameError: name 'get_current_time' is not defined
原因:之前定义的辅助函数被删除,但波动系数计算中还在使用。
解决方案:
# 旧代码
current_time = get_current_time()
# 新代码
current_time = datetime.now().timestamp()
2.5 KeyError: 'sma' 防御性修复
问题描述:
在 custom_roi 中访问 dataframe['sma'] 时可能不存在。
解决方案:
# 旧代码
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 时区管理统一化
全局常量:
UTC_PLUS_8 = timezone(timedelta(hours=8))
统一转换逻辑:
- FreqAI dataframe date 列:UTC → UTC+8
- 日志显示时间:UTC → UTC+8
- 冷启动保护时间比较:offset-aware → naive
4.2 import 优化
旧代码:
import datetime
current_time = datetime.datetime.now()
新代码:
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 小时重训周期。
七、后续优化方向
- 数据新鲜度告警阈值配置化:当前硬编码 10/30 分钟阈值,可改为策略参数
- 时区配置灵活化:支持通过环境变量或配置文件指定目标时区
- 入场信号日志精简:考虑完全移除历史信号统计日志,只保留实时入场诊断
- 币对白名单管理:从配置文件中彻底移除黑名单币对,避免混淆
八、相关文档
- 上一轮变更总结:
docs/changelog_head15.md - 策略改进文档:
doc/strategy_improvements.md