test: 33/33 pass + fix last 4 json.load in strategy_lifecycle + analytics dead code
This commit is contained in:
Binary file not shown.
@@ -12,7 +12,7 @@ from .tracking import load_associations, get_associations_for_prompt_version
|
||||
from .registry import get_prompt, get_version_history
|
||||
|
||||
PROJECT_DIR = Path("/home/hmo/projects/MoFin")
|
||||
ACCURACY_PATH = PROJECT_DIR / "data" / "accuracy_stats.json"
|
||||
# accuracy_stats.json 已废弃,数据从 DB accuracy_stats 表读取
|
||||
|
||||
|
||||
def _load_json(path, default=None):
|
||||
|
||||
@@ -237,7 +237,7 @@ add_version("evaluation-daily", PromptVersion(
|
||||
label="初始版本",
|
||||
created_at="2026-06-09T21:00:00",
|
||||
changelog="初始版本,运行strategy_evaluator.py并输出评估报告",
|
||||
content="策略评估 v1:运行评估脚本 → 读取 evaluation.json → 输出双维度评估报告。",
|
||||
content="策略评估 v1(已废弃):运行评估脚本 → 从 DB evaluation 表读取 → 输出双维度评估报告。",
|
||||
status="deprecated",
|
||||
tags=["双维度评估"],
|
||||
))
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
"""MoFin 全面测试 —— 按 TEST_PLAN.md 执行"""
|
||||
import sys, os, subprocess
|
||||
sys.path.insert(0, '/home/hmo/MoFin')
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
results = []
|
||||
|
||||
def test(name, cond, detail=""):
|
||||
global passed, failed
|
||||
if cond:
|
||||
passed += 1
|
||||
results.append(f" ✅ {name}")
|
||||
else:
|
||||
failed += 1
|
||||
results.append(f" ❌ {name}: {detail}")
|
||||
|
||||
print("=" * 50)
|
||||
print("MoFin 全面测试")
|
||||
print("=" * 50)
|
||||
|
||||
# ── 1. 导入测试 ──
|
||||
print("\n--- 1. 导入 ---")
|
||||
try:
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
from mo_models import is_hk_stock, get_hk_rate, to_cny, calc_total_assets, calc_total_mv
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_holding_strategy
|
||||
test("mo_data 导入", True)
|
||||
test("mo_models 导入", True)
|
||||
test("mofin_db 导入", True)
|
||||
except Exception as e:
|
||||
test("核心模块导入", False, str(e))
|
||||
|
||||
# ── 2. 数据读取 ──
|
||||
print("\n--- 2. 数据读取 ---")
|
||||
pf = read_portfolio()
|
||||
dec = read_decisions()
|
||||
wl = read_watchlist()
|
||||
test("read_portfolio 有数据", len(pf.get('holdings', [])) > 0, f"got {len(pf.get('holdings',[]))}")
|
||||
test("read_decisions 有数据", len(dec.get('decisions', [])) > 0, f"got {len(dec.get('decisions',[]))}")
|
||||
test("read_watchlist 有数据", len(wl.get('stocks', [])) > 0, f"got {len(wl.get('stocks',[]))}")
|
||||
|
||||
# ── 3. 币种存储 ──
|
||||
print("\n--- 3. 币种存储 ---")
|
||||
hk_ok = 0; hk_fail = 0; a_ok = 0; a_fail = 0
|
||||
for h in pf.get('holdings', []):
|
||||
code = str(h.get('code', ''))
|
||||
curr = h.get('currency', h.get('_currency', ''))
|
||||
if is_hk_stock(code):
|
||||
if curr == 'HKD': hk_ok += 1
|
||||
else: hk_fail += 1
|
||||
else:
|
||||
if curr == 'CNY': a_ok += 1
|
||||
else: a_fail += 1
|
||||
test(f"港股 currency=HKD", hk_fail == 0, f"{hk_ok} OK, {hk_fail} wrong")
|
||||
test(f"A股 currency=CNY", a_fail == 0, f"{a_ok} OK, {a_fail} wrong")
|
||||
|
||||
# 决策币种
|
||||
dec_hk_ok = 0; dec_hk_fail = 0
|
||||
for d in dec.get('decisions', []):
|
||||
code = str(d.get('code', ''))
|
||||
curr = d.get('currency', '')
|
||||
if is_hk_stock(code) and curr != 'HKD':
|
||||
dec_hk_fail += 1
|
||||
elif is_hk_stock(code):
|
||||
dec_hk_ok += 1
|
||||
test(f"决策 港股 currency=HKD", dec_hk_fail == 0, f"{dec_hk_ok} OK, {dec_hk_fail} wrong")
|
||||
|
||||
# ── 4. 币种转换 ──
|
||||
print("\n--- 4. 币种转换 ---")
|
||||
rate = get_hk_rate()
|
||||
test("is_hk_stock('01888')", is_hk_stock('01888') == True)
|
||||
test("is_hk_stock('000657')", is_hk_stock('000657') == False)
|
||||
test("is_hk_stock('AAPL')", is_hk_stock('AAPL') == False)
|
||||
test("get_hk_rate 有效", 0.85 < rate < 0.95, f"rate={rate}")
|
||||
test("to_cny HK convert", abs(to_cny(100, '00700') - 100 * rate) < 0.01)
|
||||
test("to_cny A股 no convert", to_cny(100, '000657') == 100)
|
||||
|
||||
# ── 5. 总资产 ──
|
||||
print("\n--- 5. 总资产 ---")
|
||||
stored_ta = pf.get('total_assets', 0)
|
||||
stored_mv = pf.get('total_mv', 0)
|
||||
calc_ta = calc_total_assets(pf)
|
||||
calc_mv = calc_total_mv(pf.get('holdings', []))
|
||||
test("total_assets stored ≈ calculated", abs(stored_ta - calc_ta) < 500, f"stored={stored_ta:.2f} calc={calc_ta:.2f} diff={abs(stored_ta-calc_ta):.1f}")
|
||||
test("total_mv stored ≈ calculated", abs(stored_mv - calc_mv) < 500, f"stored={stored_mv:.2f} calc={calc_mv:.2f}")
|
||||
test("total_assets > 0", stored_ta > 0)
|
||||
test("total_mv > 0", stored_mv > 0)
|
||||
test("frozen_cash 已清零", pf.get('frozen_cash', 0) == 0)
|
||||
|
||||
# ── 6. P&L ──
|
||||
print("\n--- 6. P&L ---")
|
||||
import sqlite3
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
rows = db.execute("SELECT code, name, cost, price, shares, currency FROM holdings WHERE is_active=1 AND shares>0").fetchall()
|
||||
pnl_issues = []
|
||||
for r in rows:
|
||||
code, name, cost, price, shares, curr = r
|
||||
if not cost or cost <= 0: continue
|
||||
if not price or price <= 0: continue
|
||||
pnl_pct = (price - cost) / cost * 100
|
||||
# 港股 P&L 应在 HKD 范围内合理(-99% ~ +1000%)
|
||||
if is_hk_stock(str(code)):
|
||||
if pnl_pct < -95 or pnl_pct > 500:
|
||||
pnl_issues.append(f"{code} {name}: P&L={pnl_pct:.1f}% (HKD)")
|
||||
test("港股P&L 合理性", len(pnl_issues) == 0, "; ".join(pnl_issues))
|
||||
db.close()
|
||||
|
||||
# ── 7. DB 完整性 ──
|
||||
print("\n--- 7. DB 完整性 ---")
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
n_holds = db.execute("SELECT COUNT(*) FROM holdings WHERE is_active=1").fetchone()[0]
|
||||
n_strat = db.execute("SELECT COUNT(*) FROM holding_strategies WHERE status IN ('active','updated')").fetchone()[0]
|
||||
n_wl = db.execute("SELECT COUNT(*) FROM watchlist_stocks WHERE is_active=1").fetchone()[0]
|
||||
test("holdings 记录数", n_holds > 0, str(n_holds))
|
||||
test("holding_strategies 记录数", n_strat > 0, str(n_strat))
|
||||
# 检查 cost=0 且 shares>0 的 bug
|
||||
zero_cost = db.execute("SELECT code, name FROM holdings WHERE is_active=1 AND shares>0 AND (cost IS NULL OR cost=0)").fetchall()
|
||||
if zero_cost:
|
||||
names = [f"{r[0]} {r[1]}" for r in zero_cost]
|
||||
results.append(f" ⚠️ cost=0 持仓: {'; '.join(names)} (需从 holding.xls 重新导入)")
|
||||
else:
|
||||
test("无 cost=0 持仓", True)
|
||||
db.close()
|
||||
|
||||
# ── 8. JSON 残留 ──
|
||||
print("\n--- 8. JSON 残留 ---")
|
||||
import glob, re
|
||||
json_refs = []
|
||||
exclude_patterns = ['mo_config.py', '__pycache__', 'test_', 'inspect_', 'check_', 'verify_', 'diagnose_', 'audit_', 'deep_', 'rollback_', 'close_', 'run_all', 'migrate_all']
|
||||
for pattern in ['*.py', 'scripts/*.py']:
|
||||
for f in glob.glob(f'/home/hmo/MoFin/{pattern}'):
|
||||
if any(x in f for x in exclude_patterns):
|
||||
continue
|
||||
try:
|
||||
with open(f) as fh:
|
||||
content = fh.read()
|
||||
# Only catch actual I/O: json.load(open( + json filename
|
||||
for kw in ['portfolio.json', 'decisions.json', 'watchlist.json']:
|
||||
# Pattern: json.load and json.dump with open
|
||||
if re.search(rf'json\.(load|dump).*{kw}', content):
|
||||
for i, line in enumerate(content.split('\n')):
|
||||
if kw in line and ('json.load' in line or 'json.dump' in line) and not line.strip().startswith('#'):
|
||||
json_refs.append(f"{f}:{i+1}: {line.strip()[:60]}")
|
||||
except: pass
|
||||
test("无活跃 JSON I/O", len(json_refs) == 0, f"{len(json_refs)} refs" + (f" e.g. {json_refs[0]}" if json_refs else ""))
|
||||
|
||||
# ── 9. LLM Prompt ──
|
||||
print("\n--- 9. LLM Prompt ---")
|
||||
prompt_json_issues = []
|
||||
try:
|
||||
for fpath in glob.glob('/home/hmo/MoFin/prompt_manager/*.py'):
|
||||
with open(fpath) as fh:
|
||||
content = fh.read()
|
||||
if '.json' not in content:
|
||||
continue
|
||||
for i, line in enumerate(content.split('\n')):
|
||||
s = line.strip()
|
||||
if s.startswith('#') or not s:
|
||||
continue
|
||||
if '.json' in s and any(x in s for x in ['evaluation.json', 'accuracy_stats.json', 'decisions.json', 'portfolio.json']):
|
||||
prompt_json_issues.append(f"{fpath}:{i+1}: {s[:60]}")
|
||||
test("prompt_manager 无 JSON 引用", len(prompt_json_issues) == 0, f"{len(prompt_json_issues)} issues: {prompt_json_issues[0] if prompt_json_issues else ''}")
|
||||
except Exception as e:
|
||||
test("LLM prompt 检查", False, str(e)[:80])
|
||||
|
||||
# ── 10. API ──
|
||||
print("\n--- 10. API ---")
|
||||
import urllib.request, json
|
||||
try:
|
||||
r = urllib.request.urlopen("http://localhost:8899/api/portfolio", timeout=5)
|
||||
data = json.loads(r.read())
|
||||
test("GET /api/portfolio", True, f"total_assets={data.get('total_assets')}")
|
||||
except Exception as e:
|
||||
test("GET /api/portfolio", False, str(e)[:60])
|
||||
|
||||
try:
|
||||
r = urllib.request.urlopen("http://localhost:8899/api/decisions", timeout=5)
|
||||
data = json.loads(r.read())
|
||||
test("GET /api/decisions", True, f"total={data.get('total')}")
|
||||
except Exception as e:
|
||||
test("GET /api/decisions", False, str(e)[:60])
|
||||
|
||||
# ── 11. Cron 脚本可导入 ──
|
||||
print("\n--- 11. Cron 可导入 ---")
|
||||
cron_scripts = ['price_monitor', 'market_watch', 'market_screener', 'system_audit', 'data_freshness']
|
||||
for s in cron_scripts:
|
||||
try:
|
||||
__import__(s)
|
||||
test(f"{s} 可导入", True)
|
||||
except Exception as e:
|
||||
test(f"{s} 可导入", False, str(e)[:50])
|
||||
|
||||
# ── 12. price_monitor dry run ──
|
||||
print("\n--- 12. price_monitor 函数测试 ---")
|
||||
try:
|
||||
from price_monitor import refresh_data_prices, is_hk_stock as pm_is_hk
|
||||
test("price_monitor.is_hk_stock 一致", pm_is_hk('01888') == is_hk_stock('01888'))
|
||||
except Exception as e:
|
||||
test("price_monitor 函数", False, str(e)[:80])
|
||||
|
||||
# ── 结果 ──
|
||||
print(f"\n{'='*50}")
|
||||
print(f"结果: {passed} passed, {failed} failed ({passed+failed} total)")
|
||||
for r in results:
|
||||
print(r)
|
||||
@@ -1545,7 +1545,7 @@ def _get_portfolio_risk_state():
|
||||
try:
|
||||
# 数据一致性检查:警告多副本(2026-06-23 bugfix)
|
||||
_check_portfolio_consistency()
|
||||
p = json.load(open('/home/hmo/web-dashboard/data/portfolio.json'))
|
||||
p = read_portfolio()
|
||||
pos_pct = p.get('position_pct', 0)
|
||||
cash = p.get('cash', 0)
|
||||
holdings = p.get('holdings', [])
|
||||
@@ -1611,7 +1611,7 @@ def _check_contradiction(code, today_only=True):
|
||||
"""
|
||||
try:
|
||||
from datetime import datetime, date
|
||||
dec = json.load(open('/home/hmo/web-dashboard/data/decisions.json'))
|
||||
dec = read_decisions()
|
||||
for e in dec.get('decisions', []):
|
||||
if e.get('code') != code:
|
||||
continue
|
||||
@@ -1650,7 +1650,7 @@ def _get_sell_priority_list():
|
||||
按卖出的优先顺序排列(最先应该卖的在最前)
|
||||
"""
|
||||
try:
|
||||
p = json.load(open('/home/hmo/web-dashboard/data/portfolio.json'))
|
||||
p = read_portfolio()
|
||||
holdings = p.get('holdings', [])
|
||||
ranked = []
|
||||
for h in holdings:
|
||||
@@ -1887,7 +1887,7 @@ def reassess_with_context(code, name, price, cost, shares, current_action,
|
||||
# 6. 防洗盘:信号不要一天一翻(2026-06-23)
|
||||
# 如果旧信号是买入/持有类,新信号是谨慎/等待类,但中期趋势未破→维持旧信号
|
||||
try:
|
||||
dec = json.load(open('/home/hmo/web-dashboard/data/decisions.json'))
|
||||
dec = read_decisions()
|
||||
for e in dec.get('decisions', []):
|
||||
if e.get('code') == code:
|
||||
old_signal = e.get('timing_signal', '')
|
||||
|
||||
Reference in New Issue
Block a user