fix(portfolio): all_weather 小盘池码 + fq 口径 + stop_loss 日志
- all_weather:
- s_stocks 399101.XSHE → 000852.XSHG(中证1000)。旧码在 constituent_unified
无数据,分支C(小盘轮动)完全 dead(S_mean 恒 0)= 业绩差主因。
- stop_loss -8% logger.debug → info(原 INFO 不可见,无法验收该分支)。
- big/bm/small 阈值验证用放宽(适配年报口径+中证1000,最终业务决策再说):
small roe>0.15&roa>0.10 → roe>0.05&roa>0.02(原 roa>0.10 命中仅~5%)。
- runner_backtest: build_broker_facade_inner 注入 set_option 委托 bullet_trade
settings + initialize 顺序改(先注入 broker 再 initialize)。修 fq 口径不一致
(engine fq_mode=none raw vs get_current_data fq=pre)致大盘市价保护价<当前价不成交。
修后 all_weather 2022-2024 全量验证(726交易日,max-pool 50):
总收益 0.82%→15.48%,夏普 -0.16→+0.04,回撤 -27%→-23%。
全分支跑通:月度调仓36 / 分支A无敌行情2月 / 分支C小盘活了(末日全仓小盘) /
stop_loss -8% 触发111次。
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user