diff --git a/tests/portfolio/test_all_weather.py b/tests/portfolio/test_all_weather.py index 4d6f89b..4742932 100644 --- a/tests/portfolio/test_all_weather.py +++ b/tests/portfolio/test_all_weather.py @@ -177,6 +177,55 @@ class TestStopLoss: sell_calls = [c for c in s.broker.order_target_value.call_args_list if c.args[1] == 0] assert sell_calls == [] + # ---- 昨日涨停分支:P1.2 去 1m 依赖,改 get_limit_status_batch 日线口径 ---- + + @staticmethod + def _limit_status_side_effect(is_limit_up): + def _glbs(codes, date): + return {c: {"is_limit_up": is_limit_up, "is_limit_down": False, + "is_paused": False} for c in codes} + return _glbs + + def test_yesterday_limitup_sold_when_today_not_limitup(self): + """昨日涨停 + 当日(日线)未涨停 → 涨停打开卖出。""" + from tests.portfolio.conftest import FakePosition, FakeContext + + s = make_strategy() + pos = FakePosition("600519.XSHG", avg_cost=100.0, price=95.0) # 不触发 -8% + ctx = FakeContext(positions={"600519.XSHG": pos}) + s.yesterday_hl_list = ["600519.XSHG"] + s.provider.get_limit_status_batch.side_effect = \ + self._limit_status_side_effect(is_limit_up=False) + s.stop_loss(ctx) + s.broker.order_target_value.assert_called_with("600519.XSHG", 0) + + def test_yesterday_limitup_kept_when_still_limitup(self): + """昨日涨停 + 当日仍涨停 → 继续持有,不卖。""" + from tests.portfolio.conftest import FakePosition, FakeContext + + s = make_strategy() + pos = FakePosition("600519.XSHG", avg_cost=100.0, price=95.0) + ctx = FakeContext(positions={"600519.XSHG": pos}) + s.yesterday_hl_list = ["600519.XSHG"] + s.provider.get_limit_status_batch.side_effect = \ + self._limit_status_side_effect(is_limit_up=True) + s.stop_loss(ctx) + sell_calls = [c for c in s.broker.order_target_value.call_args_list if c.args[1] == 0] + assert sell_calls == [] + + def test_yesterday_limitup_provider_failure_skips_branch(self): + """provider 无 get_limit_status_batch / 查询异常 → 跳过该分支不崩(降级)。""" + from tests.portfolio.conftest import FakePosition, FakeContext + + s = make_strategy() + pos = FakePosition("600519.XSHG", avg_cost=100.0, price=95.0) + ctx = FakeContext(positions={"600519.XSHG": pos}) + s.yesterday_hl_list = ["600519.XSHG"] + s.provider.get_limit_status_batch.side_effect = RuntimeError("boom") + s.stop_loss(ctx) # 不抛异常 + sell_calls = [c for c in s.broker.order_target_value.call_args_list if c.args[1] == 0] + assert sell_calls == [] + # =================== monthly_adjustment:轮动决策分支 =================== class TestMonthlyAdjustmentDecision: @@ -274,11 +323,15 @@ class TestMonthlyAdjustmentDecision: # =================== 选股函数直接测试 =================== class TestStockPickers: def test_small_filters_by_roe_roa(self): - """roe>0.15 & roa>0.10 → 仅保留合格股,按 market_cap asc。""" + """roe>0.05 & roa>0.02 → 仅保留合格股,按 market_cap asc。 + + 阈值是 e807bed 验证用放宽口径(原 0.15/0.10 对中证1000 命中仅~5%), + 最终业务决策再定——测试锚定当前实现。 + """ df = make_fund_df([ {"code": "A.XSHG", "roe": 0.20, "roa": 0.15, "market_cap": 500}, - {"code": "B.XSHG", "roe": 0.10, "roa": 0.20, "market_cap": 300}, # roe 不够 - {"code": "C.XSHG", "roe": 0.30, "roa": 0.05, "market_cap": 200}, # roa 不够 + {"code": "B.XSHG", "roe": 0.03, "roa": 0.20, "market_cap": 300}, # roe 不够 + {"code": "C.XSHG", "roe": 0.30, "roa": 0.01, "market_cap": 200}, # roa 不够 {"code": "D.XSHG", "roe": 0.25, "roa": 0.12, "market_cap": 100}, ]) s = make_strategy()