From 86a13ede429237862cd7d76e084232a890faf9cf Mon Sep 17 00:00:00 2001 From: claude_dev Date: Fri, 14 Aug 2026 22:57:20 +0800 Subject: [PATCH] =?UTF-8?q?test(portfolio):=20=E4=BF=AEstale=E6=B5=8B?= =?UTF-8?q?=E8=AF=95test=5Fsmall=5Ffilters=5Fby=5Froe=5Froa=E2=80=94?= =?UTF-8?q?=E9=94=9A=E5=AE=9Ae807bed=E6=94=BE=E5=AE=BD=E9=98=88=E5=80=BC(r?= =?UTF-8?q?oe0.05/roa0.02,=E5=8E=9F0.15/0.10=E5=AF=B9=E4=B8=AD=E8=AF=81100?= =?UTF-8?q?0=E5=91=BD=E4=B8=AD=E4=BB=85~5%),=E4=BF=9D=E7=95=99=E5=89=94?= =?UTF-8?q?=E9=99=A4=E4=B8=8D=E5=90=88=E6=A0=BC=E8=82=A1=E6=84=8F=E5=9B=BE?= =?UTF-8?q?(=E6=8D=A2roe0.03/roa0.01=E6=A0=B7=E6=9C=AC)=20[nas]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/portfolio/test_all_weather.py | 59 +++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) 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()