fix(HEL-494): 盘中双免费源故障切换并禁止问天假0覆盖
主源东财失败后自动改走腾讯行情,成功结果写入缓存;两源都失败时返回最近真实快照并标明延迟,不再显示假0。 Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
Cursor
multica-agent
parent
b5d65ecb41
commit
ef13d6feb5
@@ -216,6 +216,44 @@ class MemberQuoteCoverageTests(unittest.TestCase):
|
||||
self.assertEqual(source, "datahub")
|
||||
client.try_quotes.assert_not_called()
|
||||
|
||||
def test_eastmoney_failure_uses_tencent_member_quotes(self) -> None:
|
||||
client = TushareClient(token="demo")
|
||||
wanted = ["000737.SZ", "000630.SZ"]
|
||||
client.try_market_quotes = MagicMock(return_value=None)
|
||||
client.try_quotes = MagicMock(return_value=None)
|
||||
aggregator = MagicMock()
|
||||
aggregator.eastmoney_stock_quotes.side_effect = RuntimeError("HTTP 503")
|
||||
aggregator.tencent_stock_quotes.return_value = [
|
||||
{"ts_code": "000737.SZ", "close": 12.3, "pre_close": 11.2},
|
||||
{"ts_code": "000630.SZ", "close": 4.5, "pre_close": 4.4},
|
||||
]
|
||||
client.realtime_aggregator = aggregator
|
||||
client._free_realtime_quotes = MagicMock(side_effect=AssertionError("tencent already won"))
|
||||
rows, source = client._load_member_realtime_quotes(wanted, "20260908")
|
||||
self.assertEqual(len(rows), 2)
|
||||
self.assertEqual(source, "tencent_qt")
|
||||
aggregator.tencent_stock_quotes.assert_called_once()
|
||||
|
||||
def test_delayed_hub_quotes_are_kept_not_zeroed(self) -> None:
|
||||
client = TushareClient(token="demo")
|
||||
delayed = [
|
||||
{
|
||||
"ts_code": "000737.SZ",
|
||||
"close": 12.3,
|
||||
"pre_close": 11.2,
|
||||
"delayed": True,
|
||||
"delay_seconds": 90,
|
||||
"delay_notice": "主备免费行情均暂不可用,显示 90 秒前的真实快照",
|
||||
}
|
||||
]
|
||||
client.try_market_quotes = MagicMock(return_value=delayed)
|
||||
client.try_quotes = MagicMock()
|
||||
client.realtime_aggregator = MagicMock()
|
||||
rows, source = client._load_member_realtime_quotes(["000737.SZ"], "20260908")
|
||||
self.assertEqual(source, "datahub_delayed")
|
||||
self.assertEqual(rows[0]["close"], 12.3)
|
||||
client.try_quotes.assert_not_called()
|
||||
|
||||
def test_ignores_non_member_quotes_from_market_snapshot(self) -> None:
|
||||
client = TushareClient(token="demo")
|
||||
client.try_market_quotes = MagicMock(
|
||||
@@ -231,6 +269,36 @@ class MemberQuoteCoverageTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual([row["ts_code"] for row in rows], ["000737.SZ"])
|
||||
|
||||
def test_local_sw_members_survive_tushare_outage(self) -> None:
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from backend.data.providers import tushare_industries as module
|
||||
|
||||
client = TushareClient(token="demo")
|
||||
stored = [
|
||||
{
|
||||
"ts_code": "000737.SZ",
|
||||
"name": "北方铜业",
|
||||
"l2_code": "801074.SI",
|
||||
"in_date": "20200101",
|
||||
"out_date": "",
|
||||
}
|
||||
]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
original = module._SW_MEMBER_DIR
|
||||
module._SW_MEMBER_DIR = Path(tmp)
|
||||
try:
|
||||
client._sw_member_cache.clear()
|
||||
client._write_local_sw_members("801074.SI", stored)
|
||||
client.query = MagicMock(side_effect=TushareError("index_member_all down"))
|
||||
members = client._sw_sector_members("801074.SI", "20260908")
|
||||
finally:
|
||||
module._SW_MEMBER_DIR = original
|
||||
client._sw_member_cache.clear()
|
||||
self.assertEqual([item["ts_code"] for item in members], ["000737.SZ"])
|
||||
client.query.assert_not_called()
|
||||
|
||||
def test_closed_keeps_daily_inner_when_sw_daily_missing(self) -> None:
|
||||
client = TushareClient(token="demo")
|
||||
client.resolve_trade_context = lambda _date: ("20260908", "20260907")
|
||||
|
||||
@@ -133,6 +133,13 @@ class FakeFreeAggregator:
|
||||
def tencent_market_quotes(self, codes, expected_date=""):
|
||||
return self.eastmoney_market_quotes(expected_date)
|
||||
|
||||
def tencent_stock_quotes(self, codes, expected_date="", minimum=None):
|
||||
wanted = {str(code).strip() for code in (codes or []) if str(code).strip()}
|
||||
rows = self.tencent_market_quotes(codes, expected_date)
|
||||
if not wanted:
|
||||
return rows
|
||||
return [row for row in rows if str(row.get("ts_code") or "") in wanted]
|
||||
|
||||
def eastmoney_indices(self):
|
||||
return [
|
||||
{
|
||||
@@ -318,6 +325,9 @@ class RealtimeDashboardTests(unittest.TestCase):
|
||||
def tencent_market_quotes(self, codes, expected_date=""):
|
||||
return list(FREE_QUOTES)
|
||||
|
||||
def tencent_stock_quotes(self, codes, expected_date="", minimum=None):
|
||||
return list(FREE_QUOTES)
|
||||
|
||||
self.client.query = query
|
||||
self.client.realtime_aggregator = TencentOnlyAggregator()
|
||||
TushareClient._realtime_reference_cache.clear()
|
||||
|
||||
Reference in New Issue
Block a user