fix: 统一时区处理,准确率统计使用CST时间分组

- 增强 convert_to_cst() 函数,新增 source_tz 参数支持 UTC/ET/CST 输入
- 修改 get_monthly_stats() 使用 CONVERT_TZ 按 CST 月份统计
- 修改 get_daily_stats() 使用 CONVERT_TZ 按 CST 日期分组
- 修改 get_hourly_details() 按 CST 日期筛选和小时排序
- 交易记录使用 source_tz='CST' 避免重复转换
- 前端表头添加'(北京)'时区标注
This commit is contained in:
crypto_predictor committed 2026-02-07 18:14:37 +08:00
1 parent 6182630203
commit 8cdfb2a8e2
2 files changed
+43 -25

No files matched your search

+41 -23
View File
@@ -17,12 +17,13 @@ TZ_ET = ZoneInfo('America/New_York') # 美东时间 (Polymarket)
TZ_CST = ZoneInfo('Asia/Shanghai') # 中国标准时间 (前端显示) TZ_CST = ZoneInfo('Asia/Shanghai') # 中国标准时间 (前端显示)
def convert_to_cst(timestamp_val) -> str: def convert_to_cst(timestamp_val, source_tz: str = 'UTC') -> str:
""" """
将时间戳转换为 CST 时间字符串 将时间戳转换为 CST 时间字符串
Args: Args:
timestamp_val: datetime 对象或 ISO 格式时间戳字符串 (假设是 UTC) timestamp_val: datetime 对象或 ISO 格式时间戳字符串
source_tz: 输入时区 - 'UTC'(默认,币安数据), 'ET'(Polymarket), 'CST'(已是北京时间)
Returns: Returns:
CST 时间字符串 (YYYY-MM-DD HH:MM:SS) CST 时间字符串 (YYYY-MM-DD HH:MM:SS)
@@ -37,10 +38,20 @@ def convert_to_cst(timestamp_val) -> str:
# 字符串格式 # 字符串格式
dt = datetime.fromisoformat(str(timestamp_val).replace('Z', '+00:00')) dt = datetime.fromisoformat(str(timestamp_val).replace('Z', '+00:00'))
# 如果没有时区信息,假设是 UTC # 根据来源时区设置时区信息
if dt.tzinfo is None: if dt.tzinfo is None:
if source_tz == 'ET':
dt = dt.replace(tzinfo=TZ_ET)
elif source_tz == 'CST':
# 已经是 CST,直接格式化返回
return dt.strftime('%Y-%m-%d %H:%M:%S')
else: # 默认 UTC
dt = dt.replace(tzinfo=TZ_UTC) dt = dt.replace(tzinfo=TZ_UTC)
# 如果来源已经是 CST,直接格式化
if source_tz == 'CST':
return dt.strftime('%Y-%m-%d %H:%M:%S')
# 转换为 CST # 转换为 CST
dt_cst = dt.astimezone(TZ_CST) dt_cst = dt.astimezone(TZ_CST)
return dt_cst.strftime('%Y-%m-%d %H:%M:%S') return dt_cst.strftime('%Y-%m-%d %H:%M:%S')
@@ -122,7 +133,8 @@ class TradeModel:
row = cursor.fetchone() row = cursor.fetchone()
result = _dict_from_row(row) result = _dict_from_row(row)
if result and result.get('timestamp'): if result and result.get('timestamp'):
result['timestamp_cst'] = convert_to_cst(result['timestamp']) # 交易记录时间已经是 CST,直接格式化
result['timestamp_cst'] = convert_to_cst(result['timestamp'], source_tz='CST')
return result return result
@staticmethod @staticmethod
@@ -173,7 +185,8 @@ class TradeModel:
for row in rows: for row in rows:
item = _dict_from_row(row) item = _dict_from_row(row)
if item and item.get('timestamp'): if item and item.get('timestamp'):
item['timestamp_cst'] = convert_to_cst(item['timestamp']) # 交易记录时间已经是 CST,直接格式化
item['timestamp_cst'] = convert_to_cst(item['timestamp'], source_tz='CST')
data_list.append(item) data_list.append(item)
return { return {
@@ -292,7 +305,7 @@ class PredictionModel:
SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct, SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct,
SUM(CASE WHEN is_correct = 0 THEN 1 ELSE 0 END) as incorrect SUM(CASE WHEN is_correct = 0 THEN 1 ELSE 0 END) as incorrect
FROM model_predictions FROM model_predictions
WHERE DATE_FORMAT(date, '%%Y-%%m') = %s WHERE DATE_FORMAT(CONVERT_TZ(timestamp, '+00:00', '+08:00'), '%%Y-%%m') = %s
AND actual_result IS NOT NULL AND actual_result IS NOT NULL
GROUP BY model_name GROUP BY model_name
""" """
@@ -333,16 +346,16 @@ class PredictionModel:
sql = """ sql = """
SELECT SELECT
date, DATE(CONVERT_TZ(timestamp, '+00:00', '+08:00')) as date_cst,
model_name, model_name,
COUNT(*) as total, COUNT(*) as total,
SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct, SUM(CASE WHEN is_correct = 1 THEN 1 ELSE 0 END) as correct,
SUM(CASE WHEN is_correct = 0 THEN 1 ELSE 0 END) as incorrect SUM(CASE WHEN is_correct = 0 THEN 1 ELSE 0 END) as incorrect
FROM model_predictions FROM model_predictions
WHERE DATE_FORMAT(date, '%%Y-%%m') = %s WHERE DATE_FORMAT(CONVERT_TZ(timestamp, '+00:00', '+08:00'), '%%Y-%%m') = %s
AND actual_result IS NOT NULL AND actual_result IS NOT NULL
GROUP BY date, model_name GROUP BY date_cst, model_name
ORDER BY date DESC ORDER BY date_cst DESC
""" """
cursor.execute(sql, (month,)) cursor.execute(sql, (month,))
@@ -352,7 +365,7 @@ class PredictionModel:
daily_data = {} daily_data = {}
for row in rows: for row in rows:
row_dict = _dict_from_row(row) row_dict = _dict_from_row(row)
date = row_dict['date'] date = str(row_dict['date_cst']) # CST 日期
if date not in daily_data: if date not in daily_data:
daily_data[date] = {"date": date} daily_data[date] = {"date": date}
@@ -391,28 +404,33 @@ class PredictionModel:
获取指定日期的每小时详情 获取指定日期的每小时详情
Args: Args:
date: 日期 (YYYY-MM-DD) date: 日期 (YYYY-MM-DD, CST 北京时间)
""" """
with get_db_connection() as conn: with get_db_connection() as conn:
cursor = conn.cursor() cursor = conn.cursor()
# 按 CST 日期筛选,并获取 CST 小时
cursor.execute(""" cursor.execute("""
SELECT * FROM model_predictions SELECT
WHERE date = %s *,
ORDER BY hour, model_name HOUR(CONVERT_TZ(timestamp, '+00:00', '+08:00')) as hour_cst,
CONVERT_TZ(timestamp, '+00:00', '+08:00') as timestamp_cst_dt
FROM model_predictions
WHERE DATE(CONVERT_TZ(timestamp, '+00:00', '+08:00')) = %s
ORDER BY hour_cst, model_name
""", (date,)) """, (date,))
rows = cursor.fetchall() rows = cursor.fetchall()
# 按小时重组数据 # 按 CST 小时重组数据
hourly_data = {} hourly_data = {}
for row in rows: for row in rows:
row_dict = _dict_from_row(row) row_dict = _dict_from_row(row)
hour = row_dict['hour'] hour_cst = row_dict['hour_cst'] # 使用 CST 小时
if hour not in hourly_data: if hour_cst not in hourly_data:
# 将 timestamp 转换为 CST 时间 # 将 timestamp 转换为 CST 时间字符串
timestamp_cst = convert_to_cst(row_dict.get('timestamp', '')) timestamp_cst = convert_to_cst(row_dict.get('timestamp', ''))
hourly_data[hour] = { hourly_data[hour_cst] = {
"hour": hour, "hour": hour_cst, # 返回 CST 小时
"timestamp_et": row_dict['timestamp_et'], "timestamp_et": row_dict['timestamp_et'],
"timestamp_cst": timestamp_cst, "timestamp_cst": timestamp_cst,
"open_price": row_dict['open_price'], "open_price": row_dict['open_price'],
@@ -422,13 +440,13 @@ class PredictionModel:
} }
model = row_dict['model_name'] model = row_dict['model_name']
hourly_data[hour]["models"][model] = { hourly_data[hour_cst]["models"][model] = {
"prediction": row_dict['prediction'], "prediction": row_dict['prediction'],
"probability": row_dict['probability'], "probability": row_dict['probability'],
"is_correct": row_dict['is_correct'] "is_correct": row_dict['is_correct']
} }
# 按小时排序返回 # 按 CST 小时排序返回
return [hourly_data[h] for h in sorted(hourly_data.keys())] return [hourly_data[h] for h in sorted(hourly_data.keys())]
@staticmethod @staticmethod
+2 -2
View File
@@ -259,7 +259,7 @@
<table class="table table-hover mb-0"> <table class="table table-hover mb-0">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>日期</th> <th>日期 (北京)</th>
<th>DLinear 预测</th> <th>DLinear 预测</th>
<th>DLinear 准确率</th> <th>DLinear 准确率</th>
<th>TTM-R2 预测</th> <th>TTM-R2 预测</th>
@@ -341,7 +341,7 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title"> <h5 class="modal-title">
<i class="bi bi-clock"></i> {{ hourlyModal.date }} 每小时预测详情 <i class="bi bi-clock"></i> {{ hourlyModal.date }} 每小时预测详情 (北京时间)
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>