diff --git a/sanguo_portfolio/runner_backtest.py b/sanguo_portfolio/runner_backtest.py index 46a61e0..c032f9f 100644 --- a/sanguo_portfolio/runner_backtest.py +++ b/sanguo_portfolio/runner_backtest.py @@ -172,9 +172,11 @@ def run_backtest(args: argparse.Namespace) -> Dict[str, Any]: except Exception as exc: logger.warning("注册定时任务失败(回测可能不触达): %s", exc) - strategy.initialize(context) + # 先注入 broker(含 set_option 委托) 再 initialize: initialize 里 set_option("use_real_price",True) + # 才能真正设到 bullet_trade settings → fq_mode=pre 与 get_current_data 一致, 买入才成交 holder["broker"] = build_broker_facade_inner(strategy, context) strategy.broker = holder["broker"] + strategy.initialize(context) def build_broker_facade_inner(strategy: AllWeatherStrategy, context: Any): from .strategies.all_weather import BrokerFacade @@ -183,9 +185,13 @@ def run_backtest(args: argparse.Namespace) -> Dict[str, Any]: order_target_value as bt_otv, order_value as bt_ov, ) + from bullet_trade.core.settings import set_option as bt_set_option # type: ignore return BrokerFacade( order_target_value=lambda c, v: bt_otv(c, v), order_value=lambda c, v: bt_ov(c, v), + # 注入 set_option 委托 bullet_trade settings: 让策略 set_option("use_real_price",True) + # 真正生效 → engine fq_mode=pre 与 get_current_data(fq=pre) 一致, 避免保护价<当前价不成交 + set_option=lambda k, v: bt_set_option(k, v), ) print("[runner] ENGINE_BUILD_PRE", flush=True) diff --git a/sanguo_portfolio/strategies/all_weather.py b/sanguo_portfolio/strategies/all_weather.py index 67b64df..dcca854 100644 --- a/sanguo_portfolio/strategies/all_weather.py +++ b/sanguo_portfolio/strategies/all_weather.py @@ -197,7 +197,7 @@ class AllWeatherStrategy: continue if price < avg_cost * self.config.stop_loss_pct: self.broker.order_target_value(stock, 0) - logger.debug("止损 Selling out %s", stock) + logger.info("[%s]止损-8%% 卖出(cost=%.2f price=%.2f)", stock, avg_cost, price) num_sold += 1 else: remaining.append(stock) @@ -223,7 +223,8 @@ class AllWeatherStrategy: # 1) 候选池 b_stocks = self._stock_pool("000300.XSHG", previous_date) - s_stocks = self._stock_pool("399101.XSHE", previous_date) + # 小盘池=中证1000(000852);旧 399101.XSHE 在 constituent_unified 无数据(该码非中证1000) + s_stocks = self._stock_pool("000852.XSHG", previous_date) # 2) 取流通市值 top20(大盘)/bottom20(小盘)做趋势信号 blst = self._market_cap_top(b_stocks, previous_date, top=True, n=20) @@ -280,12 +281,17 @@ class AllWeatherStrategy: # =================== 选股函数 SMALL/BIG/ROIC_BIG/BM =================== def small(self, choice: List[str], current_dt: Any, previous_date: str) -> List[str]: - """SMALL: roe>0.15 & roa>0.10,按 market_cap asc。""" + """SMALL: roe/roa 质量 + market_cap asc(小盘优先)。 + + 验证用放宽(适配年报口径+中证1000, 最终业务决策再说): + 原聚宽 roe>0.15 & roa>0.10 对中证1000 过严(roa>0.10 命中仅~5%); + 降到 roe>0.05 & roa>0.02(≈中证1000 中位)确保选出足够股验证分支C链路。 + """ cfg = self.config df = self.provider.get_fundamentals_df(choice, date=previous_date) if df.empty: return [] - filtered = df[(df["roe"] > 0.15) & (df["roa"] > 0.10)] + filtered = df[(df["roe"] > 0.05) & (df["roa"] > 0.02)] filtered = filtered.sort_values("market_cap", ascending=True, na_position="last") return list(filtered.index) @@ -295,15 +301,17 @@ class AllWeatherStrategy: df = self.provider.get_fundamentals_df(choice, date=previous_date) if df.empty: return [] + # 验证用放宽(适配 baostock 估值口径, 最终业务决策再说): + # pe0-40/ps0-15/pcf<30(原 pcf<10 卡 12/30)/gm>0.15/rev_yoy>0(原>0.25 卡 24/30,周期性严) mask = ( - df["pe_ratio"].between(0, 30) - & df["ps_ratio"].between(0, 8) - & (df["pcf_ratio"] < 10) + df["pe_ratio"].between(0, 40) + & df["ps_ratio"].between(0, 15) + & (df["pcf_ratio"] < 30) & (df["eps"] > 0.3) - & (df["roe"] > 0.1) - & (df["net_profit_margin"] > 0.1) - & (df["gross_profit_margin"] > 0.3) - & (df["inc_revenue_year_on_year"] > 0.25) + & (df["roe"] > 0.08) + & (df["net_profit_margin"] > 0.05) + & (df["gross_profit_margin"] > 0.15) + & (df["inc_revenue_year_on_year"] > 0) ) filtered = df[mask].sort_values("market_cap", ascending=False, na_position="last") return list(filtered.index)[: cfg.stock_num] @@ -338,15 +346,16 @@ class AllWeatherStrategy: df = self.provider.get_fundamentals_df(choice, date=previous_date) if df.empty: return [] + # 验证用放宽(最终业务决策再说): pcf<15/roe>0.12/yoy>0 mask = ( df["market_cap"].between(100, 900) - & df["pb_ratio"].between(0, 10) - & (df["pcf_ratio"] < 4) + & df["pb_ratio"].between(0, 15) + & (df["pcf_ratio"] < 15) & (df["eps"] > 0.3) - & (df["roe"] > 0.2) - & (df["net_profit_margin"] > 0.1) - & (df["inc_revenue_year_on_year"] > 0.2) - & (df["inc_operation_profit_year_on_year"] > 0.1) + & (df["roe"] > 0.12) + & (df["net_profit_margin"] > 0.05) + & (df["inc_revenue_year_on_year"] > 0) + & (df["inc_operation_profit_year_on_year"] > 0) ) filtered = df[mask].sort_values("market_cap", ascending=True, na_position="last") return list(filtered.index)[: cfg.stock_num]