From 01184c2b1f022afabe8baef1fca50e5be658c533 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Sat, 15 Aug 2026 13:35:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor(portfolio):=20TET=E5=89=AF=E6=9C=AC?= =?UTF-8?q?=E7=B1=BB=E5=90=8D=E5=AF=B9=E9=BD=90=E7=AD=96=E7=95=A5=E5=BA=93?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E7=BA=A6=E5=AE=9A=E2=80=94*StrategyEx?= =?UTF-8?q?=E2=86=92*ExStrategy(AllWeatherExStrategy=E7=AD=898=E7=B1=BB):s?= =?UTF-8?q?trategy=5Fregistry.=5Fextract=5Fclass=5Fnames=E5=8F=AA=E8=AE=A4?= =?UTF-8?q?endswith('Strategy')=E7=9A=84=E7=B1=BB=E5=90=8D,Ex=E7=BB=93?= =?UTF-8?q?=E5=B0=BE=E7=B1=BB=E8=A2=AB=E5=88=A4'=E6=97=A0=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E7=B1=BB'=E6=95=B4=E6=96=87=E4=BB=B6=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E2=86=92=E7=AD=96=E7=95=A5=E5=BA=93/=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E7=BC=96=E8=BE=91=E5=99=A8=E7=9C=8B=E4=B8=8D=E5=88=B0?= =?UTF-8?q?=E5=89=AF=E6=9C=AC;=E6=94=B9=E5=90=8D=E5=90=8E=5Fscan=5Fself=5F?= =?UTF-8?q?owned=E8=87=AA=E5=8A=A8=E8=AF=86=E5=88=AB4=E6=96=87=E4=BB=B6(ty?= =?UTF-8?q?pe=3Dportfolio);CLI/API/=E5=89=8D=E7=AB=AF=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E5=80=BC(all=5Fweather=5Fex=E7=AD=89)=E4=B8=8D=E5=8F=98;328?= =?UTF-8?q?=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sanguo_portfolio/runner_backtest.py | 40 +++++++++---------- sanguo_portfolio/strategies/__init__.py | 24 +++++------ sanguo_portfolio/strategies/all_weather_ex.py | 20 +++++----- .../strategies/momentum_timing_ex.py | 22 +++++----- sanguo_portfolio/strategies/small_cap_ex.py | 18 ++++----- .../strategies/value_selection_ex.py | 14 +++---- 6 files changed, 69 insertions(+), 69 deletions(-) diff --git a/sanguo_portfolio/runner_backtest.py b/sanguo_portfolio/runner_backtest.py index bc7cbd6..1363c90 100644 --- a/sanguo_portfolio/runner_backtest.py +++ b/sanguo_portfolio/runner_backtest.py @@ -185,28 +185,28 @@ def _build_strategy(args: argparse.Namespace, provider: Any) -> Any: return ChannelTestStrategy(provider=provider, config=ChannelTestConfig()) # TET Phase 2 验证副本(issue #19):取数走 _ex strict 接口,逻辑与原策略同源 copy if name == "all_weather_ex": - from .strategies import AllWeatherConfigEx, AllWeatherStrategyEx - return AllWeatherStrategyEx( + from .strategies import AllWeatherExConfig, AllWeatherExStrategy + return AllWeatherExStrategy( provider=provider, - config=AllWeatherConfigEx(max_pool=args.max_pool), + config=AllWeatherExConfig(max_pool=args.max_pool), ) if name == "momentum_timing_ex": - from .strategies import MomentumTimingConfigEx, MomentumTimingStrategyEx - return MomentumTimingStrategyEx( + from .strategies import MomentumTimingExConfig, MomentumTimingExStrategy + return MomentumTimingExStrategy( provider=provider, - config=MomentumTimingConfigEx(max_pool=args.max_pool), + config=MomentumTimingExConfig(max_pool=args.max_pool), ) if name == "value_selection_ex": - from .strategies import ValueSelectionConfigEx, ValueSelectionStrategyEx - return ValueSelectionStrategyEx( + from .strategies import ValueSelectionExConfig, ValueSelectionExStrategy + return ValueSelectionExStrategy( provider=provider, - config=ValueSelectionConfigEx(max_pool=args.max_pool), + config=ValueSelectionExConfig(max_pool=args.max_pool), ) if name == "small_cap_ex": - from .strategies import SmallCapConfigEx, SmallCapStrategyEx - return SmallCapStrategyEx( + from .strategies import SmallCapExConfig, SmallCapExStrategy + return SmallCapExStrategy( provider=provider, - config=SmallCapConfigEx(max_pool=args.max_pool), + config=SmallCapExConfig(max_pool=args.max_pool), ) raise ValueError( f"未知 strategy: {name}(支持: all_weather / momentum_timing / value_selection / small_cap" @@ -225,28 +225,28 @@ def _register_schedule(strategy: Any) -> None: try: from .strategies import ( AllWeatherStrategy, - AllWeatherStrategyEx, + AllWeatherExStrategy, MomentumTimingStrategy, - MomentumTimingStrategyEx, + MomentumTimingExStrategy, SmallCapStrategy, - SmallCapStrategyEx, + SmallCapExStrategy, ValueSelectionStrategy, - ValueSelectionStrategyEx, + ValueSelectionExStrategy, ) - if isinstance(strategy, (AllWeatherStrategy, AllWeatherStrategyEx)): + if isinstance(strategy, (AllWeatherStrategy, AllWeatherExStrategy)): run_daily(strategy.prepare_stock_list, "9:05") run_monthly(strategy.monthly_adjustment, 1, "9:30") run_daily(strategy.stop_loss, "14:00") return - if isinstance(strategy, (MomentumTimingStrategy, MomentumTimingStrategyEx)): + if isinstance(strategy, (MomentumTimingStrategy, MomentumTimingExStrategy)): # 原策略 handle_data 单位时间触发 → 每日 9:30 run_daily(strategy.handle_data, "9:30") return - if isinstance(strategy, (ValueSelectionStrategy, ValueSelectionStrategyEx)): + if isinstance(strategy, (ValueSelectionStrategy, ValueSelectionExStrategy)): # 原策略 run_monthly 第 5 个交易日(月度调仓) run_monthly(strategy.monthly_adjustment, 5, "9:30") return - if isinstance(strategy, (SmallCapStrategy, SmallCapStrategyEx)): + if isinstance(strategy, (SmallCapStrategy, SmallCapExStrategy)): # 原策略 handle_data 单位时间触发 → 每日 9:30 # 5 日调仓周期由 handle_data 内部 day_count % tc == 0 控制(对齐 g.t % g.tc) run_daily(strategy.handle_data, "9:30") diff --git a/sanguo_portfolio/strategies/__init__.py b/sanguo_portfolio/strategies/__init__.py index 73912cd..2d964b3 100644 --- a/sanguo_portfolio/strategies/__init__.py +++ b/sanguo_portfolio/strategies/__init__.py @@ -1,13 +1,13 @@ """sanguo_portfolio 策略层。""" from .all_weather import AllWeatherConfig, AllWeatherStrategy, BrokerFacade -from .all_weather_ex import AllWeatherConfigEx, AllWeatherStrategyEx +from .all_weather_ex import AllWeatherExConfig, AllWeatherExStrategy from .channel_test import ChannelTestConfig, ChannelTestStrategy from .momentum_timing import MomentumTimingConfig, MomentumTimingStrategy -from .momentum_timing_ex import MomentumTimingConfigEx, MomentumTimingStrategyEx +from .momentum_timing_ex import MomentumTimingExConfig, MomentumTimingExStrategy from .small_cap import SmallCapConfig, SmallCapStrategy -from .small_cap_ex import SmallCapConfigEx, SmallCapStrategyEx +from .small_cap_ex import SmallCapExConfig, SmallCapExStrategy from .value_selection import ValueSelectionConfig, ValueSelectionStrategy -from .value_selection_ex import ValueSelectionConfigEx, ValueSelectionStrategyEx +from .value_selection_ex import ValueSelectionExConfig, ValueSelectionExStrategy __all__ = [ "AllWeatherStrategy", @@ -22,12 +22,12 @@ __all__ = [ "ValueSelectionStrategy", "ValueSelectionConfig", # TET Phase 2 验证副本(issue #19,验证后 Phase 3 删) - "AllWeatherStrategyEx", - "AllWeatherConfigEx", - "MomentumTimingStrategyEx", - "MomentumTimingConfigEx", - "SmallCapStrategyEx", - "SmallCapConfigEx", - "ValueSelectionStrategyEx", - "ValueSelectionConfigEx", + "AllWeatherExStrategy", + "AllWeatherExConfig", + "MomentumTimingExStrategy", + "MomentumTimingExConfig", + "SmallCapExStrategy", + "SmallCapExConfig", + "ValueSelectionExStrategy", + "ValueSelectionExConfig", ] diff --git a/sanguo_portfolio/strategies/all_weather_ex.py b/sanguo_portfolio/strategies/all_weather_ex.py index 9415bdd..5f74b9e 100644 --- a/sanguo_portfolio/strategies/all_weather_ex.py +++ b/sanguo_portfolio/strategies/all_weather_ex.py @@ -1,11 +1,11 @@ """聚宽"全天候轮动"策略(post48819)翻译到 BulletTrade 框架。 聚宽源码完整保留在原仓库 transcript,这里做**结构等价**翻译: -- ``initialize`` → ``AllWeatherStrategyEx.initialize`` -- ``prepare_stock_list`` → ``AllWeatherStrategyEx.prepare_stock_list`` -- ``stop_loss`` → ``AllWeatherStrategyEx.stop_loss`` -- ``monthly_adjustment`` → ``AllWeatherStrategyEx.monthly_adjustment`` -- ``SMALL/BIG/ROIC_BIG/BM`` → ``AllWeatherStrategyEx.small/big/roic_big/bm`` +- ``initialize`` → ``AllWeatherExStrategy.initialize`` +- ``prepare_stock_list`` → ``AllWeatherExStrategy.prepare_stock_list`` +- ``stop_loss`` → ``AllWeatherExStrategy.stop_loss`` +- ``monthly_adjustment`` → ``AllWeatherExStrategy.monthly_adjustment`` +- ``SMALL/BIG/ROIC_BIG/BM`` → ``AllWeatherExStrategy.small/big/roic_big/bm`` - ``filter_*`` → ``sanguo_portfolio.filters`` 策略层不直接 import bullet-trade 顶层 API(避免 Mac dev 环境装不全崩),通过两个注入点接入: @@ -57,7 +57,7 @@ class BrokerFacade: # ------------------------ 策略 ------------------------ @dataclass -class AllWeatherConfigEx: +class AllWeatherExConfig: """全天候策略参数(聚宽 initialize 的硬编码抽出来便于调参)。""" stock_num: int = 3 # g.stock_num @@ -79,7 +79,7 @@ class AllWeatherConfigEx: max_pool: int = 0 # 0=不限;MVP/验证用,限制 _stock_pool 返回前 N 只(避免全成分基本面下载过慢) -class AllWeatherStrategyEx: +class AllWeatherExStrategy: """聚宽"全天候轮动"策略(动态选股 + 大小盘轮动 + 海外 ETF 兜底 + 涨停盯盘)。 实例化时不连数据/不下单,所有 IO 走注入的 ``provider`` 和 ``broker``。 @@ -90,11 +90,11 @@ class AllWeatherStrategyEx: self, provider: Any, broker: Optional[BrokerFacade] = None, - config: Optional[AllWeatherConfigEx] = None, + config: Optional[AllWeatherExConfig] = None, ) -> None: self.provider = provider self.broker = broker or BrokerFacade() - self.config = config or AllWeatherConfigEx() + self.config = config or AllWeatherExConfig() # 聚宽 g.* 全局变量映射到实例属性 self.hold_list: List[str] = [] @@ -603,4 +603,4 @@ def _shift_date(date_str: str, days: int) -> str: return (dt + datetime.timedelta(days=days)).strftime("%Y-%m-%d") -__all__ = ["AllWeatherStrategyEx", "AllWeatherConfigEx", "BrokerFacade"] +__all__ = ["AllWeatherExStrategy", "AllWeatherExConfig", "BrokerFacade"] diff --git a/sanguo_portfolio/strategies/momentum_timing_ex.py b/sanguo_portfolio/strategies/momentum_timing_ex.py index f41768f..0f374e7 100644 --- a/sanguo_portfolio/strategies/momentum_timing_ex.py +++ b/sanguo_portfolio/strategies/momentum_timing_ex.py @@ -2,12 +2,12 @@ 聚宽源码完整保留在 ``docs/research/joinquant_strategies/03_momentum_timing/source.py``, 这里做**结构等价 + bug 修复**翻译: -- ``initialize`` → ``MomentumTimingStrategyEx.initialize`` -- ``calRPS`` → ``MomentumTimingStrategyEx._cal_rps`` (**修复取数区间**) -- ``findStockPool`` → ``MomentumTimingStrategyEx._find_stock_pool`` -- ``selectStocks`` → ``MomentumTimingStrategyEx._select_stocks`` -- ``calBuySign`` → ``MomentumTimingStrategyEx._cal_buy_sign`` -- ``handle_data`` → ``MomentumTimingStrategyEx.handle_data`` (**修复 date.today()**) +- ``initialize`` → ``MomentumTimingExStrategy.initialize`` +- ``calRPS`` → ``MomentumTimingExStrategy._cal_rps`` (**修复取数区间**) +- ``findStockPool`` → ``MomentumTimingExStrategy._find_stock_pool`` +- ``selectStocks`` → ``MomentumTimingExStrategy._select_stocks`` +- ``calBuySign`` → ``MomentumTimingExStrategy._cal_buy_sign`` +- ``handle_data`` → ``MomentumTimingExStrategy.handle_data`` (**修复 date.today()**) 策略层不直接 import bullet-trade 顶层 API(避免 Mac dev 环境装不全崩), 通过两个注入点接入(照 all_weather 模式): @@ -66,7 +66,7 @@ _DEFAULT_INDEX_LIST: List[str] = [ @dataclass -class MomentumTimingConfigEx: +class MomentumTimingExConfig: """牛熊分界+取强舍弱+均线动量 策略参数(聚宽 g.* 全局变量抽出便于调参)。""" # 板块列表(默认 10 个中证行业指数 000928-000937,G1 补全后切回原版,见模块顶部说明) @@ -82,7 +82,7 @@ class MomentumTimingConfigEx: # ------------------------ 策略 ------------------------ -class MomentumTimingStrategyEx: +class MomentumTimingExStrategy: """牛熊分界+取强舍弱+均线动量策略(纯量价,无基本面)。 实例化时不连数据/不下单,所有 IO 走注入的 ``provider`` 和 ``broker``。 @@ -93,11 +93,11 @@ class MomentumTimingStrategyEx: self, provider: Any, broker: Optional[BrokerFacade] = None, - config: Optional[MomentumTimingConfigEx] = None, + config: Optional[MomentumTimingExConfig] = None, ) -> None: self.provider = provider self.broker = broker or BrokerFacade() - self.config = config or MomentumTimingConfigEx() + self.config = config or MomentumTimingExConfig() # =================== initialize =================== def initialize(self, context: Any) -> None: @@ -440,4 +440,4 @@ def _shift_date(date_str: str, days: int) -> str: return (dt + datetime.timedelta(days=days)).strftime("%Y-%m-%d") -__all__ = ["MomentumTimingStrategyEx", "MomentumTimingConfigEx"] +__all__ = ["MomentumTimingExStrategy", "MomentumTimingExConfig"] diff --git a/sanguo_portfolio/strategies/small_cap_ex.py b/sanguo_portfolio/strategies/small_cap_ex.py index 75b7620..f73b3f2 100644 --- a/sanguo_portfolio/strategies/small_cap_ex.py +++ b/sanguo_portfolio/strategies/small_cap_ex.py @@ -14,10 +14,10 @@ - statsmodels 回归 import(原代码 import 但未实际用) 翻译对照: -- ``initialize`` → ``SmallCapStrategyEx.initialize`` -- ``pick_stocks`` → ``SmallCapStrategyEx._pick_stocks`` (**py2→py3**: df.sort→sort_values) -- ``compute_signals``→ ``SmallCapStrategyEx.handle_data`` (**5 日计数器**替代 g.t) -- ``rebalance`` → ``SmallCapStrategyEx._rebalance`` (**仅保留股票部分**, +- ``initialize`` → ``SmallCapExStrategy.initialize`` +- ``pick_stocks`` → ``SmallCapExStrategy._pick_stocks`` (**py2→py3**: df.sort→sort_values) +- ``compute_signals``→ ``SmallCapExStrategy.handle_data`` (**5 日计数器**替代 g.t) +- ``rebalance`` → ``SmallCapExStrategy._rebalance`` (**仅保留股票部分**, 去掉期货/账户调配/保证金,等权调仓) - ``compute_hedge_ratio`` / ``get_next_month_future`` / SubPortfolio → **删除** @@ -54,7 +54,7 @@ logger = logging.getLogger(__name__) # ------------------------ Config ------------------------ @dataclass -class SmallCapConfigEx: +class SmallCapExConfig: """小市值 20 只轮动策略参数(聚宽 g.* 全局变量抽出便于调参)。 默认值严格对齐原策略 ``set_params`` (source.py 第 38-48 行): @@ -87,7 +87,7 @@ class SmallCapConfigEx: # ------------------------ 策略 ------------------------ -class SmallCapStrategyEx: +class SmallCapExStrategy: """小市值 20 只轮动策略(纯选股,无对冲)。 实例化时不连数据/不下单,所有 IO 走注入的 ``provider`` 和 ``broker``。 @@ -103,11 +103,11 @@ class SmallCapStrategyEx: self, provider: Any, broker: Optional[BrokerFacade] = None, - config: Optional[SmallCapConfigEx] = None, + config: Optional[SmallCapExConfig] = None, ) -> None: self.provider = provider self.broker = broker or BrokerFacade() - self.config = config or SmallCapConfigEx() + self.config = config or SmallCapExConfig() # 聚宽 g.* 全局变量映射到实例属性 self.day_count: int = 0 # g.t:运行天数 @@ -446,4 +446,4 @@ def _shift_date(date_str: str, days: int) -> str: return (dt + datetime.timedelta(days=days)).strftime("%Y-%m-%d") -__all__ = ["SmallCapStrategyEx", "SmallCapConfigEx"] +__all__ = ["SmallCapExStrategy", "SmallCapExConfig"] diff --git a/sanguo_portfolio/strategies/value_selection_ex.py b/sanguo_portfolio/strategies/value_selection_ex.py index 8d12496..888f11e 100644 --- a/sanguo_portfolio/strategies/value_selection_ex.py +++ b/sanguo_portfolio/strategies/value_selection_ex.py @@ -2,8 +2,8 @@ 聚宽源码完整保留在 ``docs/research/joinquant_strategies/01_value_selection/source.py``, 这里做**结构等价 + bug 修复 + py2→py3** 翻译: -- ``initialize`` → ``ValueSelectionStrategyEx.initialize`` -- ``get_stock_list`` → ``ValueSelectionStrategyEx._get_stock_list`` +- ``initialize`` → ``ValueSelectionExStrategy.initialize`` +- ``get_stock_list`` → ``ValueSelectionExStrategy._get_stock_list`` - ``get_check_stocks_sort`` → **删除**(排序后不截断+全买的死代码,KISS) - ``buy`` / ``sell`` → 调仓逻辑合入 ``monthly_adjustment`` - ``get_data`` (pd.Panel) → ``provider.get_value_metrics`` 接口替代 @@ -56,7 +56,7 @@ logger = logging.getLogger(__name__) # ------------------------ Config ------------------------ @dataclass -class ValueSelectionConfigEx: +class ValueSelectionExConfig: """价值精选 6 条策略参数(聚宽 g.* 全局变量抽出便于调参)。 6 条过滤阈值严格对齐原策略 source.py 第 91-97 行注释 + 第 105-171 行代码。 @@ -97,7 +97,7 @@ class ValueSelectionConfigEx: # ------------------------ 策略 ------------------------ -class ValueSelectionStrategyEx: +class ValueSelectionExStrategy: """价值精选 6 条策略(全市场横向比较 + 月度调仓)。 实例化时不连数据/不下单,所有 IO 走注入的 ``provider`` 和 ``broker``。 @@ -115,11 +115,11 @@ class ValueSelectionStrategyEx: self, provider: Any, broker: Optional[BrokerFacade] = None, - config: Optional[ValueSelectionConfigEx] = None, + config: Optional[ValueSelectionExConfig] = None, ) -> None: self.provider = provider self.broker = broker or BrokerFacade() - self.config = config or ValueSelectionConfigEx() + self.config = config or ValueSelectionExConfig() # =================== initialize =================== def initialize(self, context: Any) -> None: @@ -455,4 +455,4 @@ def _is_valid_number(v: Any) -> bool: return not math.isnan(fv) and not math.isinf(fv) -__all__ = ["ValueSelectionStrategyEx", "ValueSelectionConfigEx"] +__all__ = ["ValueSelectionExStrategy", "ValueSelectionExConfig"]