总资产权威数据源统一修复

问题:总资产每次报告重新计算,数字不一致。
根因:cash字段错误(92664→73759),stale_push_wlin二次×0.866,
      报告各算各的。

修复:
1. portfolio.json cash 修正为Dad截图确认值73,758.85
2. price_monitor 每轮写入 total_mv + total_assets 到portfolio.json
   (从此所有报告只读这个字段,不自算)
3. stale_push_wlin 删除重复的 hmv *= 0.866(数据已CNY)
4. portfolio.json 加 currency: CNY 标记防混淆
5. 日志记录本次修复
This commit is contained in:
知微
2026-06-29 21:39:06 +08:00
parent a8d5418726
commit 9709c43ccb
8 changed files with 1453 additions and 782 deletions
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""data_validate.py — 数据自检,在所有报告产出前执行
检查清单:
1. 总资产 = 市值 + 现金 (误差 < 1%
2. 持仓 vs 决策交叉检查
3. 币种一致性(港股必须currency=HKD
4. 数据时效:portfolio.json/decisions.json 今日已更新
返回值:通过→退出码0,输出"OK"。失败→退出码1,输出问题描述。
"""
import json, sys
from datetime import datetime, timezone
DATA_DIR = "/home/hmo/web-dashboard/data"
PORTFOLIO_PATH = f"{DATA_DIR}/portfolio.json"
DECISIONS_PATH = f"{DATA_DIR}/decisions.json"
STALE_REPORT = f"{DATA_DIR}/strategy_staleness_report.json"
issues = []
# ── 1. 总资产校验 ────────────────────────────────────────────
try:
pf = json.load(open(PORTFOLIO_PATH))
mv_calc = sum(h["shares"] * h["price"] for h in pf.get("holdings", []) if h.get("price"))
stored_ta = pf.get("total_assets", 0)
cash = pf.get("cash", 0)
expected_ta = round(mv_calc + cash, 2)
if stored_ta > 0 and abs(stored_ta - expected_ta) / stored_ta > 0.01:
issues.append(f"总资产不匹配: 存储{stored_ta} ≠ 计算{expected_ta} (市值{mv_calc}+现金{cash})")
except Exception as e:
issues.append(f"portfolio.json读取失败: {e}")
# ── 2. 持仓 vs 决策交叉检查 ──────────────────────────────────
try:
dec = json.load(open(DECISIONS_PATH))
dec_codes = {}
for d in dec.get("decisions", []):
dec_codes[d["code"]] = d
for h in pf.get("holdings", []):
code = h["code"]
if code not in dec_codes:
issues.append(f"持仓{code}({h.get('name','?')}) 在decisions.json中无对应决策")
for code, d in dec_codes.items():
if d.get("status") == "active" and d.get("type") == "持仓策略":
if not any(h["code"] == code for h in pf.get("holdings", [])):
issues.append(f"决策{code}({d.get('name','?')})标记持仓但portfolio.json中无此股")
except Exception as e:
issues.append(f"决策检查失败: {e}")
# ── 3. 币种一致性 ────────────────────────────────────────────
try:
for d in dec.get("decisions", []):
code = str(d.get("code", ""))
# 港股必须标记currency
if len(code) == 5 and code[0] in ("0", "1"):
cur = d.get("currency", "")
if cur not in ("HKD", "CNY"):
issues.append(f"港股{code}({d.get('name','?')}) 缺currency标记,不可靠")
# 如果标记了HKDstop_loss也应该是合理的HKD价(>10
sl = d.get("stop_loss", 0)
if cur == "HKD" and sl > 0 and sl < 1:
issues.append(f"港股{code} currency=HKD但stop_loss={sl} 异常低")
except Exception as e:
issues.append(f"币种检查失败: {e}")
# ── 4. 数据时效 ──────────────────────────────────────────────
today = datetime.now().strftime("%Y-%m-%d")
try:
if pf.get("updated_at", "").startswith(today):
pass # OK
else:
issues.append(f"portfolio.json updated_at={pf.get('updated_at','?')} 不是今日")
except:
pass
# ── 输出 ──────────────────────────────────────────────────────
if issues:
print("DATA_VALIDATE_FAIL")
for i in issues:
print(f" ⚠️ {i}")
sys.exit(1)
else:
print("DATA_VALIDATE_OK")
sys.exit(0)
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""holdings_reconciliation.py — 每日持仓数据一致性校验
在 decisions.json 和 portfolio.json 之间做双向核对:
1. 股数不一致 → 以 portfolio.json 为准(券商导入为源头真理)
2. 股票存在一个文件但不存在另一个 → 同步到双方一致
3. 总资产重新计算并写入双方
24小时内禁止修改策略参数(止盈/止损/买入区),只修股数和总资产。
"""
import json, sys
from datetime import datetime
DECISIONS = "/home/hmo/web-dashboard/data/decisions.json"
PORTFOLIO = "/home/hmo/web-dashboard/data/portfolio.json"
def main():
dec = json.load(open(DECISIONS))
pf = json.load(open(PORTFOLIO))
# Build maps
dmap = {d["code"]: d for d in dec.get("decisions", [])}
pmap = {h["code"]: h for h in pf.get("holdings", []) if h.get("shares", 0) > 0}
changes = []
# 1. Remove from decisions if not in portfolio (ghost holdings)
for code in list(dmap.keys()):
d = dmap[code]
in_portfolio = code in pmap
if not in_portfolio:
if d.get("shares", 0) > 0:
old = d["shares"]
d["shares"] = 0
d["type"] = "自选策略"
d.setdefault("changelog", []).append({
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
"from": old,
"to": 0,
"reason": "reconciliation: 不在券商持仓"
})
changes.append(f" {d.get('name','')}({code}): 清仓{old}→0股(不在portfolio)")
continue
# Same stock in both: sync share count (portfolio is source of truth)
p_shares = pmap[code]["shares"]
if d.get("shares", 0) != p_shares:
old = d.get("shares", 0)
d["shares"] = p_shares
d["type"] = "持仓策略"
d.setdefault("changelog", []).append({
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
"from": old,
"to": p_shares,
"reason": "reconciliation: 股数与券商一致"
})
changes.append(f" {d.get('name','')}({code}): 股数{old}{p_shares}(对齐portfolio)")
# 2. Add to decisions if in portfolio but not in decisions
for code in pmap:
h = pmap[code]
if code not in dmap:
# Stock is in portfolio but not in decisions → add stub
stub = {
"code": code,
"name": h.get("name", f"STOCK_{code}"),
"shares": h["shares"],
"price": h.get("price", 0),
"stop_loss": 0,
"take_profit": 0,
"entry_low": 0,
"entry_high": 0,
"cost": h.get("cost", 0),
"type": "持仓策略",
"status": "active",
"timing_signal": "持有",
"action": "持仓策略 | 等待技术分析完善",
"tech_snapshot": "",
"action_note": "reconciliation: 自动补充",
"reassessed_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
"changelog": [{
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
"reason": "reconciliation: 券商持仓→自动补充策略"
}],
"trigger": {},
"analysis": {},
"currency": "CNY"
}
dec["decisions"].append(stub)
changes.append(f" {stub['name']}({code}): decisions新增持仓({h['shares']}股,来自portfolio)")
# 3. Recalculate total_assets in portfolio
stock_value = 0
for h in pf.get("holdings", []):
if h.get("shares", 0) > 0 and h.get("price", 0) > 0:
stock_value += h["shares"] * h["price"]
cash = pf.get("cash", 0)
total_assets = round(stock_value + cash, 2)
dec_total = 0
for d in dec.get("decisions", []):
if d.get("shares", 0) > 0 and d.get("price", 0) > 0:
dec_total += d["shares"] * d["price"]
old_total = pf.get("total_assets", 0)
pf["total_assets"] = total_assets
pf["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
# 4. Report
now = datetime.now().strftime("%Y-%m-%d %H:%M")
print(f"【持仓一致性校验】{now}")
print(f"")
if changes:
print(f"修正项 ({len(changes)}):")
for c in changes:
print(c)
else:
print("无差异,全部一致 ✅")
print(f"")
print(f"portfolio stock_value: {stock_value:.2f}")
print(f"portfolio cash: {cash:.2f}")
print(f"portfolio total_assets: {old_total}{total_assets}")
print(f"decisions stock_value: {dec_total:.2f}")
print(f"decisions count(shares>0): {len([d for d in dec['decisions'] if d.get('shares',0)>0])}")
# Write
dec["total"] = len(dec["decisions"])
json.dump(dec, open(DECISIONS, "w"), ensure_ascii=False, indent=2)
json.dump(pf, open(PORTFOLIO, "w"), ensure_ascii=False, indent=2)
print(f"done")
if __name__ == "__main__":
main()