tradingSystem/app/repo/macro_repo.py

112 lines
4.4 KiB
Python

# -*- 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