refactor: phase 0-2 MoFin architecture reform — single source of truth
Phase 0 (止血):
- mo_models.py: unified calc_total_assets(), is_hk_stock(), get_hk_rate() — single source of truth
- Fixed 3 files missing frozen_cash: holdings_reconciliation, server, import_holding_xls
- Fixed stale_push_wlin: unified is_hk_stock detection, removed hardcoded 0.866
- Fixed price_monitor: consolidated 2 duplicate total_assets blocks into mo_models calls
- Fixed stock_scorer: replaced broken len()<=5 is_hk_stock heuristic
- Fixed strategy_lifecycle: replaced non-existent currency_utils import with mo_models
Phase 1 (DSA adapter):
- mo_provider.py: wraps DSA DataFetcherManager (16 fetchers, auto-fallback)
- TDX relay as primary, DSA as backup for realtime/kline/news/fundamentals
Phase 2 (Integration):
- mo_bridge.py: injects DSA market review + news context into MoFin analysis prompts
- Graceful degradation if DSA not installed
Infrastructure:
- mo_config.py: centralized Config singleton replacing scattered hardcoded paths
- All 11 changed files pass python compile check
Impact: total_assets now computed in ONE place (mo_models).
is_hk_stock now ONE implementation (no more false negatives).
HK rate now ONE source (hk_rate API → cache → 0.87 fallback).
No more hardcoded 0.866/0.8664/0.8700 divergence.
This commit is contained in:
+18
-33
@@ -10,6 +10,9 @@ import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
# ── MoFin unified model ──────────────────────────────────────────────
|
||||
from mo_models import is_hk_stock, get_hk_rate, calc_total_assets, calc_total_mv, calc_position_pct
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
WATCHLIST_PATH = "/home/hmo/web-dashboard/data/watchlist.json"
|
||||
@@ -26,10 +29,9 @@ except ImportError:
|
||||
HAS_REASSESS = False
|
||||
|
||||
try:
|
||||
from hk_rate import hkd_to_cny
|
||||
HK_RATE = hkd_to_cny()
|
||||
HK_RATE = get_hk_rate()
|
||||
except Exception:
|
||||
HK_RATE = 0.8700 # fallback
|
||||
HK_RATE = 0.87 # ultimate fallback
|
||||
|
||||
# 分支系统与情景检测
|
||||
try:
|
||||
@@ -152,8 +154,8 @@ def refresh_data_prices():
|
||||
if s['code'] in prices:
|
||||
price, _, change_pct = prices[s['code']]
|
||||
if price > 0:
|
||||
# 港股:API返回HKD,需转RMB(2026-06-23 bugfix)
|
||||
if str(s['code']).startswith(('0','1')) and len(str(s['code']))==5:
|
||||
# 港股:API返回HKD,需转RMB
|
||||
if is_hk_stock(s['code']):
|
||||
price = round(price * HK_RATE, 2)
|
||||
old = s.get('price', 0)
|
||||
if abs(old - price) > 0.001:
|
||||
@@ -163,16 +165,10 @@ def refresh_data_prices():
|
||||
changed = True
|
||||
if changed:
|
||||
pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
# 统一计算总资产:持仓市值 + 现金(所有港股价已×HK_RATE转CNY)
|
||||
pf['total_mv'] = round(sum(
|
||||
h.get('shares',0) * h.get('price',0) for h in pf.get('holdings',[])
|
||||
), 2)
|
||||
# total_assets = 持仓市值 + 可用现金 + 冻结资金(缺一不可!2026-06-29 bugfix)
|
||||
# cash = 可用资金(从截图/导入/成交记录来的,price_monitor不动它)
|
||||
# frozen_cash = 冻结资金(T+2未交收/挂单占用)
|
||||
available = float(pf.get('cash', 0) or 0)
|
||||
frozen = float(pf.get('frozen_cash', 0) or 0)
|
||||
pf['total_assets'] = round(pf['total_mv'] + available + frozen, 2)
|
||||
# 统一计算总资产(mo_models 唯一公式)
|
||||
pf['total_mv'] = calc_total_mv(pf.get('holdings', []))
|
||||
pf['total_assets'] = calc_total_assets(pf)
|
||||
pf['position_pct'] = calc_position_pct(pf)
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
elif pf.get('updated_at'):
|
||||
# 即使价格无变化,每10分钟刷新一次updated_at,防健康检查误报
|
||||
@@ -190,8 +186,8 @@ def refresh_data_prices():
|
||||
if s['code'] in prices:
|
||||
price, _, change_pct = prices[s['code']]
|
||||
if price > 0:
|
||||
# 港股:API返回HKD,需转RMB(2026-06-23 bugfix)
|
||||
if str(s['code']).startswith(('0','1')) and len(str(s['code']))==5:
|
||||
# 港股:API返回HKD,需转RMB
|
||||
if is_hk_stock(s['code']):
|
||||
price = round(price * HK_RATE, 2)
|
||||
old = s.get('price', 0)
|
||||
if abs(old - price) > 0.001:
|
||||
@@ -203,28 +199,17 @@ 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只更新个股价,不更新汇总)---
|
||||
# --- 汇总值重算(使用 mo_models 唯一公式)---
|
||||
try:
|
||||
live_market_value = sum(
|
||||
h.get('shares', 0) * h.get('price', 0)
|
||||
for h in pf.get('holdings', [])
|
||||
)
|
||||
|
||||
# 现金:绝不重算。保留上次的值(来自截图/导入/手动修改)。
|
||||
# 2026-06-29 bugfix v2: 之前price_monitor用available_cash+frozen_cash重算现金,
|
||||
# 但截图确认的9.2万被旧冻结数据(3.9万)覆盖=113k,导致cash来回跳
|
||||
# 修正:price_monitor只更新market_value,不碰cash
|
||||
|
||||
live_market_value = calc_total_mv(pf.get('holdings', []))
|
||||
old_mv = pf.get('total_mv', 0)
|
||||
|
||||
if abs(old_mv - live_market_value) > 0.01:
|
||||
pf['total_mv'] = round(live_market_value, 2)
|
||||
|
||||
# total_assets = 持仓市值 + 可用现金 + 冻结资金(重复!同步上一处公式)
|
||||
available = float(pf.get('cash', 0) or 0)
|
||||
frozen = float(pf.get('frozen_cash', 0) or 0)
|
||||
pf['total_assets'] = round(live_market_value + available + frozen, 2)
|
||||
pf['total_assets'] = calc_total_assets(pf)
|
||||
if pf['total_assets'] > 0:
|
||||
pf['position_pct'] = round(live_market_value / pf['total_assets'] * 100, 2)
|
||||
pf['position_pct'] = calc_position_pct(pf)
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user