处理页面渲染不完全的问题
This commit is contained in:
parent
e217f2c817
commit
48f9e653da
|
|
@ -58,7 +58,7 @@ scripts/
|
||||||
test_batch4_units.py 动作引擎 四类自主动作触发与数量口径 11 例
|
test_batch4_units.py 动作引擎 四类自主动作触发与数量口径 11 例
|
||||||
test_batch5_units.py 决策系统信号流解析与消化口径 8 例
|
test_batch5_units.py 决策系统信号流解析与消化口径 8 例
|
||||||
test_batch6_units.py ws 通道: 测试向量/签名/公钥形态/水位/DDL体检 51 例
|
test_batch6_units.py ws 通道: 测试向量/签名/公钥形态/水位/DDL体检 51 例
|
||||||
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) 33 例
|
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) 34 例
|
||||||
init_db.py 建表 (应用 ddl_pms_v1.sql, 幂等, 默认演练; 含 DDL 体检)
|
init_db.py 建表 (应用 ddl_pms_v1.sql, 幂等, 默认演练; 含 DDL 体检)
|
||||||
check_db.py 实机连通性与表结构自检 (需真实 .env)
|
check_db.py 实机连通性与表结构自检 (需真实 .env)
|
||||||
gen_keys.py ws 通道密钥: 生成 / 只取公钥(--pubkey) / PEM(--pem) / 自检(--check)
|
gen_keys.py ws 通道密钥: 生成 / 只取公钥(--pubkey) / PEM(--pem) / 自检(--check)
|
||||||
|
|
@ -175,6 +175,12 @@ QMT ──trade/order_update──▶ pms-ws ──落 pms_qmt_inbox──▶
|
||||||
|
|
||||||
已经被历史成交污染的账本,用 `scripts/reset_ledger.py` 清掉重来(只删 PMS 自有表,绝不碰下游 `trading_*`),然后跑一次 `POST /api/ops/reconcile?apply_fix=true` 让对账以下游为准把真实持仓补回来。
|
已经被历史成交污染的账本,用 `scripts/reset_ledger.py` 清掉重来(只删 PMS 自有表,绝不碰下游 `trading_*`),然后跑一次 `POST /api/ops/reconcile?apply_fix=true` 让对账以下游为准把真实持仓补回来。
|
||||||
|
|
||||||
|
### 接管既有持仓时的成本价口径
|
||||||
|
|
||||||
|
对账补仓位(`ADD_RECON_LOT`)的开仓价**优先取下游 `trading_position.cost_price`**,现价只在下游给不出成本价时兜底,并在 `price_source` 与 `note` 里注明是估的。
|
||||||
|
|
||||||
|
这条不是细节:拿现价当成本,摊薄成本就等于当天价、**安全垫齐刷刷是 0**——而安全垫是盈利加仓(≥3%)、保垫减仓(峰值≥6%)、补仓评估档(−8%/−15%)共同的判断依据。一只真实成本 20、现价 10 的票(实亏 50%)会被记成「不赚不亏」,该评估的补仓不评估;页面和日报上的浮动盈亏也全是 0。2026-07-29 首次接管 22 只持仓时踩到,已修并有单测守着。
|
||||||
|
|
||||||
## 自主提议的分流(设计 §6 / §7)
|
## 自主提议的分流(设计 §6 / §7)
|
||||||
|
|
||||||
动作引擎每分钟扫一遍持仓,产出 FILL / ADD / DCA / TRIM 四类候选,然后依次过三道:
|
动作引擎每分钟扫一遍持仓,产出 FILL / ADD / DCA / TRIM 四类候选,然后依次过三道:
|
||||||
|
|
|
||||||
|
|
@ -217,22 +217,35 @@ def diff_positions(book_rows: list, ds_rows: list) -> list:
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def build_recon_fixes(diffs: list, price_map=None, lots_map=None) -> list:
|
def build_recon_fixes(diffs: list, price_map=None, lots_map=None, cost_map=None) -> list:
|
||||||
"""按差异生成修正动作 (以下游为准, 全部留痕)。
|
"""按差异生成修正动作 (以下游为准, 全部留痕)。
|
||||||
|
|
||||||
delta > 0 → 新增 RECON 批次补足 (价格取现价/成本代理, 无价则标 NEED_PRICE)
|
delta > 0 → 新增 RECON 批次补足
|
||||||
delta < 0 → 按核销次序冲销 |delta| 股
|
delta < 0 → 按核销次序冲销 |delta| 股 (用现价算已实现盈亏)
|
||||||
|
|
||||||
|
**补仓位时开仓价必须优先取下游的成本价, 不能拿现价充数。** 这条踩过:
|
||||||
|
2026-07-29 首次把下游 22 只已有持仓接进账本时, RECON 批次全按现价开仓, 于是每只票的
|
||||||
|
摊薄成本都等于当天现价、安全垫齐刷刷是 0 —— 而安全垫是盈利加仓(≥3%)、保垫减仓(峰值≥6%)、
|
||||||
|
补仓评估档(−8%/−15%) 共同的判断依据。一只真实成本 20、现价 10 的票 (实亏 50%) 会被记成
|
||||||
|
"不赚不亏", 该评估的补仓不评估; 页面与日报上的浮动盈亏也全是 0。
|
||||||
|
下游 `trading_position.cost_price` 本来就有这个数, 没有理由不用。
|
||||||
|
现价只在下游给不出成本价时兜底, 并在 note 里注明来源 —— 账本上要看得出这笔成本是估的。
|
||||||
"""
|
"""
|
||||||
price_map = price_map or {}
|
price_map = price_map or {}
|
||||||
lots_map = lots_map or {}
|
lots_map = lots_map or {}
|
||||||
|
cost_map = cost_map or {}
|
||||||
fixes = []
|
fixes = []
|
||||||
for d in diffs or []:
|
for d in diffs or []:
|
||||||
code, delta = d["ts_code"], d["delta"]
|
code, delta = d["ts_code"], d["delta"]
|
||||||
px = float(price_map.get(code) or 0)
|
px = float(price_map.get(code) or 0)
|
||||||
if delta > 0:
|
if delta > 0:
|
||||||
|
cost = float(cost_map.get(code) or 0)
|
||||||
|
open_px, src = (cost, "下游成本价") if cost > 0 else (px, "现价兜底(下游无成本价)")
|
||||||
fixes.append({"ts_code": code, "op": "ADD_RECON_LOT", "qty": delta,
|
fixes.append({"ts_code": code, "op": "ADD_RECON_LOT", "qty": delta,
|
||||||
"price": px, "need_price": px <= 0,
|
"price": open_px, "need_price": open_px <= 0,
|
||||||
"note": f"对账修正(+{delta}股): 以下游为准补入 RECON 批次"})
|
"price_source": src,
|
||||||
|
"note": f"对账修正(+{delta}股): 以下游为准补入 RECON 批次, "
|
||||||
|
f"开仓价取{src}"})
|
||||||
else:
|
else:
|
||||||
lots = lots_map.get(code) or []
|
lots = lots_map.get(code) or []
|
||||||
res = apply_sell_to_lots(lots, -delta)
|
res = apply_sell_to_lots(lots, -delta)
|
||||||
|
|
|
||||||
|
|
@ -258,10 +258,13 @@ def reconcile(*, apply_fix: bool = True) -> dict:
|
||||||
|
|
||||||
codes = [d["ts_code"] for d in diffs]
|
codes = [d["ts_code"] for d in diffs]
|
||||||
prices = market.get_prices(codes)
|
prices = market.get_prices(codes)
|
||||||
|
# 下游的成本价 —— 补仓位时的开仓价优先取它, 现价只兜底 (见 rc.build_recon_fixes 注释:
|
||||||
|
# 拿现价当成本会让安全垫齐刷刷归零, 整条纪律链跟着失灵)
|
||||||
|
costs = {r["ts_code"]: r.get("cost") for r in (ds.get("rows") or [])}
|
||||||
lots_map = {c: [{"lot_id": l["id"], "lot_type": l["lot_type"], "qty": int(l["qty"]),
|
lots_map = {c: [{"lot_id": l["id"], "lot_type": l["lot_type"], "qty": int(l["qty"]),
|
||||||
"open_date": _date_key(l["open_date"])}
|
"open_date": _date_key(l["open_date"])}
|
||||||
for l in pms_repo.list_lots(c, status="OPEN")] for c in codes}
|
for l in pms_repo.list_lots(c, status="OPEN")] for c in codes}
|
||||||
fixes = rc.build_recon_fixes(diffs, price_map=prices, lots_map=lots_map)
|
fixes = rc.build_recon_fixes(diffs, price_map=prices, lots_map=lots_map, cost_map=costs)
|
||||||
for f in fixes:
|
for f in fixes:
|
||||||
try:
|
try:
|
||||||
_apply_fix(f)
|
_apply_fix(f)
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,14 @@ def main():
|
||||||
print(f" FAIL 游标重置: {type(e).__name__}: {e}")
|
print(f" FAIL 游标重置: {type(e).__name__}: {e}")
|
||||||
failed.append(CURSOR_KEY)
|
failed.append(CURSOR_KEY)
|
||||||
|
|
||||||
|
# 对账连续不一致天数归零 —— 账本刚清空, 上一轮攒下的 streak 会让下一次对账直接判 ERROR
|
||||||
|
try:
|
||||||
|
from app.services import param_store
|
||||||
|
param_store.set_param("PMS_RECON_STREAK", 0, "reset_ledger")
|
||||||
|
print(" OK PMS_RECON_STREAK 归零 (否则下次对账会带着旧的连续天数直接升 ERROR)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" WARN 对账连续天数归零失败: {type(e).__name__}: {e}")
|
||||||
|
|
||||||
# ws 通道状态一并归零 —— 指令与 inbox 都清了, 留着旧水位会让补发对不上
|
# ws 通道状态一并归零 —— 指令与 inbox 都清了, 留着旧水位会让补发对不上
|
||||||
try:
|
try:
|
||||||
execute("UPDATE pms_ws_state SET last_seq = 0, acked_seq = 0, server_seq = 0, "
|
execute("UPDATE pms_ws_state SET last_seq = 0, acked_seq = 0, server_seq = 0, "
|
||||||
|
|
|
||||||
|
|
@ -742,6 +742,39 @@ def _():
|
||||||
downstream_repo.fetch_filled_orders = orig
|
downstream_repo.fetch_filled_orders = orig
|
||||||
|
|
||||||
|
|
||||||
|
@case("账本服务·对账补仓位取下游成本价, 不拿现价充数")
|
||||||
|
def _():
|
||||||
|
from app.core import recon as rc
|
||||||
|
from app.repo import downstream_repo
|
||||||
|
from app.services import ledger_service as ls, market
|
||||||
|
fake = install_fakes(prices={"600000.SH": 10.0, "000001.SZ": 30.0})
|
||||||
|
# 下游有两只票: A 真实成本 20 (现价 10, 实亏 50%), B 下游没给成本价
|
||||||
|
downstream_repo.fetch_positions = lambda: {
|
||||||
|
"columns": {"qty": "total_quantity", "cost": "cost_price"}, "raw_count": 2,
|
||||||
|
"rows": [{"ts_code": "600000.SH", "qty": 1000, "avail_qty": 1000, "cost": 20.0,
|
||||||
|
"price": 10.0},
|
||||||
|
{"ts_code": "000001.SZ", "qty": 500, "avail_qty": 500, "cost": None,
|
||||||
|
"price": 30.0}]}
|
||||||
|
r = ls.reconcile(apply_fix=True)
|
||||||
|
assert r["ok"], r
|
||||||
|
by = {f["ts_code"]: f for f in r["fixes"]}
|
||||||
|
# A: 必须记 20 而不是 10 —— 记成 10 的话安全垫是 0, 这只实亏 50% 的票就永远不进补仓评估
|
||||||
|
assert by["600000.SH"]["price"] == 20.0, by["600000.SH"]
|
||||||
|
assert by["600000.SH"]["price_source"] == "下游成本价"
|
||||||
|
# B: 下游没成本价才退到现价, 且留痕注明是估的
|
||||||
|
assert by["000001.SZ"]["price"] == 30.0 and "兜底" in by["000001.SZ"]["price_source"]
|
||||||
|
# 落到账本上: 摊薄成本 = 真实成本, 安全垫 = (10-20)/20 = -50%
|
||||||
|
pos = fake.positions["600000.SH"]
|
||||||
|
assert abs(float(pos["avg_cost"]) - 20.0) < 1e-6, pos
|
||||||
|
assert abs(float(pos["cushion_pct"]) - (-0.5)) < 1e-4, pos
|
||||||
|
|
||||||
|
# 纯逻辑侧: cost_map 缺项时逐只独立回退, 不会一只没成本就全退现价
|
||||||
|
fixes = rc.build_recon_fixes(
|
||||||
|
[{"ts_code": "A", "delta": 100}, {"ts_code": "B", "delta": 200}],
|
||||||
|
price_map={"A": 1.0, "B": 2.0}, cost_map={"A": 9.0})
|
||||||
|
assert [f["price"] for f in fixes] == [9.0, 2.0], fixes
|
||||||
|
|
||||||
|
|
||||||
@case("账本服务·对账以下游为准 + 连续不一致升级")
|
@case("账本服务·对账以下游为准 + 连续不一致升级")
|
||||||
def _():
|
def _():
|
||||||
from app.services import ledger_service as ls
|
from app.services import ledger_service as ls
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue