Files
MoFin/mo_bridge.py
T
hmo 6abc2e45b0 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.
2026-06-29 23:25:54 +08:00

148 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
mo_bridge.py — MoFin ↔ DSA 集成桥接
在 MoFin 的定时分析流程(cron_to_xmpp.py)中,
在 LLM 分析 prompt 之前注入 DSA 的宏观情报。
用法:
from mo_bridge import enrich_analysis_context
# 在 LLM 分析前调用
context = enrich_analysis_context()
if context:
prompt += f"\n\n## 今日大盘背景\n{context}"
依赖:
需要 DSA 源码 + 依赖(pip install litellm akshare yfinance 等)
未安装时优雅降级,不影响 MoFin 正常运行。
"""
import sys
import os
import json
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
# DSA 源码路径
_DSA_BASE = Path(__file__).resolve().parent.parent / "daily-stock-analysis" / "ZhuLinsen-daily_stock_analysis-a448886"
_HAS_DSA = _DSA_BASE.is_dir() and (_DSA_BASE / "data_provider" / "base.py").exists()
def enrich_analysis_context(region: str = "cn") -> str:
"""从 DSA 获取市场背景和新闻舆情,注入 MoFin 分析上下文。
Args:
region: cn/hk/us/both — 分析哪个市场
Returns:
str: Markdown 格式的市场背景文本(可直接注入分析 prompt)
如果 DSA 不可用,返回空字符串
"""
parts = []
# 1. 大盘复盘
market_text = get_market_review(region)
if market_text:
parts.append(f"## 今日大盘背景\n{market_text}")
# 2. 搜索舆情(如果有 DSA search_service
news_text = get_news_context()
if news_text:
parts.append(f"## 今日重要新闻\n{news_text}")
return "\n\n".join(parts)
def get_market_review(region: str = "cn") -> str | None:
"""获取 DSA 市场复盘摘要"""
if not _HAS_DSA:
return None
try:
sys.path.insert(0, str(_DSA_BASE))
# 尝试从 DSA 的本地缓存中读取最近的市场复盘
from src.services.daily_market_context import DailyMarketContextService
# 先看本地是否有缓存
cache_dir = _DSA_BASE / "data" / "market_review"
if cache_dir.exists():
files = sorted(cache_dir.glob("*.md"), key=os.path.getmtime, reverse=True)
if files:
content = files[0].read_text(encoding="utf-8")
# 只取摘要部分(前 500 字)
lines = content.split("\n")
summary_lines = []
for line in lines:
if len(line.strip()) > 5:
summary_lines.append(line)
if len(summary_lines) >= 20:
break
return "\n".join(summary_lines)
# 如果没有缓存,尝试实时获取(需要 DSA 完整配置)
logger.debug("未找到 DSA 市场复盘缓存,跳过")
return None
except Exception as e:
logger.debug("获取 DSA 市场复盘失败: %s", e)
return None
finally:
# 清理 sys.path
if str(_DSA_BASE) in sys.path:
sys.path.remove(str(_DSA_BASE))
def get_news_context() -> str | None:
"""搜索今日重要财经新闻"""
# 预留接口:待 DSA 依赖安装后实现
# 目前 MoFin 的 mofin_news.py 已覆盖基本新闻需求
return None
def get_stock_fundamentals(code: str) -> dict | None:
"""通过 DSA 获取股票基本面数据"""
if not _HAS_DSA:
return None
try:
sys.path.insert(0, str(_DSA_BASE))
from mo_provider import MoDataProvider
provider = MoDataProvider()
return provider.get_fundamentals(code)
except Exception as e:
logger.debug("获取 %s 基本面失败: %s", code, e)
return None
finally:
if str(_DSA_BASE) in sys.path:
sys.path.remove(str(_DSA_BASE))
# ── 便捷入口 ──────────────────────────────────────────────────────────
def quick_summary() -> str:
"""快速获取今日分析上下文(单次调用)"""
return enrich_analysis_context()
# ── 自检 ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
print(f"DSA 可用: {_HAS_DSA}")
print(f"DSA 路径: {_DSA_BASE}")
if _HAS_DSA:
context = enrich_analysis_context()
if context:
print(f"\n=== 市场上下文 ({len(context)} 字符) ===")
print(context[:1000])
else:
print("\n无可用市场上下文(DSA 缓存为空)")
else:
print("\nDSA 不可用,跳过。部署后需安装依赖:")
print(" pip install litellm akshare yfinance baostock")