fix: validate Shenwan constituent coverage

This commit is contained in:
leefer
2026-07-24 08:45:23 +08:00
parent 3c79a4976b
commit fde2728a86
4 changed files with 340 additions and 21 deletions
+74 -1
View File
@@ -8,7 +8,11 @@ from unittest.mock import MagicMock, patch
from heaven_engine import _market_line_scores, build_manual_market_hexagram
from realtime_aggregator import WebRealtimeAggregator
from server import DashboardService
from tushare_client import TushareClient
from tushare_client import (
TushareClient,
_filter_members_by_listing,
_sector_coverage_issue,
)
class HeavenMarketLineTests(unittest.TestCase):
@@ -196,6 +200,75 @@ class HeavenMarketLineTests(unittest.TestCase):
class ShenwanMembershipTests(unittest.TestCase):
def test_confirmed_delisted_members_are_removed_for_the_target_date(self):
members = [
{"ts_code": "601318.SH", "name": "中国平安"},
{"ts_code": "601319.SH", "name": "中国人保"},
{"ts_code": "000627.SZ", "name": "退市成员"},
{"ts_code": "999999.SZ", "name": "状态未知成员"},
]
reference = {
"601318.SH": {"list_date": "20070228", "delist_date": ""},
"601319.SH": {"list_date": "20181030", "delist_date": ""},
"000627.SZ": {"list_date": "19961112", "delist_date": "20250930"},
}
eligible, excluded = _filter_members_by_listing(
members, reference, "20260723"
)
self.assertEqual(
[item["ts_code"] for item in eligible],
["601318.SH", "601319.SH", "999999.SZ"],
)
self.assertEqual(excluded[0]["ts_code"], "000627.SZ")
self.assertEqual(excluded[0]["reason"], "目标日期前已退市")
def test_member_is_kept_for_dates_before_its_delisting(self):
members = [{"ts_code": "000627.SZ", "name": "历史有效成员"}]
reference = {
"000627.SZ": {"list_date": "19961112", "delist_date": "20250930"}
}
eligible, excluded = _filter_members_by_listing(
members, reference, "20250929"
)
self.assertEqual(eligible, members)
self.assertEqual(excluded, [])
def test_sector_coverage_gate_adapts_to_member_count(self):
self.assertEqual(_sector_coverage_issue(5, 5, 100), "")
self.assertIn("全部可解释", _sector_coverage_issue(5, 4, 80))
self.assertEqual(_sector_coverage_issue(5, 4, 100, 5), "")
self.assertEqual(_sector_coverage_issue(10, 9, 90), "")
self.assertIn("至少90%", _sector_coverage_issue(9, 8, 88.9))
self.assertIn("最多缺1只", _sector_coverage_issue(20, 18, 90))
self.assertEqual(_sector_coverage_issue(50, 45, 90), "")
self.assertIn("低于90%", _sector_coverage_issue(50, 44, 88))
@patch.object(TushareClient, "query")
def test_confirmed_suspension_explains_a_missing_quote(self, query: MagicMock):
TushareClient._suspension_cache.clear()
query.return_value = [{
"ts_code": "601319.SH",
"suspend_date": "20260720",
"resume_date": "20260725",
"suspend_reason": "重大事项",
}]
members = [
{"ts_code": "601318.SH", "name": "中国平安"},
{"ts_code": "601319.SH", "name": "中国人保"},
]
suspended = TushareClient("token")._confirmed_suspended_members(
members, {"601318.SH"}, "20260723"
)
self.assertEqual(len(suspended), 1)
self.assertEqual(suspended[0]["ts_code"], "601319.SH")
self.assertEqual(suspended[0]["reason"], "重大事项")
@patch.object(TushareClient, "query")
def test_latest_effective_membership_wins_over_stale_is_new_row(self, query: MagicMock):
stale_y = {
+7 -1
View File
@@ -5,6 +5,8 @@ import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from tushare_client import _sector_coverage_issue
def load_method(name: str):
source = Path("server.py").read_text(encoding="utf-8")
@@ -19,7 +21,11 @@ def load_method(name: str):
and node.name == name
)
module = ast.Module(body=[method], type_ignores=[])
namespace = {"datetime": datetime, "Any": object}
namespace = {
"datetime": datetime,
"Any": object,
"_sector_coverage_issue": _sector_coverage_issue,
}
exec(compile(ast.fix_missing_locations(module), "server.py", "exec"), namespace)
return namespace[name]