Files
Chan/web/services/runtime/timeframes.py
T
jackyu66gitandCursor df27b4dde8 refactor: ECR-002 拆分 runtime 包并加深 analyze 契约(已审)
将 web/services/runtime.py 拆为 runtime/ 子模块并保持门面兼容;补齐 ESS 文档、门面/契约/TF_DF 测试与 CODE_REVIEW Approve。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 18:15:23 +08:00

122 lines
3.3 KiB
Python

from __future__ import annotations
from collections import OrderedDict
from .state import DEFAULT_TIMEFRAME_LABELS
def _zone_cache_ttl(tf_name: str) -> int:
"""根据时间周期返回缓存过期时间(秒)"""
minutes = timeframe_to_minutes(tf_name) or 5
if minutes <= 5:
return 120 # 5m及以下: 2分钟
elif minutes <= 15:
return 300 # 15m: 5分钟
elif minutes <= 60:
return 600 # 1h: 10分钟
else:
return 1800 # 4h+: 30分钟
def timeframe_to_minutes(tf: str):
"""将时间周期转换为分钟数,用于排序。"""
if not tf:
return None
unit = tf[-1]
try:
value = int(tf[:-1])
except (ValueError, TypeError):
return None
multiplier = {
'm': 1,
'h': 60,
'd': 1440,
'w': 10080,
'M': 43200, # 30天近似
}.get(unit)
if multiplier is None:
return None
return value * multiplier
def format_timeframe_label(tf: str) -> str:
"""将时间周期转换为可读标签。"""
if not tf:
return tf
unit = tf[-1]
try:
value = int(tf[:-1])
except (ValueError, TypeError):
return tf
if unit == 'm':
return f"{value}分钟"
if unit == 'h':
return f"{value}小时"
if unit == 'd':
return "日线" if value == 1 else f"{value}日线"
if unit == 'w':
return "周线" if value == 1 else f"{value}周线"
if unit == 'M':
return "月线" if value == 1 else f"{value}月线"
return tf
def build_timeframe_labels(timeframes):
ordered = sorted(
timeframes,
key=lambda tf: timeframe_to_minutes(tf) if timeframe_to_minutes(tf) is not None else float('inf'),
)
labels = OrderedDict()
for tf in ordered:
labels[tf] = format_timeframe_label(tf)
return labels
def compute_timeframe_defaults(labels_ordered):
"""
根据已排序的「周期 → 中文标签」映射,计算主 / 次 / 次次周期默认值。
labels_ordered: OrderedDict 或按插入顺序排列的 dict。
"""
if not labels_ordered:
labels_ordered = DEFAULT_TIMEFRAME_LABELS.copy()
timeframe_keys = list(labels_ordered.keys())
preferred_main = next((tf for tf in ['5m', '15m', '1h'] if tf in labels_ordered), None)
default_main = preferred_main or (timeframe_keys[0] if timeframe_keys else '1m')
if default_main not in labels_ordered and timeframe_keys:
default_main = timeframe_keys[0]
if timeframe_keys:
try:
idx = timeframe_keys.index(default_main)
default_element = timeframe_keys[idx - 1] if idx > 0 else timeframe_keys[0]
except ValueError:
default_element = timeframe_keys[0]
else:
default_element = default_main
if timeframe_keys:
try:
idx_el = timeframe_keys.index(default_element)
default_sub_sub = timeframe_keys[idx_el - 1] if idx_el > 0 else timeframe_keys[0]
except ValueError:
default_sub_sub = timeframe_keys[0]
else:
default_sub_sub = default_element
return default_main, default_element, default_sub_sub, timeframe_keys
def is_smaller_timeframe(tf1, tf2):
"""判断时间周期tf1是否小于tf2"""
tf1_value = timeframe_to_minutes(tf1)
tf2_value = timeframe_to_minutes(tf2)
if tf1_value is None or tf2_value is None:
return False
return tf1_value < tf2_value
def is_smaller_or_equal_timeframe(tf1, tf2):
"""判断时间周期tf1是否小于等于tf2"""
tf1_value = timeframe_to_minutes(tf1)
tf2_value = timeframe_to_minutes(tf2)
if tf1_value is None or tf2_value is None:
return False
return tf1_value <= tf2_value