feat(HEL-463): 接入剩余行情数据到 datahub
扩展盘后正式集(涨跌停/人气/龙虎榜/板块日线)与盘中观察 API(报价/指数/分时),网站 bridge 按开关接入并回退旧链路;问天改为按数据依赖跟随开关,不再整栈强制旧路径。 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
16ba83ec01
commit
605f97e5df
@@ -45,6 +45,30 @@ RAW = {
|
||||
{"ts_code": "600000.SH", "trade_date": "20240902", "vol": 100, "price": 10.15, "amount": 1500000, "pre_close": 10.00, "turnover_rate": 0.1, "volume_ratio": 1.2, "float_share": 2000},
|
||||
{"ts_code": "000001.SZ", "trade_date": "20240902", "vol": 80, "price": 11.05, "amount": 1200000, "pre_close": 11.10, "turnover_rate": 0.2, "volume_ratio": 0.9, "float_share": 1800},
|
||||
],
|
||||
"limit_list_d": [
|
||||
{"trade_date": "20240902", "ts_code": "600000.SH", "industry": "银行", "name": "浦发银行", "close": 10.2, "pct_chg": 9.95, "amount": 1e8, "limit_amount": 5000, "float_mv": 800, "total_mv": 1000, "turnover_ratio": 5.0, "fd_amount": 2e7, "first_time": "09:30:01", "last_time": "14:55:00", "open_times": 0, "up_stat": "1/1", "limit_times": 1, "limit_type": "U"},
|
||||
],
|
||||
"ths_hot": [
|
||||
{"ts_code": "600000.SH", "ts_name": "浦发银行", "hot": 90.0, "rank": 1, "pct_change": 1.2, "current_price": 10.2, "concept": "银行", "data_type": "热股", "trade_date": "20240902"},
|
||||
],
|
||||
"dc_hot": [
|
||||
{"ts_code": "600000.SH", "ts_name": "浦发银行", "rank": 2, "pct_change": 1.2, "current_price": 10.2, "hot": 80.0, "concept": "银行", "data_type": "A股市场", "trade_date": "20240902"},
|
||||
],
|
||||
"hm_detail": [
|
||||
{"trade_date": "20240902", "ts_code": "600000.SH", "ts_name": "浦发银行", "buy_amount": 1000, "sell_amount": 200, "net_amount": 800, "hm_name": "测试游资", "hm_orgs": "某某营业部", "tag": "超买"},
|
||||
],
|
||||
"top_list": [
|
||||
{"trade_date": "20240902", "ts_code": "600000.SH", "name": "浦发银行", "pct_change": 9.95, "reason": "涨幅偏离值达7%"},
|
||||
],
|
||||
"ths_daily": [
|
||||
{"ts_code": "885811.TI", "trade_date": "20240902", "open": 1000, "high": 1010, "low": 990, "close": 1005, "pre_close": 995, "pct_change": 1.0, "vol": 100, "turnover_rate": 1.2},
|
||||
],
|
||||
"dc_index": [
|
||||
{"ts_code": "BK0475", "trade_date": "20240902", "name": "银行", "open": 100, "high": 101, "low": 99, "close": 100.5, "pre_close": 99.5, "pct_change": 1.0, "vol": 10, "amount": 1e8, "turnover_rate": 0.5},
|
||||
],
|
||||
"sw_daily": [
|
||||
{"ts_code": "801780.SI", "trade_date": "20240902", "name": "银行", "open": 2000, "high": 2010, "low": 1990, "close": 2005, "pct_change": 0.8, "vol": 50, "amount": 2e8},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -66,4 +90,9 @@ def fake_transport(api_name: str, params: dict, fields: str):
|
||||
start = str(params.get("start_date") or "")
|
||||
end = str(params.get("end_date") or "99999999")
|
||||
return [row for row in RAW["trade_cal"] if start <= row["cal_date"] <= end]
|
||||
return list(RAW.get(api_name) or [])
|
||||
rows = list(RAW.get(api_name) or [])
|
||||
if api_name == "limit_list_d":
|
||||
limit_type = str(params.get("limit_type") or "")
|
||||
if limit_type:
|
||||
rows = [row for row in rows if str(row.get("limit_type") or "") == limit_type]
|
||||
return rows
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from datahub.adapters.tushare import TushareAdapter
|
||||
from datahub.crypto import SecretVault
|
||||
from datahub.hub import Hub
|
||||
from datahub.settings import Settings
|
||||
from tests.fixtures import TRADE_DATE, fake_transport
|
||||
|
||||
|
||||
class ExtendedEodTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
key = SecretVault.generate_key()
|
||||
settings = Settings(
|
||||
host="127.0.0.1",
|
||||
port=0,
|
||||
encryption_key=key,
|
||||
api_token="k" * 32,
|
||||
admin_password="StartPass1",
|
||||
tushare_token="tushare-secret",
|
||||
db_path=Path(self.tmp.name) / "hub.db",
|
||||
backup_dir=Path(self.tmp.name) / "backups",
|
||||
scheduler_enabled=False,
|
||||
quality={"daily_row_ratio": 0.5, "null_rate_max": 0.5, "list_limit_default": 5000, "list_limit_max": 5000},
|
||||
)
|
||||
adapter = TushareAdapter("tushare-secret", transport=fake_transport)
|
||||
self.hub = Hub(settings, adapter=adapter)
|
||||
self.hub.pipeline.ingest_reference(TRADE_DATE)
|
||||
for dataset in ("daily", "valuation", "moneyflow", "auction", "index_daily"):
|
||||
self.hub.pipeline.run_dataset(dataset, TRADE_DATE)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.hub.stop()
|
||||
self.tmp.cleanup()
|
||||
|
||||
def test_extended_soft_datasets_publish_and_serve(self) -> None:
|
||||
results = self.hub.pipeline.run_extended_soft(
|
||||
("limit_events", "popularity", "dragon_tiger", "sector_daily"),
|
||||
TRADE_DATE,
|
||||
)
|
||||
for name in ("limit_events", "popularity", "dragon_tiger", "sector_daily"):
|
||||
self.assertEqual(results[name]["state"], "published", results[name])
|
||||
api = self.hub.api
|
||||
limits = api.handle("/v1/limit-events", {"date": [TRADE_DATE]})
|
||||
self.assertGreaterEqual(len(limits["data"]), 1)
|
||||
self.assertEqual(limits["meta"]["tier"], "official")
|
||||
pop = api.handle("/v1/popularity", {"date": [TRADE_DATE], "source": ["ths"]})
|
||||
self.assertEqual(pop["data"][0]["source"], "ths")
|
||||
lhb = api.handle("/v1/dragon-tiger", {"date": [TRADE_DATE]})
|
||||
self.assertEqual(lhb["data"][0]["hm_name"], "测试游资")
|
||||
# hub stores 万元→元
|
||||
self.assertEqual(lhb["data"][0]["buy_amount"], 10_000_000.0)
|
||||
sectors = api.handle("/v1/sectors", {"date": [TRADE_DATE], "family": ["ths"]})
|
||||
self.assertEqual(sectors["data"][0]["family"], "ths")
|
||||
status = api.handle("/v1/datasets/status", {"date": [TRADE_DATE]})
|
||||
names = {item["dataset"] for item in status["data"]}
|
||||
self.assertTrue({"limit_events", "popularity", "dragon_tiger", "sector_daily"} <= names)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -19,11 +19,17 @@ class LayoutTests(unittest.TestCase):
|
||||
def test_reserved_adapters_present(self) -> None:
|
||||
from datahub.adapters import RESERVED
|
||||
|
||||
for name in ("eastmoney", "tencent", "ths", "xgb", "akshare", "ifind"):
|
||||
for name in ("ths", "xgb", "akshare", "ifind"):
|
||||
self.assertIn(name, RESERVED)
|
||||
probe = RESERVED[name].probe()
|
||||
self.assertEqual(probe["state"], "reserved")
|
||||
self.assertFalse(probe["configured"])
|
||||
for name in ("eastmoney", "tencent"):
|
||||
self.assertIn(name, RESERVED)
|
||||
probe = RESERVED[name].probe()
|
||||
# Live free adapters: probe may be ok/error/empty depending on network.
|
||||
self.assertIn(probe["state"], {"ok", "empty", "error"})
|
||||
self.assertTrue(probe["configured"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user