feat
This commit is contained in:
146
py-client/config/__init__.py
Normal file
146
py-client/config/__init__.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import socket
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
@dataclass
|
||||
class SignalConfig:
|
||||
"""单个交易信号的数据源及开仓限制配置。"""
|
||||
|
||||
# 信号接口相对于 api_host 的路径。
|
||||
url: str = ""
|
||||
|
||||
# 允许使用该信号的时间段;"*" 表示不限制时间。
|
||||
timezone: str = "*"
|
||||
|
||||
# 当前价格高于信号昨收价时是否仍允许开仓。
|
||||
gt_last_price_is_open: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class GlobalConfig:
|
||||
"""所有主机共享的系统配置。"""
|
||||
|
||||
qmt_base_url: str = ""
|
||||
qmt_token: str = ""
|
||||
api_host: str = ""
|
||||
qmt_data_dir: str = ""
|
||||
|
||||
# Windows 主机名到对应账户配置文件的映射。
|
||||
hosts: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
# 信号名称到信号配置的映射。
|
||||
signals: dict[str, SignalConfig] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountConfig:
|
||||
"""当前主机所使用的账户及交易策略参数。"""
|
||||
|
||||
account_id: str = ""
|
||||
host_key: str = ""
|
||||
buy_value: float = 0
|
||||
min_cash_ratio: float = 0
|
||||
loss_trigger_pct: float = 0
|
||||
grid_step_pct: float = 1
|
||||
min_profit_pct: float = 0
|
||||
enable_loss_add_position: bool = False
|
||||
signal_allow: list[str] = field(default_factory=list)
|
||||
excluded_codes: list[str] = field(default_factory=list)
|
||||
|
||||
# 当前账户启用的策略名称,例如 trend。
|
||||
strategy: str = ""
|
||||
|
||||
|
||||
# load() 成功后保存已加载的配置,供策略模块直接读取。
|
||||
global_config: GlobalConfig | None = None
|
||||
account_config: AccountConfig | None = None
|
||||
|
||||
# QMT 和外部 HTTP 接口的默认请求超时时间,单位为秒。
|
||||
HTTP_TIMEOUT = 5.0
|
||||
|
||||
|
||||
def load(
|
||||
etc_dir: str | Path | None = None,
|
||||
hostname: str | None = None,
|
||||
) -> tuple[GlobalConfig, AccountConfig]:
|
||||
"""加载公共配置以及当前主机对应的账户配置。
|
||||
|
||||
Args:
|
||||
etc_dir: 配置文件目录,其中必须包含 ``_global.yaml``;为空时
|
||||
默认使用 py-client 下的 ``etc`` 目录。
|
||||
hostname: 指定要加载的主机名;为空时使用当前计算机名。
|
||||
|
||||
Returns:
|
||||
由全局配置和账户配置组成的二元组。
|
||||
|
||||
Raises:
|
||||
ValueError: 配置缺失、格式错误或策略参数不合法。
|
||||
"""
|
||||
global global_config, account_config
|
||||
|
||||
root = Path(etc_dir) if etc_dir is not None else Path(__file__).parent.parent / "etc"
|
||||
raw = _yaml(root / "_global.yaml")
|
||||
|
||||
# 将原始字典转换为带类型的信号配置,方便业务代码使用属性访问。
|
||||
signals = {
|
||||
key: SignalConfig(**(value or {}))
|
||||
for key, value in (raw.get("signals") or {}).items()
|
||||
}
|
||||
values = {
|
||||
key: raw.get(key, "")
|
||||
for key in ("qmt_base_url", "qmt_token", "api_host", "qmt_data_dir")
|
||||
}
|
||||
|
||||
current = hostname or socket.gethostname()
|
||||
hosts = raw.get("hosts") or {}
|
||||
account_file = next(
|
||||
(
|
||||
value
|
||||
for key, value in hosts.items()
|
||||
if key.strip().lower() == current.strip().lower()
|
||||
),
|
||||
"",
|
||||
)
|
||||
|
||||
# QMT 地址、外部 API 地址和数据目录是启动策略的必要参数。
|
||||
if (
|
||||
not values["qmt_base_url"]
|
||||
or not values["api_host"]
|
||||
or values["qmt_data_dir"] == "."
|
||||
):
|
||||
raise ValueError("Global 配置缺少必要参数")
|
||||
|
||||
if not account_file:
|
||||
raise ValueError(f'_global.yaml 未配置计算机 "{current}"')
|
||||
if not Path(account_file).suffix:
|
||||
account_file += ".yaml"
|
||||
|
||||
global_config = GlobalConfig(**values, hosts=hosts, signals=signals)
|
||||
|
||||
# 策略状态文件写入该目录,启动时提前确保目录存在。
|
||||
Path(global_config.qmt_data_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
account_config = AccountConfig(**_yaml(root / account_file))
|
||||
if account_config.buy_value <= 0 or account_config.grid_step_pct <= 0:
|
||||
raise ValueError("buy_value、grid_step_pct 必须大于 0")
|
||||
if not account_config.strategy.strip():
|
||||
raise ValueError("strategy 不能为空")
|
||||
|
||||
# host_key 统一为小写,避免不同模块比较时受大小写影响。
|
||||
account_config.host_key = account_config.host_key.lower()
|
||||
account_config.strategy = account_config.strategy.lower()
|
||||
return global_config, account_config
|
||||
|
||||
|
||||
def _yaml(path: Path) -> dict:
|
||||
"""读取 YAML 文件,并将空文件转换为空字典。"""
|
||||
try:
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
return yaml.safe_load(handle) or {}
|
||||
except (OSError, yaml.YAMLError) as exc:
|
||||
raise ValueError(f"读取或解析配置 {path} 失败: {exc}") from exc
|
||||
Reference in New Issue
Block a user