diff --git a/price_monitor.py b/price_monitor.py index 347c311..ecca774 100644 --- a/price_monitor.py +++ b/price_monitor.py @@ -193,6 +193,40 @@ def refresh_data_prices(): wl['updated_at'] = datetime.now().isoformat() json.dump(wl, open(WATCHLIST_PATH, 'w'), ensure_ascii=False, indent=2) + # --- 汇总值重算(2026-06-29 bugfix: 之前price_monitor只更新个股价,不更新汇总)--- + # total_market_value / total_assets / cash / position_pct 来自import_holding_xls快照 + # 价格更新后必须同步刷新汇总,否则报告使用过期汇总 → 现金/资产/仓位全错 + try: + live_market_value = sum( + h.get('shares', 0) * h.get('price', 0) + for h in pf.get('holdings', []) + ) + # 从 stale_report 读可用现金(Dad截图确认值) + stale_cash = 0 + try: + sr = json.load(open('/home/hmo/web-dashboard/data/strategy_staleness_report.json')) + fallback_cash = pf.get('cash', 0) or 0 + stale_cash = sr.get('portfolio', {}).get('cash', fallback_cash) + except: + stale_cash = pf.get('cash', 0) or 0 + + old_mv = pf.get('total_market_value', 0) + if abs(old_mv - live_market_value) > 0.01: + pf['total_market_value'] = round(live_market_value, 2) + + old_cash = pf.get('cash', 0) + if abs(old_cash - stale_cash) > 0.01: + pf['cash'] = stale_cash + + pf['total_assets'] = round(live_market_value + stale_cash, 2) + if pf['total_assets'] > 0: + pf['position_pct'] = round(live_market_value / pf['total_assets'] * 100, 2) + pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M') + json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2) + except Exception as e: + print(f" [汇总重算失败] {e}", flush=True) + # --- 结束汇总重算 --- + return updated diff --git a/strategy_lifecycle.py b/strategy_lifecycle.py index 864ed4a..b1bac13 100644 --- a/strategy_lifecycle.py +++ b/strategy_lifecycle.py @@ -423,12 +423,17 @@ def batch_fetch_prices(codes): def get_price_tencent(code): - """获取实时价格,自动识别A股/港股""" + """获取实时价格,港股转CNY统一存CNY""" + try: + from currency_utils import to_cny, is_hk_stock + except ImportError: + to_cny = lambda v, r=None: v + is_hk_stock = lambda c: len(str(c).strip()) == 5 and str(c).strip().isdigit() try: raw_code = code.split('_')[0] if not raw_code: return None - if len(raw_code) == 5 and raw_code.isdigit(): + if is_hk_stock(raw_code): prefix = "hk" elif raw_code.startswith("6") or raw_code.startswith("5"): prefix = "sh" @@ -442,8 +447,11 @@ def get_price_tencent(code): return float(fields[i]) if fields[i].strip() else 0.0 except: return 0.0 + price = f(3) + if is_hk_stock(raw_code) and price > 0: + price = to_cny(price) return { - "price": f(3), "close": f(4), "high": f(33), "low": f(34), + "price": price, "close": f(4), "high": f(33), "low": f(34), "code": raw_code, } except Exception as e: diff --git a/system_audit.py b/system_audit.py index ac84149..ff1f8b5 100644 --- a/system_audit.py +++ b/system_audit.py @@ -116,7 +116,20 @@ def audit_advice(conn): # ── 5. 组合健康 ── def audit_portfolio(conn): try: - pos = conn.execute("SELECT SUM(position_pct) FROM holdings WHERE is_active=1").fetchone()[0] or 0 + # 优先从 portfolio.json 读总仓位(更准确,基于实际市值/总资产) + pj_path = WEB_DATA / "portfolio.json" + if not pj_path.exists(): + pj_path = DATA_DIR / "portfolio.json" + if pj_path.exists(): + pj = json.loads(pj_path.read_text()) + pos = pj.get("position_pct", 0) + cash = pj.get("cash", 0) + available = pj.get("available_cash", cash) + else: + # 兜底:SQLite position_pct 之和 + pos = conn.execute("SELECT SUM(position_pct) FROM holdings WHERE is_active=1").fetchone()[0] or 0 + available = 0 + log_ok("组合", f"总仓位{pos:.1f}%") if pos > 90: log_issue("组合", "MEDIUM", f"仓位{pos:.1f}%超过90%,现金紧张")