tradingSystem/config/settings.py

293 lines
23 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.

# -*- coding: utf-8 -*-
"""
tradingSystem (PMS) 配置
========================
纪律 (沿袭 bionic_trader 踩坑经验):
1. 所有配置字段必须在 Settings 类中显式声明 —— pydantic 的 extra="ignore" 会静默忽略
未声明的环境变量, 这是历史头号坑。新增配置必须同时加字段。
2. 基础设施键名与 bionic_trader 对齐 (PROXY_DB_URL / SIGNAL_REDIS_* / SOURCE_DB_EXT_DSN
/ DB_MYSQL_URL), 同一份 .env 可服务两个系统。
3. 业务参数在此处只是「初值」: 页面修改后持久化到 pms_runtime_param 表,
运行时经 ParamStore 读取 (表值优先于本文件)。禁止在业务代码里直接
读 settings 取业务参数 —— 统一走 ParamStore, 保证页面调参即时生效。
"""
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
# =========================================================
# 一、基础设施 (键名对齐 bionic_trader, 同一 .env 可共用)
# =========================================================
PROXY_DB_URL: str = "mysql+pymysql://user:pass@192.168.16.153:3307/my_quant_db"
# 153 代理: pms_* 全部表 + trading_position / trading_order / trading_buy_plan /
# strategy_daily_results (决策系统结论) —— 严格单表访问, 代理禁多表联查
SOURCE_DB_EXT_DSN: str = "mysql+pymysql://user:pass@192.168.16.150:3306/factordb_mysql"
# 因子库直连。**PMS 当前不使用**: 因子分表 gp_stock_factor_pro_YYYYMM 经
# ShardingSphere 动态分配物理落点, 直连单台会在分片变动的月份漏表, 必须走
# PROXY_DB_URL 逐表单查 (沿用 bionic_trader 的踩坑结论)。保留本键仅为与 bionic
# 的 .env 对齐, 连不通不影响任何功能。
DB_MYSQL_URL: str = "mysql+pymysql://user:pass@192.168.18.199:3306/db_gp_cj"
# 大盘指数 zs_day_data (页面区制提示用, 非约束)
SIGNAL_REDIS_HOST: str = "192.168.18.208"
SIGNAL_REDIS_PORT: int = 6379
SIGNAL_REDIS_PASSWORD: str = ""
SIGNAL_REDIS_DB_INTRADAY: int = 2 # 决策系统盘中信号流 (PMS 订阅消化)
SIGNAL_REDIS_DB_ACTIONS: int = 3 # 决策系统风控 SELL 流 (PMS 订阅消化)
SIGNAL_REDIS_DB_QUOTES: int = 13 # 实时分钟行情 (择时/扫描)
SIGNAL_REDIS_SOCKET_TIMEOUT: int = 5
PMS_REDIS_URL: str = "redis://:pass@192.168.16.150:6379/8"
# 上游信号(mtf 实况分)只读源 —— 214 db0。密钥只从 .env 注入(见下「密钥」纪律), 代码默认留空。
# .env 里加一行: UP_MR_REDIS_URL=redis://:<密码>@192.168.16.214:36379/0
# (208 上的决策系统/择时层/资金异动几条流复用现有 SIGNAL_REDIS_*, 无需另配。)
UP_MR_REDIS_URL: str = ""
# PMS 自己的 Celery 总线, 独立 db=8 (决策系统用 db7, 物理同机逻辑隔离)
PMS_WEB_HOST: str = "0.0.0.0" # 管理页面 (FastAPI + 单页)
PMS_WEB_PORT: int = 38100
# =========================================================
# 二、业务参数「初值」(运行时以 pms_runtime_param 表为准, 页面可调)
# =========================================================
# --- 资金与总体 (对应参数命令) ---
PMS_TOTAL_SCALE: float = 2_000_000 # 总操作规模 (元)
PMS_PORTFOLIO_CAP: float = 0.60 # 总仓上限 (占规模)
PMS_STOCK_CAP: float = 0.08 # 单股上限
PMS_STOCK_TARGET_DEFAULT: float = 0.06 # 默认单股目标仓位
PMS_MAX_NAMES: int = 15 # 最大持仓数
PMS_CASH_RESERVE: float = 0.0 # 预留现金比例 (永不动用)
PMS_AUTONOMY: str = "propose_only" # full / propose_only / off
# --- 批次与安全垫 ---
PMS_BATCH_SPLIT: str = "0.5,0.25,0.25" # 底仓/回踩补足/盈利加仓
PMS_CUSHION_SOLID: float = 0.03 # 安全垫厚垫线 (解锁加仓)
PMS_TRIM_PEAK: float = 0.06 # 保垫减仓: 垫子峰值门槛
PMS_TRIM_GIVEBACK: float = 0.5 # 保垫减仓: 回吐比例门槛
PMS_DCA_TRIGGERS: str = "-0.08,-0.15" # 补仓评估档
PMS_DCA_DEEP_CONFIRM: float = -0.15 # 此档及更深永远需用户确认
PMS_DCA_MAX_RATIO: float = 0.5 # 补仓上限 (占底仓)
PMS_NO_CHASE_MA5: float = 0.06 # 距 MA5 超此幅度不追买
PMS_BUILD_WINDOW_TDAYS: int = 10 # 建仓期窗口 (交易日)
PMS_FILL_MAX_LOSS: float = -0.03 # 浮亏深于此不走回踩补足 (转补仓域)
PMS_WEAK_NEG_DAYS: int = 5 # 降仓「清弱票」判定: 安全垫连续为负 N 日 (设计 §3.2)
PMS_PROPOSAL_TTL_HOURS: int = 24 # 自主提议待确认有效期 (超时置 EXPIRED)
# --- 自主新建仓 (动作引擎第五类动作 OPEN, 2026-08-06) ---
# 让自主提议除了管已有持仓, 也能从上游候选池里挑新票建底仓, 走同一条
# 规则闸 → 研判闸 → 档位分流。要解决的是「每天靠人下一条升仓命令」这件事。
# **刻意不设每日开仓上限**: 名额与金额在 action_engine.scan_open 里边走边扣,
# 候选数天生不超过剩余名额; 再往后还有择时的买入区间做天然过滤 (现价不在区间内
# 一律等待、不追)。每天实际建成几只由这几层自己收敛, 不由一个人为的数字决定。
PMS_OPEN_AUTONOMY: str = "full" # 新建仓档位兼总开关: full / propose_only / off
# 与 PMS_AUTONOMY 分开的理由: 「新建仓要不要人点头」和「加仓要不要人点头」是两个
# 不同的决定, 不该被一个开关捆住。off = 根本不扫描新建仓, 行为回到 2026-08-06 之前。
PMS_OPEN_REQUIRE_WS_CASH: bool = True # 拿不到 ws 资金快照时不自动新建仓 (写 skipped 留痕)
# 规则闸那条「拿不到真实资金只告警不拦」是为**已经排好的命令**设计的 —— 通道故障
# 不该升级成业务停摆。但无人值守地从零建仓是可以等的, 所以这里比规则闸严一档:
# 07-30 教训 (scale 200 万 / 账户实际 98 万, 方案一路放行到下游才被拒) 不能重演。
PMS_OPEN_SIGNAL_PRIORITY: bool = True # 今天被决策系统判过盘中转多的候选票排最前
# 决策系统判出 REVERSAL_BUY 时会往 db2 的 intraday_signals 流广播, PMS 一直订阅得到,
# 但 2026-08-06 之前 signal_rules 把买入类信号一律归进"只留痕", 而留痕又只给持仓票写 ——
# 于是「决策系统今天看多了某只没持仓的票」这件事, PMS 收到了却一个字都不留。
# 现在补上了留痕 (action=SIGNAL_BUY, verdict=NOTE), 动作引擎读它给候选排序。
# **只影响先后, 不影响资格**: 不在候选池里的票不会因为有信号就被建仓。
PMS_OPEN_REF_DRIFT_MAX: float = 0.03 # 参考位盘中被改写超此幅度 → 该票当日暂停新建仓
# 择时读的 strategy_daily_results 会被盘中补扫就地改写 (实证: 002335 支撑 31.27→29.00,
# 差 7.3%)。已有持仓有摊薄成本与安全垫做锚, 漂一点不翻转判断; 而新建仓的买入区间
# 完全由支撑压力推出来, 区间一平移, 上午「不追」的票下午可能变成「出手」, 而无人在看。
# 处置: 当日首答把 observed 的支撑/压力/区间锁进指令 progress.ref_lock, 之后每答比对。
# --- 上游选股计划接口 (候选池的事实源; 口径与待确认项见 UPSTREAM_PLAN_API.md) ---
# 上游只回答「买什么、排第几」, 不给价格金额 —— 买多少/什么价是 PMS 自己算。
# 接口拿不到一律显式失败 (候选池为空 + ERROR), 绝不静默回退旧表。
PMS_CANDIDATE_SOURCE: str = "plan_api" # plan_api(默认) / buy_plan(旧表) / both(并集)
# 计划服务与 PMS 同机: 走 host.docker.internal (compose 里配了 host-gateway)。
# 容器直连宿主机物理 IP 192.168.16.155:8300 会被宿主机 INPUT 链丢包 (超时非拒绝);
# 若日后计划服务搬到别的机器, 这里直接填那台的 IP:端口。空=停用, 候选池恒为空。
PMS_PLAN_API_BASE: str = "http://host.docker.internal:8300"
PMS_PLAN_API_PATH: str = "/plan"
PMS_PLAN_TIMEOUT: int = 10 # 单次请求超时 (秒)
PMS_PLAN_CACHE_SEC: int = 300 # 计划缓存秒数 (上游日频产出, 没必要每跳都拉)
# 下面三个是**发给上游的请求参数** (对方 api.py: top=20/obs_top=10/theme_cap=5 都是
# 可传的, 不是它的既定政策)。0 = 不传, 用上游默认。要个宽池子回来, 主题分散交给
# PMS_PLAN_THEME_CAP_LOCAL 在候选阶段做 —— 理由见 plan_feed.select_candidates。
PMS_PLAN_TOP: int = 300 # 上游 top: 主榜要多少条
PMS_PLAN_OBS_TOP: int = 100 # 上游 obs_top: 观察档要多少条
PMS_PLAN_THEME_CAP: int = 999 # 上游 theme_cap: 让它别裁 (PMS 自己裁)
PMS_PLAN_THEME_CAP_LOCAL: int = 5 # PMS 侧同主题限额, 在 TOP_N 截断**之前**生效, 0=不限
PMS_PLAN_TOP_N: int = 30 # 主榜按 score 降序取前 N 进池 (900+ 只是排序池不是清单)
PMS_PLAN_TIERS: str = "强传导" # 传导档白名单; 留空=不按档过滤
# **已定纪律 (2026-07-31, 用户): 候选宁缺毋滥, 不因当天强传导少就放宽到弱传导。**
# 上游文档说强传导规模随市场结构波动、无板块传导的日子可能一个都没有 —— 那种日子
# 候选池就该是空的。只有"热度启动信息经确认"时才考虑放宽, 且要显式改这个参数。
PMS_PLAN_INCLUDE_OBSERVE: bool = False # 观察档是否进候选池 (上游把它定位为备选, 无 upside)
PMS_PLAN_MIN_SCORE: float = 0.0 # score 下限, 0=不设
PMS_PLAN_MIN_SOURCES: int = 0 # evidence.n_sources 下限, 0=不设
PMS_PLAN_MIN_UPSIDE: float = 0.0 # 预期空间下限 (0.5=+50%), 0=不设。券商目标价
# 口径, 噪音大 (榜首能到 +212%), 只做下限过滤, **不参与排序** —— 排序始终是 score
PMS_PLAN_STALE_TDAYS: int = 1 # 计划日龄超此交易日数即判过期并拒用 (防上游停更)
PMS_PLAN_EXCLUDE_ST: bool = True # 剔除 ST/*ST/退市 (上游明确不剔除, 这道闸归 PMS)
PMS_PLAN_THEME_SYNC: bool = False # 把 theme 灌进 pms_industry_map。**默认关**:
# theme 是事件驱动的传导主题, 覆盖率约三成且词表不规范, 当行业标签会让集中度约束
# 天天漂 —— 行业源请用 PMS_SECTOR_SOURCE=gp_stock_category
PMS_PLAN_QUERY_EXTRA: str = "" # 附加查询串逃生口, 如 "foo=1"。上游哪天加了新参数
# 不用改代码即可透传; top/obs_top/theme_cap 已有显式参数, 同名键以显式参数为准
# --- 榜单变化提示 (PMS 自算; 上游 changes 字段恒为 null, 见 UPSTREAM_PLAN_API.md §9) ---
PMS_PLAN_SNAPSHOT: bool = True # 每次拉计划落一份名册快照 (变化提示的底本)。
# 关掉只是没有变化提示, 不影响候选池; 但同时也失去"手上这份是哪一版"的自证能力
PMS_PLAN_SNAPSHOT_KEEP: int = 200 # 快照保留份数 (一份 300 只约 20KB)
PMS_PLAN_DIFF_RANK_JUMP: int = 50 # 名次跳变多少名才报
PMS_PLAN_DIFF_TAIL_GUARD: float = 0.5 # 榜尾进出的噪音闸: 排名进入榜单前这个比例才算数。
# 榜是按 top 截断过的 —— 从 rank 250 掉到 310 只是被截掉, 不是上游摘了它。不设这道闸
# 每天会多几十上百条假掉榜, 提示就没人看了。1.0=只挡榜长之外, 0=一律当噪音
# --- 行业约束 (硬拦截; 数据源接口化) ---
# 行业划分数据源。gp_hybk = 199 库的行业板块表 (三级 884*, 每票只认一个主行业),
# 2026-07-31 起的正式口径; gp_stock_category 实测不可用 (无 stock_code 列), 勿用。
PMS_SECTOR_SOURCE: str = "" # "" = 停用并页面提示 / gp_hybk / custom_table
PMS_SECTOR_HYBK_LEVEL: str = "l3" # gp_hybk 取哪一级: l3=884(三级, 默认) / l2=881(二级)
PMS_SECTOR_MAX_NAMES: int = 4
PMS_SECTOR_MAX_RATIO: float = 0.40 # 占总仓
# --- 择时执行 (内置实现B; 决策系统接通后切实现A) ---
PMS_EXEC_WINDOW_TDAYS: int = 3 # 任务命令默认执行窗口
PMS_SELL_AVOID_OPEN_MIN: int = 30 # 卖出避开开盘 N 分钟
PMS_BUY_HALT_DAYUP: float = 0.05 # 当日涨幅超此停止买入
PMS_EOD_FORCE_TIME: str = "14:45" # 当日配额兜底时点
PMS_EOD_FORCE_DISCOUNT: float = 0.998 # 兜底限价 = 现价 × 此系数 (卖出)
PMS_MIN_LOT_MERGE: bool = True # 一手检查: 批次自动合并
PMS_DISPATCH_EXPIRE_MIN: int = 30 # 指令下发后未被接受的过期时间
PMS_DISPATCH_MODE: str = "shadow" # 下发通道: shadow(影子,默认) / ws(直连QMT,待实现)
PMS_EXEC_SLICES: int = 1 # 当日配额分几笔出手 (设计「分笔卖出配额」)
PMS_ORDER_TTL_MIN: int = 10 # 单个下发分片的挂单有效期(交易分钟), 到点下游自动撤
# --- 卖出分桶收口与紧急直通 (2026-08-17 清仓择时改造) ---
# 「现价不低于均价才卖」是只在强势时放行的条件, 下跌日全天不满足, 全部数量会
# 堆到 14:45 一笔打出。分桶: 桶内照旧择价, 桶收口无条件补齐该桶份额, 把跌日的
# 强制完成分散到多个时点。留空 = 不分桶, 行为与改造前一致。最后一桶恒为兜底时点。
PMS_SELL_BUCKET_TIMES: str = "11:30,14:00"
PMS_URGENT_SELL_DISCOUNT: float = 0.995 # 紧急卖出限价系数 (一键清仓/高置信风控清仓
# 直通出手用, 比兜底的 0.998 更激进; 每分钟按新现价重定, 下跌中追着走)
# --- 择时实现A (委托决策系统, 待办 #9; 契约见 BIONIC_PMS_INTERFACE.md) ---
# 决策系统按它凌晨算好的支撑/压力给出执行区间, 盘中只回答「现价在不在区间内」;
# 配额/兜底/停牌/一字板/不追高(当日涨幅) 这些检查始终留在 PMS 本地。
# 不可用自动退实现B (设计 §13), 绝不因对端故障停出手。
PMS_EXEC_IMPL: str = "B" # B=内置保守择时(默认) / A=委托决策系统
PMS_EXEC_API_BASE: str = "" # 实现A接口根地址; 留空=沿用 PMS_JUDGE_API_BASE (同一个 bionic 服务)
PMS_EXEC_PATH: str = "/api/intraday/pms_exec"
PMS_EXEC_TIMEOUT_SEC: int = 8 # 咨询超时(秒)。盘中 tick 等不起长超时, 超了本轮退B
PMS_EXEC_ADVICE_TTL_MIN: int = 10 # 应答有效期(交易分钟), 期内不重复咨询; 对端 valid_min 只缩不放
PMS_EXEC_FAIL_COOLDOWN_MIN: int = 5 # 咨询失败后的冷却(交易分钟), 冷却内直接走B不再咨询
PMS_EXEC_LIMIT_BAND: float = 0.10 # 建议价偏离现价超此幅度视为异常数据, 改用本地口径。
# 建议价通常是执行区间的边缘, 离现价百分之几属正常, 所以放到 10% (A股单日涨跌幅上限);
# 真被这条改写时 reason 里会说明
# --- QMT WebSocket 直连通道 (协议见 QMT_WS_PROTOCOL.md V1.0) ---
# 连接由 pms-ws 常驻进程独占 (app/ws/runner.py); executor 经 pms_qmt_order
# 出口表递单, 详见 app/services/dispatcher.py 头部的「进程边界」一节。
PMS_QMT_WS_URL: str = "ws://192.168.16.98:9443/pms"
PMS_QMT_WS_ENABLED: bool = False # pms-ws 进程总开关; False = 空转不连接不写心跳
PMS_QMT_ACK_BATCH: int = 20 # 累积确认: 每落库 N 条发一次 ack_seq
PMS_QMT_ACK_INTERVAL_SEC: float = 2.0 # 累积确认: 或每 N 秒发一次 (取先到)
PMS_QMT_HEARTBEAT_SEC: int = 5 # ping 间隔
PMS_QMT_IDLE_TIMEOUT_SEC: int = 15 # 超过此时长未收到对端消息即重连
PMS_QMT_OUTBOX_POLL_SEC: float = 0.5 # 出口队列轮询间隔 (择时本是分钟级, 无需更快)
PMS_QMT_HEARTBEAT_DB_SEC: int = 2 # ws 进程写存活心跳的间隔
PMS_QMT_HEARTBEAT_STALE_SEC: int = 15 # 心跳陈旧超此秒数 → 判定进程已死, dispatcher 拒发
PMS_QMT_CONNECT_TIMEOUT_SEC: int = 10 # 建连超时
PMS_QMT_QUERY_INTERVAL_SEC: int = 300 # 全量快照轮询 query_positions+query_funds (协议 §6.2)
PMS_QMT_SEND_MAX_ATTEMPTS: int = 3 # 单张委托发送重试上限, 试满置 SEND_FAILED
# 下面两项是**密钥**: 只从 .env 注入, 不入库、不进 ParamStore、不上页面 (协议 §10.1.1)。
# param_store.SECRET_KEYS 已把它们挡在可调参数与页面快照之外。
PMS_QMT_SIGN_SEED_HEX: str = "" # PMS 私钥 seed (Ed25519, 32 字节 hex)
PMS_QMT_PEER_PUBKEY_B64: str = "" # QMT 公钥 (base64), 验上行签名用
# --- 风险披露与刹车 ---
PMS_RISK_WARN_ENTRY: float = 0.01 # 单笔敞口告警线 (占规模)
PMS_RISK_WARN_PORTFOLIO: float = 0.06 # 组合敞口告警线
PMS_BRAKE_DRAWDOWN: float = 0.05 # 组合刹车: 高水位回撤
PMS_BRAKE_DAYS: int = 3 # 刹车持续交易日
PMS_STOP_ATR_MULT: float = 2.0 # 自算止损参考: 成本 - N×ATR
PMS_REF_STALE_TDAYS: int = 3 # 决策系统结论日龄超此转自算兜底
# --- 研判闸 (委托决策系统) ---
PMS_JUDGE_ENABLED: bool = True
PMS_JUDGE_ACTIONS: str = "FILL,ADD,DCA,SWITCH,OPEN"
PMS_JUDGE_TIMEOUT: int = 90 # 超时 → 降级 propose_only
PMS_JUDGE_TICK_BUDGET_SEC: int = 150 # 单轮提议扫描用于研判的时间预算 (秒)
# **这不是节流, 是让一次心跳做得完。** judge.request 是同步阻塞的, 单次上限 90 秒,
# 而 scheduler 给所有调度任务设的软超时是 240 秒 —— 三只票送研判就顶破了。
# 以前送研判的只有三只持仓票且多数轮次被去重挡掉, 所以没撞上; 新建仓上线后
# 冷启动那天会有十来条, 第一跳就会把任务打死在中途 (而且是已经落了一部分表之后)。
# 预算用尽时本轮剩下的候选整条跳过并写 skipped, 下一分钟的心跳接着做 —— 一条都不丢,
# 也没有任何按天计的上限。候选按分数降序, 先做完的一定是分数最高的那些。
PMS_JUDGE_API_BASE: str = "" # 决策系统 PMS 研判接口根地址; 空=未接通(自动降级人工确认)
PMS_JUDGE_PATH: str = "/api/intraday/pms_judge" # 研判接口路径 (bionic 侧配套改造后确定)
# --- T0 做T (命令授权制) ---
PMS_T0_RATIO_MAX: float = 0.333 # T仓硬上限 (占持仓)
PMS_T0_PULLBACK_PCT: float = 0.03 # 正T: 距当日高点回落触发
PMS_T0_RALLY_PCT: float = 0.05 # 反T: 日内涨幅触发
PMS_T0_ROUND_TARGET: float = 0.015 # 单次T目标价差
PMS_T0_CLOSE_TIME: str = "14:50" # 强制平回时点
PMS_T0_STOCK_DAY_LOSS: float = 0.003 # 单票当日T亏熔断 (占规模)
PMS_T0_GLOBAL_DAY_LOSS: float = 0.01 # 全局当日T亏熔断
PMS_STRATEGY_ENABLED: bool = False # 个股交易方案(策略)层总开关; False=strategy_runner 空转不评估
# --- 决策系统信号消化 (设计 §10) ---
PMS_SIGNAL_ENABLED: bool = True
PMS_SIGNAL_GROUP: str = "pms_signal_consumer" # 独立消费组, 不与 trading_service 抢消息
PMS_SIGNAL_CONSUMER: str = "pms_1"
PMS_SIGNAL_STREAM_INTRADAY: str = "intraday_signals:{ymd}" # db2, 每日一条流
PMS_SIGNAL_STREAM_SELL: str = "bionic:signals:llm_sell_actions" # db3, 固定 key
# 2026-08-18 实测: bionic 对持仓票的卖出置信度(0~100 证据一致性)多落在 60~84,
# 原门槛 0.75 把大量持仓票的卖出静默忽略。降到 0.60: 60~84 一律浮上来变提议(可见、
# 一键采纳), ≥85 仍自动清仓; 加上 signal_service 对持仓票的"未采纳也留痕", 不再丢信号。
PMS_SIGNAL_SELL_CONF_MIN: float = 0.60 # 低于此置信度的卖出信号不消化(仅持仓票留痕)
PMS_SIGNAL_AUTO_EXIT_CONF: float = 0.85 # 高于此置信度直接转清仓指令, 之间则落提议
PMS_SIGNAL_TRIM_RATIO: float = 0.3333 # 中等置信度时的减仓比例
# --- 宏观择时层 (股汇对冲指数 → 组合仓位命令 + 个股宏观闸) ---
# 方案 MACRO_TIMING_PLAN.md; 阈值与对数映射参数依据标定报告 MACRO_CALIB_2026-08-18.md。
# 模拟仓拍板 (2026-08-18): 默认总开关开 + 全自动档, 不设观察期; **转实盘前
# 改回 False / propose_only**。ENABLED 已进 param_store.FAIL_CLOSED: 参数表
# 读不到时按关闭处理, 基础设施故障期间宁可不动。
PMS_MACRO_ENABLED: bool = True # 总开关; 关 = 调度位空转, 不取数不计算
PMS_MACRO_AUTONOMY: str = "full" # off 只算不动 / propose_only 出建议 / full 自动下命令
PMS_MACRO_SIGNALS: str = "hedge_fx" # 启用的信号清单 (逗号分隔; 注册表在 macro_service)
PMS_MACRO_STOCK_GATE: bool = True # 个股宏观闸: 偏热确认期间暂停自主增持与策略买开腿
PMS_MACRO_HOT_TH: float = 25.0 # 偏热进入阈值 (标定: 约 1.4 段/年)
PMS_MACRO_COLD_TH: float = -20.0 # 偏冷进入阈值 (标定: 约 4 段/年, 用户确认取 -20)
PMS_MACRO_EXIT_BAND: float = 15.0 # 迟滞退出带; 偏热侧它同时是降仓命令的触发线
PMS_MACRO_CONFIRM_DAYS: int = 1 # 偏冷进区连续 N 个交易日才触发 (标定: 等第二天更差)
PMS_MACRO_TRIGGER_HOT: str = "zone_exit" # 偏热口径: 回落再动 (标定: 机会成本最低)
PMS_MACRO_TRIGGER_COLD: str = "zone_enter" # 偏冷口径: 进区即动 (标定: 反弹在极值当口)
PMS_MACRO_LOG_S0: float = 0.049 # 对数映射 target = S0·ln(1+e/k), 标定回填
PMS_MACRO_LOG_K: float = 1.6 # 对数映射尺度 k, 标定回填
PMS_MACRO_SHIFT_MAX: float = 0.20 # 单个极值周期累计调整封顶 (占规模百分点)
PMS_MACRO_STEP_MIN: float = 0.02 # 裁剪后步长小于此不动; 也是确认当日首步保底
PMS_MACRO_COOLDOWN_TDAYS: int = 3 # 同方向两次动作最小间隔 (交易日)
PMS_MACRO_MIN_PCT: float = 0.20 # 宏观降仓地板 (用户命令不受此限)
PMS_MACRO_MAX_PCT: float = 0.0 # 宏观升仓天花板; 0 = 不单设, 由总仓上限兜底
PMS_MACRO_WINDOW_TDAYS: int = 3 # 宏观命令的执行窗口
PMS_MACRO_RESPECT_BRAKE: bool = True # 组合刹车期间不自动升仓 (方案唯一假设项)
PMS_MACRO_STALE_TDAYS: int = 3 # 任一数据源日龄超此 (交易日) → 信号不可用
PMS_MACRO_RET_WIN: int = 20 # 收益计算窗口
PMS_MACRO_Z_WIN: int = 40 # 标准化窗口
PMS_MACRO_SHIBOR_BETA: float = 0.02 # 利率修正系数 β
# --- 对账与回放 ---
PMS_REPLAY_INTERVAL_MIN: int = 5
PMS_RECON_ALARM_DAYS: int = 3 # 连续不一致 N 日升级 ERROR
# 持仓事实源: ws_first(默认, ws 快照为主/表为兜底) / ws_only / table_only
# 目标架构下 trading_service 全量退出业务, 那张表没有明确写入方; 协议 §6.2 定的也是 ws 快照。
PMS_RECON_SOURCE: str = "ws_first"
PMS_RECON_WS_SNAPSHOT_MAX_AGE_SEC: int = 900 # ws 快照超此秒数视为过期 (轮询 300s + 余量)
settings = Settings()