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:
@@ -89,13 +89,11 @@ def main():
|
||||
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)
|
||||
# 3. Recalculate total_assets in portfolio (use mo_models for unified formula)
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from mo_models import calc_total_assets
|
||||
total_assets = calc_total_assets(pf)
|
||||
dec_total = 0
|
||||
for d in dec.get("decisions", []):
|
||||
if d.get("shares", 0) > 0 and d.get("price", 0) > 0:
|
||||
|
||||
@@ -88,9 +88,10 @@ def main():
|
||||
print(" holding文件不含现金行,必须手动提供。可以用:")
|
||||
print(f" python3 import_holding_xls.py --cash 73758.0")
|
||||
|
||||
# Use provided values or calculate
|
||||
# Use provided values or calculate (unified formula includes frozen_cash)
|
||||
if total_assets <= 0:
|
||||
total_assets = total_mv_cny + cash
|
||||
frozen_cash = float(args.get('frozen', pf.get('frozen_cash', 0)) or 0)
|
||||
total_assets = total_mv_cny + cash + frozen_cash
|
||||
if market_value <= 0:
|
||||
market_value = round(total_mv_cny, 2)
|
||||
|
||||
|
||||
+25
-17
@@ -20,6 +20,26 @@ import threading
|
||||
import time
|
||||
from datetime import datetime, time
|
||||
|
||||
# ── MoFin unified model import ──────────────────────────────────────
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
try:
|
||||
from mo_models import is_hk_stock, get_hk_rate, to_cny, calc_total_assets
|
||||
_USE_MO_MODELS = True
|
||||
except ImportError:
|
||||
_USE_MO_MODELS = False
|
||||
def is_hk_stock(code):
|
||||
code = str(code or '').strip().upper()
|
||||
return len(code) == 5 and code.isdigit() and code[0] in ('0', '1')
|
||||
def get_hk_rate():
|
||||
return 0.87
|
||||
def to_cny(price, code):
|
||||
if price is None: return price
|
||||
if is_hk_stock(code): return round(float(price) * get_hk_rate(), 2)
|
||||
return price
|
||||
def calc_total_assets(pf):
|
||||
total_mv = sum((h.get('shares',0) or 0) * (h.get('price',0) or 0) for h in pf.get('holdings',[]))
|
||||
return round(total_mv + (pf.get('cash',0) or 0) + (pf.get('frozen_cash',0) or 0), 2)
|
||||
|
||||
# 市场时段检查
|
||||
_MARKET_HOURS = {
|
||||
'ashare': (time(9, 30), time(15, 0)),
|
||||
@@ -246,14 +266,9 @@ def hk_lot_size(code):
|
||||
def lot_cost(code, price):
|
||||
if str(code).startswith("688"):
|
||||
return 200 * price
|
||||
elif len(str(code)) == 5:
|
||||
elif is_hk_stock(code):
|
||||
lot = hk_lot_size(code)
|
||||
try:
|
||||
sys.path.insert(0, '/home/hmo/MoFin')
|
||||
from hk_rate import hkd_to_cny
|
||||
rate = hkd_to_cny()
|
||||
except Exception:
|
||||
rate = 0.87
|
||||
rate = get_hk_rate()
|
||||
return int(lot * price * rate)
|
||||
else:
|
||||
return 100 * price
|
||||
@@ -487,13 +502,8 @@ def main():
|
||||
# 直接取 portfolio.json 的总资产(导入时已做港币→人民币换算)
|
||||
total_assets = pf.get("total_assets", 0) or 0
|
||||
if total_assets <= 0:
|
||||
# fallback: 手动算
|
||||
for h in pf.get("holdings", []):
|
||||
mv = h.get("shares", 0) * h.get("price", 0)
|
||||
if len(str(h.get("code", ""))) <= 5: # 港股
|
||||
mv *= 0.866
|
||||
total_assets += mv
|
||||
total_assets += available_cash
|
||||
# fallback: use unified calc_total_assets from mo_models
|
||||
total_assets = calc_total_assets(pf)
|
||||
except Exception:
|
||||
total_assets = available_cash * 5 # fallback
|
||||
|
||||
@@ -599,9 +609,7 @@ def main():
|
||||
if hs <= 0 or hp <= 0:
|
||||
continue
|
||||
hmv = hs * hp
|
||||
h_code = str(h.get("code", ""))
|
||||
if len(h_code) <= 5:
|
||||
hmv *= 0.866 # approximate HKD→CNY
|
||||
# 港股价格已是 CNY(price_monitor 写入时已转),不需要再乘汇率
|
||||
hpl_pct = (hp - hc) / hc * 100 if hc else 0
|
||||
|
||||
# 6维全面评分(越低越差,越建议卖)
|
||||
|
||||
@@ -136,14 +136,9 @@ def rank_by_outlook(holdings_list, decisions_data):
|
||||
return results
|
||||
|
||||
|
||||
def is_hk_stock(code):
|
||||
"""判断是否为港股(港股通标的代码通常5位)"""
|
||||
return len(str(code)) <= 5
|
||||
|
||||
|
||||
def is_a_stock(code):
|
||||
"""判断是否为A股(6位代码)"""
|
||||
return len(str(code)) == 6
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from mo_models import is_hk_stock, is_a_stock
|
||||
|
||||
|
||||
def settlement_delay_note(sell_code, buy_code):
|
||||
|
||||
Reference in New Issue
Block a user