Files
claude_dev de04a8904b feat(portfolio): 移植3聚宽策略到BulletTrade + 8bug修正 + 数据缺口文档
三策略(聚宽py2→BulletTrade 0.9.2,BrokerFacade注入跨版本兼容):
- momentum_timing 动量择时(牛熊分界+行业RPS+均线,切回10中证行业指数)
- value_selection 价值精选(6条基本面过滤,切回沪深300)
- small_cap 小市值(去IC对冲,切回000985中证全指)

框架:
- runner_backtest 加 --strategy 分发(原硬编码all_weather)
- provider 加 get_value_metrics(价值精选6条基本面,NOTICE_DATE治前视偏差)
- 72单测全过(21+27+24)

修8个回测实测发现的真bug:
- 01第⑥条EPS绝对值0.08~0.5与①大盘矛盾→6条交集恒空致全程空仓,按注释本意改净利润同比8~50%
- 03原帖calRPS取数区间错(get_price start=end只取1天)→涨跌幅恒0 RPS失效;date.today()取真实今天非回测日
- 02 universe 000985不在constituent_unified→候选池空

VPS实测(短区间验证逻辑,非长期表现): 01价值+23%/03行业轮动+48%/02选出20只小盘

数据缺口(详见docs/research/joinquant_strategies/SUMMARY.md + data_gaps_fix_plan.md):
- 三表"1/3损坏"误报已撤回(全扫5530文件/表0损坏,沪深95%+健康,仅北交所920xxx空,不做北交所)
- 真实缺口: 行业成份股(G1已补)/000985(G2已补)/IC期货(02对冲去掉)/provider批量接口(G5待做,解锁长回测)
2026-07-28 22:20:49 +08:00

55 lines
2.2 KiB
Python

"""sanguo_portfolio:聚宽全天候轮动策略搬到 BulletTrade 框架。
子模块:
- ``factors`` 纯函数估值/ROIC 因子(无外部依赖)
- ``filters`` ST/停牌/科创北交/次新/涨跌停 过滤(无外部依赖)
- ``providers`` BaostockProvider(Mac/回测,历史成分股+TTM) /
SanguoMiniQmtProvider(VPS 实盘,继承 bullet-trade MiniQMTProvider)
- ``strategies`` AllWeatherStrategy(聚宽 post48819 翻译)
ENV GUARD(bullet-trade 0.9.2 坑):
- ``import bullet_trade`` 时 default provider 是 jqdata,会强制 ``import jqdatasdk``,
本地/服务器都没装(用户铁律不用 jqdata 付费)。
- 在任何 ``import bullet_trade`` **之前**设 ``DEFAULT_DATA_PROVIDER``,
default provider 切到非 jqdata provider,跳过 jqdatasdk。
- 实际数据走 ``set_data_provider(...)`` 覆盖。
"""
import os
import sys as _sys
from unittest.mock import MagicMock as _MagicMock
# ENV + mock 必须早于任何 bullet_trade import(providers 子模块会触发 bullet_trade):
# miniqmt → import xtquant(周六休市 miniQMT 客户端不响应→卡死)
# jqdata → import jqdatasdk(用户铁律不装→ModuleNotFoundError)
# 方案: ENV 设 jqdata + 预插 mock jqdatasdk(@jq.utils.assert_auth 装饰器 passthrough),
# 让 bullet_trade import 走 jqdata 分支拿 mock 不崩不卡;真实 provider 由
# set_data_provider 运行时注入覆盖(local/baostock/miniqmt)。
# 必须在包 __init__ 顶部(providers import 触发 bullet_trade 之前),runner_backtest 顶部太晚。
os.environ.setdefault("DEFAULT_DATA_PROVIDER", "jqdata")
if "jqdatasdk" not in _sys.modules:
_m = _MagicMock()
_m.utils.assert_auth = lambda func: func # jqdata.py 用 @jq.utils.assert_auth 装饰器
_sys.modules["jqdatasdk"] = _m
from . import factors, filters
from .providers import BaostockProvider, SanguoMiniQmtProvider
from .strategies import (
AllWeatherConfig,
AllWeatherStrategy,
BrokerFacade,
MomentumTimingConfig,
MomentumTimingStrategy,
)
__all__ = [
"factors",
"filters",
"BaostockProvider",
"SanguoMiniQmtProvider",
"AllWeatherStrategy",
"AllWeatherConfig",
"BrokerFacade",
"MomentumTimingStrategy",
"MomentumTimingConfig",
]