myTestFreqAI/doc/changelog_timezone_fix.md
2026-02-09 19:44:31 +08:00

8.0 KiB
Raw Blame History

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

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_timeoffset-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 分钟",实际上这是 最后一次入场信号的时间,不是当前数据时间。

解决方案

拆分为两个独立日志:

  1. 数据新鲜度日志(独立显示):
latest_data_date = dataframe['date'].iloc[-1]  # dataframe 最后一行
self.strategy_log(
    f"[{pair}] 数据新鲜度: ✅ 4.7min | 最新K线: 08:36:00 | 总行数: 299"
)
  1. 入场信号日志(仅在有信号时显示):
# 移除了旧的 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.jsonlive.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 小时重训周期。


七、后续优化方向

  1. 数据新鲜度告警阈值配置化:当前硬编码 10/30 分钟阈值,可改为策略参数
  2. 时区配置灵活化:支持通过环境变量或配置文件指定目标时区
  3. 入场信号日志精简:考虑完全移除历史信号统计日志,只保留实时入场诊断
  4. 币对白名单管理:从配置文件中彻底移除黑名单币对,避免混淆

八、相关文档