# -*- coding: utf-8 -*- """ 宏观择时 · 取数与落表 (严格单表访问) ====================================== 两类访问, 都走 153 代理 (source="proxy"): 1. 三张行情源表只读 —— 表位置与列名由 2026-08-18 标定实跑钉死 (报告 MACRO_CALIB_2026-08-18.md 第一节, 改列名先改那份依据再改这里): zs_day_data 上证日线 symbol='000001.SH', 列 timestamp / close gp_fx_daily 美元兑离岸人民币 ts_code='USDCNH.FXCM', 列 trade_date / bid_close gp_shibor 拆借利率宽表 列 trade_date / rate_1m (单位百分数) 2. 信号历史表 pms_macro_signal 读写 —— 每信号每交易日一行, 重扫就地更新 (唯一键)。 计算不在这里 (core/macro_rules.py), 决策与下命令也不在这里 (services/macro_service.py)。 """ from __future__ import annotations import json import logging from datetime import datetime from app.db.session import execute, fetch_all, fetch_one logger = logging.getLogger("pms.macro_repo") # 源表口径 (标定钉死) ZS_SYMBOL = "000001.SH" FX_TSCODE = "USDCNH.FXCM" def _dumps(v): return json.dumps(v, ensure_ascii=False) if not isinstance(v, (str, type(None))) else v def _loads(v): if v in (None, ""): return {} try: return json.loads(v) if isinstance(v, str) else dict(v) except (TypeError, ValueError): return {} # ================================================================ 行情源表 (只读) def fetch_hedge_inputs(limit_days: int = 140) -> dict: """股汇对冲指数的三条原始序列, 各取最近 N 条, 返回升序 (ymd, value) 列表。 N 默认 140 = 收益窗 20 + 标准化窗 40 + 富余; 单表查询, 符合代理的单表纪律。 任一查询失败向上抛 (DBUnavailable), 由 macro_service 按「不可用 → 不动作」处理。 """ n = max(80, int(limit_days)) zs = fetch_all( "SELECT `timestamp` AS d, `close` AS v FROM zs_day_data " "WHERE symbol = :s ORDER BY `timestamp` DESC LIMIT :n", {"s": ZS_SYMBOL, "n": n}, source="proxy") fx = fetch_all( "SELECT trade_date AS d, bid_close AS v FROM gp_fx_daily " "WHERE ts_code = :c ORDER BY trade_date DESC LIMIT :n", {"c": FX_TSCODE, "n": n}, source="proxy") sh = fetch_all( "SELECT trade_date AS d, rate_1m AS v FROM gp_shibor " "ORDER BY trade_date DESC LIMIT :n", {"n": n}, source="proxy") def _ser(rows): out = [] for r in rows: if r.get("v") is None: continue out.append((r["d"], float(r["v"]))) out.reverse() # DESC 取回 → 升序返回 return out return {"zs": _ser(zs), "fx": _ser(fx), "shibor": _ser(sh)} # ================================================================ pms_macro_signal def upsert_signal(*, signal_key: str, trade_date: int, value, zone: str, detail=None, action: str = "NONE", ref_id: str = "", note: str = "") -> int: """写当日信号行; 同 (信号, 交易日) 重扫就地更新 (唯一键 uk_sig_date)。""" return execute( "INSERT INTO pms_macro_signal (signal_key, trade_date, value, zone, detail_json, " "action, ref_id, note, updated_at) " "VALUES (:k, :d, :v, :z, :dj, :a, :r, :nt, :ts) " "ON DUPLICATE KEY UPDATE value = :v, zone = :z, detail_json = :dj, " "action = :a, ref_id = :r, note = :nt, updated_at = :ts", {"k": signal_key, "d": int(trade_date), "v": value, "z": zone, "dj": _dumps(detail or {}), "a": action or "NONE", "r": ref_id or "", "nt": (note or "")[:500], "ts": datetime.now()}, source="proxy") def get_signal(signal_key: str, trade_date: int): r = fetch_one( "SELECT * FROM pms_macro_signal WHERE signal_key = :k AND trade_date = :d " "ORDER BY id DESC LIMIT 1", {"k": signal_key, "d": int(trade_date)}, source="proxy") if r: r["detail"] = _loads(r.get("detail_json")) return r def recent_signals(signal_key: str, limit: int = 30) -> list: """最近的信号行, 按交易日从新到旧。detail_json 已解析进 detail 键。""" rows = fetch_all( "SELECT * FROM pms_macro_signal WHERE signal_key = :k " "ORDER BY trade_date DESC LIMIT :n", {"k": signal_key, "n": max(1, int(limit))}, source="proxy") for r in rows: r["detail"] = _loads(r.get("detail_json")) return rows