Files
easy_tdx_max/src/easy_tdx/config.py
T
GitHub 155328df8b release: v1.16.2 — 三轮审计质量加固(B6.9→A7.9)
经三轮代码审计后的综合质量加固版本,覆盖协议核心层、数据正确性、
错误处理、测试真实度与可维护性。761 单测全绿(+58),ruff/mypy 全过。

主要修复:
- 离线 .day 写入原子化(fsync + _repair_tail + 读取校验,CQS 守住)
- 回测止损前视偏差(延迟下一根开盘 + 跳空保护)
- VWAP 权重索引 / bar_time fail-fast / 绩效除零保护
- 闭包绑定 / 路径穿越 / naive datetime 跨时区 / ruff UP038

重构:
- 抽 AsyncHeartbeatMixin 收敛 4 处心跳副本(12→1)
- 统一 _RETRY_DELAYS 退避序列 / scanner 失败可观测性

新增 5 个测试文件 + 公共 API 类型契约,CI 加 Windows 矩阵 +
trusted publishing 签名 + 锁文件。

详见 CHANGELOG.md
2026-07-02 03:37:37 +08:00

285 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""集中管理服务器地址、端口、超时等配置。
优先级:环境变量 > ~/.easy_tdx/config.json > 源码内嵌默认值。
配置文件示例::
{
"best_host": "180.153.18.170",
"best_host_updated_at": "2026-05-22T10:30:00",
"known_hosts": ["111.229.247.189", ...],
"calc_hosts": ["120.76.152.87"],
"mac_hosts": ["121.36.248.138", ...],
"port": 7709,
"timeout": 15.0
}
环境变量覆盖::
EASY_TDX_HOST -- 单台主机地址
EASY_TDX_PORT -- 端口
EASY_TDX_TIMEOUT -- 超时秒数
EASY_TDX_KNOWN_HOSTS -- 逗号分隔的候选主机列表
EASY_TDX_CONFIG_DIR -- 配置文件目录(默认 ~/.easy_tdx
"""
import json
import os
from datetime import datetime
from pathlib import Path
from typing import Any, cast
from zoneinfo import ZoneInfo
_CONFIG_DIR = Path(os.environ.get("EASY_TDX_CONFIG_DIR", str(Path.home() / ".easy_tdx")))
_CONFIG_FILE = _CONFIG_DIR / "config.json"
# 业务时间统一用上海时区(与 client.py 一致),避免 naive datetime 跨时区歧义(审计 #18)。
_SHANGHAI_TZ = ZoneInfo("Asia/Shanghai")
# ---------------------------------------------------------------------------
# 源码内嵌默认值(config.json 不存在或字段缺失时的兜底)
# ---------------------------------------------------------------------------
_FALLBACK_HOSTS: list[str] = [
"111.229.247.189",
"150.158.160.2",
"180.153.18.170",
"124.71.187.122",
"180.153.18.171",
"180.153.18.172",
"119.147.212.81",
"115.238.56.198",
"115.238.90.165",
"218.75.126.9",
"47.107.75.159",
"59.175.238.38",
"110.41.147.114",
"110.41.2.72",
"101.33.225.16",
"175.178.112.197",
"175.178.128.227",
"43.139.95.83",
"124.223.163.242",
"122.51.120.217",
"123.60.164.122",
"124.70.199.56",
"62.234.50.143",
"81.70.151.186",
"82.156.214.79",
"159.75.29.111",
"43.139.18.171",
"81.71.32.47",
"122.51.232.182",
"118.25.98.114",
"121.36.225.169",
"123.60.70.228",
"123.60.73.44",
"124.70.133.119",
"124.71.187.72",
"119.97.185.59",
"129.204.230.128",
"101.42.240.54",
"124.71.9.153",
"123.60.84.66",
"111.230.186.52",
"101.43.159.194",
"120.53.8.251",
"152.136.191.169",
"116.205.163.254",
"116.205.171.132",
"116.205.183.150",
"49.232.15.141",
"82.156.174.84",
"101.42.164.241",
"101.35.121.35",
"111.231.113.208",
]
_FALLBACK_CALC_HOSTS: list[str] = [
"120.76.152.87",
]
_FALLBACK_MAC_HOSTS: list[str] = [
"121.36.248.138",
"123.60.47.136",
"121.37.207.165",
]
_FALLBACK_EX_HOSTS: list[str] = [
"112.74.214.43",
"120.25.218.6",
"43.139.173.246",
"159.75.90.107",
"106.52.170.195",
"139.9.191.175",
"175.24.47.69",
"150.158.9.199",
"150.158.20.127",
"49.235.119.116",
"49.234.13.160",
"116.205.143.214",
"124.71.223.19",
"113.45.175.47",
"123.60.173.210",
"118.89.69.202",
]
_FALLBACK_MAC_EX_HOSTS: list[str] = [
"116.205.135.205",
"121.37.232.167",
]
_FALLBACK_PORT = 7709
_FALLBACK_TIMEOUT = 15.0
# ---------------------------------------------------------------------------
# 内部读写
# ---------------------------------------------------------------------------
def _load() -> dict[str, Any]:
try:
if _CONFIG_FILE.exists():
return cast(dict[str, Any], json.loads(_CONFIG_FILE.read_text("utf-8")))
except Exception:
pass
return {}
def _save(data: dict[str, Any]) -> None:
_CONFIG_DIR.mkdir(parents=True, exist_ok=True)
tmp = _CONFIG_FILE.with_suffix(".tmp")
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False), "utf-8")
tmp.replace(_CONFIG_FILE)
# ---------------------------------------------------------------------------
# 公开 getter
# ---------------------------------------------------------------------------
def get_best_host() -> str:
"""返回当前最佳主机地址。优先级:环境变量 > config.json > 默认列表首个。"""
env = os.environ.get("EASY_TDX_HOST")
if env:
return env
cfg = _load()
return cast(str, cfg.get("best_host", _FALLBACK_HOSTS[0]))
def get_known_hosts() -> list[str]:
"""返回候选行情主机列表。"""
env = os.environ.get("EASY_TDX_KNOWN_HOSTS")
if env:
return [h.strip() for h in env.split(",") if h.strip()]
cfg = _load()
return cast(list[str], cfg.get("known_hosts", list(_FALLBACK_HOSTS)))
def get_calc_hosts() -> list[str]:
"""返回计算服务器列表。"""
cfg = _load()
return cast(list[str], cfg.get("calc_hosts", list(_FALLBACK_CALC_HOSTS)))
def get_mac_hosts() -> list[str]:
"""返回 MAC 行情服务器列表。"""
cfg = _load()
return cast(list[str], cfg.get("mac_hosts", list(_FALLBACK_MAC_HOSTS)))
def get_ex_hosts() -> list[str]:
"""返回扩展行情服务器列表。"""
cfg = _load()
return cast(list[str], cfg.get("ex_hosts", list(_FALLBACK_EX_HOSTS)))
def get_best_ex_host() -> str:
"""返回当前最佳扩展行情主机。"""
env = os.environ.get("EASY_TDX_EX_HOST")
if env:
return env
cfg = _load()
return cast(str, cfg.get("best_ex_host", _FALLBACK_EX_HOSTS[0]))
def get_mac_ex_hosts() -> list[str]:
"""返回 MAC 协议扩展行情服务器列表。"""
cfg = _load()
return cast(list[str], cfg.get("mac_ex_hosts", list(_FALLBACK_MAC_EX_HOSTS)))
def get_best_mac_ex_host() -> str:
"""返回当前最佳 MAC 协议扩展行情主机。"""
env = os.environ.get("EASY_TDX_MAC_EX_HOST")
if env:
return env
cfg = _load()
return cast(str, cfg.get("best_mac_ex_host", _FALLBACK_MAC_EX_HOSTS[0]))
def get_port() -> int:
"""返回默认端口。"""
env = os.environ.get("EASY_TDX_PORT")
if env:
return int(env)
cfg = _load()
return cast(int, cfg.get("port", _FALLBACK_PORT))
def get_timeout() -> float:
"""返回默认超时秒数。"""
env = os.environ.get("EASY_TDX_TIMEOUT")
if env:
return float(env)
cfg = _load()
return cast(float, cfg.get("timeout", _FALLBACK_TIMEOUT))
# ---------------------------------------------------------------------------
# 持久化
# ---------------------------------------------------------------------------
def save_best_host(host: str) -> None:
"""保存最佳主机到配置文件;首次写入时同时补全默认配置。"""
cfg = _load()
cfg["best_host"] = host
cfg["best_host_updated_at"] = datetime.now(_SHANGHAI_TZ).isoformat()
if "known_hosts" not in cfg:
cfg["known_hosts"] = list(_FALLBACK_HOSTS)
if "calc_hosts" not in cfg:
cfg["calc_hosts"] = list(_FALLBACK_CALC_HOSTS)
if "mac_hosts" not in cfg:
cfg["mac_hosts"] = list(_FALLBACK_MAC_HOSTS)
if "port" not in cfg:
cfg["port"] = _FALLBACK_PORT
if "ex_hosts" not in cfg:
cfg["ex_hosts"] = list(_FALLBACK_EX_HOSTS)
if "mac_ex_hosts" not in cfg:
cfg["mac_ex_hosts"] = list(_FALLBACK_MAC_EX_HOSTS)
_save(cfg)
def save_best_ex_host(host: str) -> None:
"""保存最佳扩展行情主机到配置文件。"""
cfg = _load()
cfg["best_ex_host"] = host
cfg["best_ex_host_updated_at"] = datetime.now().isoformat()
if "ex_hosts" not in cfg:
cfg["ex_hosts"] = list(_FALLBACK_EX_HOSTS)
if "mac_ex_hosts" not in cfg:
cfg["mac_ex_hosts"] = list(_FALLBACK_MAC_EX_HOSTS)
_save(cfg)
def save_best_mac_ex_host(host: str) -> None:
"""保存最佳 MAC 协议扩展行情主机到配置文件。"""
cfg = _load()
cfg["best_mac_ex_host"] = host
cfg["best_mac_ex_host_updated_at"] = datetime.now().isoformat()
if "mac_ex_hosts" not in cfg:
cfg["mac_ex_hosts"] = list(_FALLBACK_MAC_EX_HOSTS)
_save(cfg)