feat(HEL-382): 搭建 datahub 底座和盘后正式数据链路
新增独立 xiaobai-datahub 服务(SQLite WAL、Tushare 盘后发布、/v1 契约和管理后台),不改现站页面与数据链路。 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
c2ebc0ab91
commit
3498dd7a4b
@@ -0,0 +1,78 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from datahub.normalize import (
|
||||
AMOUNT_THOUSAND_YUAN,
|
||||
AMOUNT_WAN_YUAN,
|
||||
VOLUME_LOT,
|
||||
apply_qfq,
|
||||
normalize_auction,
|
||||
normalize_daily,
|
||||
normalize_index_daily,
|
||||
normalize_moneyflow,
|
||||
normalize_valuation,
|
||||
review_daily_to_canonical,
|
||||
)
|
||||
from tests.fixtures import RAW
|
||||
|
||||
|
||||
class NormalizeTests(unittest.TestCase):
|
||||
def test_daily_matches_architecture_and_review_native_conversion(self) -> None:
|
||||
raw = RAW["daily"][0]
|
||||
hub = normalize_daily(raw, adj_factor=1.1)
|
||||
# review stores Tushare native units; canonical = review * factor
|
||||
review_native = dict(raw)
|
||||
converted = review_daily_to_canonical(review_native)
|
||||
self.assertEqual(hub["amount"], converted["amount"])
|
||||
self.assertEqual(hub["amount"], raw["amount"] * AMOUNT_THOUSAND_YUAN)
|
||||
self.assertEqual(hub["volume"], raw["vol"] * VOLUME_LOT)
|
||||
self.assertEqual(hub["close"], 10.2)
|
||||
self.assertEqual(hub["adj_factor"], 1.1)
|
||||
self.assertEqual(hub["ts_code"], "600000.SH")
|
||||
|
||||
def test_moneyflow_wan_to_yuan(self) -> None:
|
||||
raw = RAW["moneyflow"][0]
|
||||
hub = normalize_moneyflow(raw)
|
||||
self.assertEqual(hub["net_mf_amount"], raw["net_mf_amount"] * AMOUNT_WAN_YUAN)
|
||||
self.assertEqual(hub["buy_lg_amount"], 30 * AMOUNT_WAN_YUAN)
|
||||
|
||||
def test_valuation_mv_wan_to_yuan(self) -> None:
|
||||
raw = RAW["daily_basic"][0]
|
||||
hub = normalize_valuation(raw)
|
||||
self.assertEqual(hub["total_mv"], 1000 * AMOUNT_WAN_YUAN)
|
||||
self.assertEqual(hub["circ_mv"], 800 * AMOUNT_WAN_YUAN)
|
||||
|
||||
def test_index_daily_amount_thousand_yuan(self) -> None:
|
||||
raw = RAW["index_daily"][0]
|
||||
hub = normalize_index_daily(raw)
|
||||
self.assertEqual(hub["amount"], raw["amount"] * AMOUNT_THOUSAND_YUAN)
|
||||
self.assertEqual(hub["volume"], raw["vol"] * VOLUME_LOT)
|
||||
|
||||
def test_auction_amount_already_yuan(self) -> None:
|
||||
raw = RAW["stk_auction"][0]
|
||||
hub = normalize_auction(raw)
|
||||
self.assertEqual(hub["amount"], raw["amount"])
|
||||
self.assertEqual(hub["volume"], raw["vol"] * VOLUME_LOT)
|
||||
|
||||
def test_field_diff_against_review_native_is_explained(self) -> None:
|
||||
"""Golden: every non-zero diff vs review-native daily is a documented unit factor."""
|
||||
raw = RAW["daily"][0]
|
||||
hub = normalize_daily(raw)
|
||||
diffs = {}
|
||||
for key in ("open", "high", "low", "close", "pct_chg"):
|
||||
if hub[key] != raw[key]:
|
||||
diffs[key] = (raw[key], hub[key])
|
||||
self.assertEqual(diffs, {})
|
||||
self.assertNotEqual(hub["amount"], raw["amount"])
|
||||
self.assertEqual(hub["amount"] / raw["amount"], AMOUNT_THOUSAND_YUAN)
|
||||
self.assertEqual(hub["volume"] / raw["vol"], VOLUME_LOT)
|
||||
|
||||
def test_qfq_formula(self) -> None:
|
||||
self.assertEqual(apply_qfq(10.0, 1.1, 2.2), 5.0)
|
||||
none_price = apply_qfq(None, 1.1, 2.2)
|
||||
self.assertIsNone(none_price)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user