From 5b9f22a643a09f066f734a8120bd6f3052d741e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Sat, 4 Jul 2026 09:45:27 +0800 Subject: [PATCH] test: 33/33 pass + fix last 4 json.load in strategy_lifecycle + analytics dead code --- .../strategy_lifecycle.cpython-312.pyc | Bin 107375 -> 107023 bytes prompt_manager/analytics.py | 2 +- prompt_manager/init_registry.py | 2 +- scripts/run_all_tests.py | 206 ++++++++++++++++++ strategy_lifecycle.py | 8 +- 5 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 scripts/run_all_tests.py diff --git a/__pycache__/strategy_lifecycle.cpython-312.pyc b/__pycache__/strategy_lifecycle.cpython-312.pyc index d6b5f6a3c82311b3278b3fb46700dd7b98e55cec..e5ca9c80bd02508488f1fd37012de3743e8164a8 100644 GIT binary patch delta 9724 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 XMnSKn8jFL4SA+eEhiG4o2890w;IO6j delta 10204 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 diff --git a/prompt_manager/analytics.py b/prompt_manager/analytics.py index aca9416..20af847 100644 --- a/prompt_manager/analytics.py +++ b/prompt_manager/analytics.py @@ -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): diff --git a/prompt_manager/init_registry.py b/prompt_manager/init_registry.py index 74bf29b..f29be4a 100644 --- a/prompt_manager/init_registry.py +++ b/prompt_manager/init_registry.py @@ -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=["双维度评估"], )) diff --git a/scripts/run_all_tests.py b/scripts/run_all_tests.py new file mode 100644 index 0000000..1f5a2c6 --- /dev/null +++ b/scripts/run_all_tests.py @@ -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) diff --git a/strategy_lifecycle.py b/strategy_lifecycle.py index 3281074..eacf924 100644 --- a/strategy_lifecycle.py +++ b/strategy_lifecycle.py @@ -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', '')