mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 18:04:20 +08:00
feat: 离线 fixture 测试、unknown 字段探测脚本、高可用传输层
- tests/fixtures/:录制 12 条命令真实响应 body(hex),配套 json 预期值 - tests/unit/test_commands_offline.py:13 个离线 pytest,无需网络,验证 各命令解析正确性及所有已知 bug 修复(#1~#5) - scripts/probe_unknowns.py:对比 unknown_1/2/3/5~8 与均价、涨停价等假设, 输出相关性分析报告,供后续字段逆向使用 - transport/sync.py:新增 ping_host()、ping_all()(并发测速)、KNOWN_HOSTS - client.py:TdxClient.from_best_host() 工厂方法(自动优选最低延迟服务器)、 TdxClient.ping_all() 静态代理、_execute() 断线自动重连(重试一次) - 修复 GetTransactionDataCmd.parse_response 多余 skip 参数调用 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
283682f6b4
commit
0fa685dbdd
+1
@@ -0,0 +1 @@
|
||||
1000d7eed0c2cce1cabe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a22c0000b9abcbbeb8c5bff600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a22c0000ec510000b2c6cef1b7d6cef600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008e7e00003fd70000b9c9b6abd1d0bebf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd55010061580000b9c9b1bebde1b9b900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002eae01002f270000d7cab1bed4cbd7f700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005dd501005d360000d2b5c4dab5e3c6c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003630303030302e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004cba0b020037210100d0d0d2b5b7d6cef6000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b3630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a3f12c0300c1240000b9abcbbeb4f3cac200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a13630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000edb251030065430600d1d0bebfb1a8b8e600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e43630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce179509009e1a0100beadd3aab7d6cef600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb3630303030302e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b5af0a0029790000d6f7c1a6d7b7d7d9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d3630303030302e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007ede280b0031680000b7d6baecc0a9b9c900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000273630303030302e7478740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0f910b00edea0400b8dfb2e3d6cec0ed00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013630303030302e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000081fc7b1000b16d0000c1fabba2b0f1b5a5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e3630303030302e74787400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016c41d1100b51e0000b9d8c1aab8f6b9c900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000473630303030302e747874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cfade9100017340000
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"num_records": 16,
|
||||
"first": {
|
||||
"filename": "600000.txt",
|
||||
"start": 0,
|
||||
"length": 11426
|
||||
}
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"length": 8070,
|
||||
"prefix": "☆最新提示☆ ◇600000 浦发银行 更新日期:2026-04-10◇ 港澳资讯 灵通V9.0\r\n"
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
0100013630303030305f484b4a10000100eb253501460a31015f484b4a00c0f5444aa42c4c86d6224d00000000000000005c8fc23fcf3a1650000000009138814ce8a30c4b400b1d4800000000c05ee54a643b004d79c4424eaee7254d948fe54c00000000907f4c4cf4f4934b6b36b34d48e26e4c000000000c9b4b4ca247404cbacc3e4c44566f4d3d0ab14100004041
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"liutong_guben": 33305837500.0,
|
||||
"zong_guben": 33305837500.0,
|
||||
"meigujing_zichan": 22.1299991607666
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
f000ec5124418510ee03bc9c014123a9344101805000008744010d8032000bae3802189b2c42039a21010fad3a038101bc68012f844f4117ad3c000d9826011a8f3f010fa8204106a312000a9d1f022ab5580012b4294404ac4e42489f490041b90e0043b11c004286180201ac120001b013424384240141b3370101a80f414096110202a4144101a60d0204ab164103901d01028b0d000b90440002b10b03108d3400059c114209a62f4204a53b0001a50c0001be1100019a0f0104b9210105891f0008a632000386124103981f0005ba3700029d1b0001b9080206a120410381150004941d4202923500019e254241a71a00418f0f4143b81c0301ad1f4242a92001408d124141841b0140a5140040982b4141ba0b0041851b4141ba0e0042b21d0042bb134143841e0042af130042950f0041850b004194090141b108414281104149a13d0043a5170043b0140048b5400041b50701419206414185070141bc090041ab074453b357014485170141ac094244ad13414e933d414c832e00478b1b0156b36901438a10424ab32d0247ad2d41499b3241419c060142b20a414587170143961341468a1b024f93694148a03241479b24034296120242962000419c150043a13941409e054243bf184148933700438b120241be0c4244a91d0042831100419c0a41449b190444bc4242449c210042b8150341a7244243b2214243961b0142ab160042b5150141b3070041bb100141b2100040bd040042b51e4142ab16024186114141910b0041b30e414194090041930c0041ac080141b9164142971a0042b61b0240b80c00409d0e0040ba0a004198270140b00400408f104141be15014099090101866341409a104143b4810141429f3201408c104141a7140140be110041951601409a0c4241be1501409d1401408a170040a9064140800e0041b5200404b4544200a41801018f3000019d110000a40a0000a10e0001b710410082170101b32d0000b2094240811b01018d400000b7190100a110010283214100b50e0100a20a00019a0f4100840c0202a61c000287204200a50f01019c130202bd1a0104902e41049a310002a01f4101bd170202871c41049f340002af200002ae1f00019e0e0001b8144101b81401018e104101bf130001ab130001a50a0001b1150003b633000191150001b1194101be274101971f4240a41f004098260040a61641419d2441459d7b014181140140aa130100971200009b0c0000ac160203866c4100a0110101ab1a41019a244100a51c0200810e4100b20e00008c0e4100ac1b4140be1b010094230000980e0000851d0000ac1f000084164140961e0000170000000202a07b
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"num_records": 240,
|
||||
"first": {
|
||||
"price": 10.29,
|
||||
"vol": 10044
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
0a00ec512441800386103200008003411301008003009d0401008003003001008003009d02010080034183020100800301b50202008003411e0100810300170100840302a07b0200
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"num_records": 10,
|
||||
"first": {
|
||||
"hour": 14,
|
||||
"minute": 56,
|
||||
"price": 10.3,
|
||||
"vol": 50
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
f0000000013630303030303e109d0f04040643acfaa60edd0f9889349483013a6ec94da7831cb2851841a6e2020001bd0788124102a8cd0187314203906aab11560fa00f9f01ae920144d203a9a30201f8019bf6010201be2d0009a346410cbd7e020ba33a4104bf37000593210204b51e000f8d400005aa150008b02d4107843a4101821101049a240005ba32410186150103bb1e0101b50b420598580001833e0203b53e0003881a0001af060002ac0e000c8c5700059c3d4101b20c0101b10a41029c280102a31f0002861c00008d054102962000018215010187190001b0260001af11000193164101a7264100a5200000b9320040a12c4140a60c0040bf0801409014424a95e0010141bd1b0042ba330141b41c4140ae030141a7150040a10e41428d320041b71a4141ae0b0141aa09004198140043bc434141bc1b0141841900418c0e0041ba164141a9130141bd160041ad1c00438c360041a40c414189124147a37d0142a01d00419a1c014094070040a8044140a70a00408c0700409f060140ab0841408d0b0040b4120140850b0040ac130040a7050040a60b4140950a0140990441408b0a0140b3094140800d01409a064140bc090141be430040b1110041be1f00408c07004081160040810d01008d05414082200042b13200408e0500409f080040960b4140bf070042843b00409c040140ba090040bd050040af110040ae0400408907004098050040b80d0041bb1e414088050140930a0040a0064142802e0040910b014182364144b36b0041a30c4141870c0140a9080040860b41418e160140bd0a0040a40d4142803201408c0a41408d0b0140880600418a1f0140960d0040ac0801409b184140b812004099090040b204004087084140a7060140b8020040b4064141a62d0140b3024140970301408c044140aa0f0040af0a0240a8370000af1b4100bd080100a1024100a5040040a22e0040810441408d0a00408e0a0140ad0d004094120040ae06414094100140890541409c0a0140bb0b4140a0060040bd0b0040aa140140b3100100a32101008f2542008b170000aa06010099030100b911000094100000900600018d114101be180101a20f0100940a0000be050000a5040001ac164101a01400028b32000086070000ae030000b31b0000940641009a1a01009b080000890a0000b10a0000b6094100a00700009f214140ae040000b90b0040ac4a41418618024082184140962201009b044140920c01009a0d4140a6250040ae100100af1000008d050000890500009f0e0040b81400008a090000a1150000b40a0000bb080000a5060001983800409a0e0000ad0f4141bd5e0040801c0040961600409b120040b025004092130040bc150040851e414092160040ad1e01408f15004090254140941d0141802d41458fde0101001d0000004143948301
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"num_records": 240,
|
||||
"first": {
|
||||
"price": 0.01,
|
||||
"vol": 48,
|
||||
"unknown_1": 54
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
0500332635018aa001c20200ea0253fb1c4c32f1c64d3726350100cc0232ea02bc82104c573fb54d38263501149a01ae01721025384c2b39e74d3926350154cc0232cc02d2d52f4c4a73db4d3a263501006814c601a7ba224c3a6ec94d
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"num_records": 5,
|
||||
"first": {
|
||||
"open": 10.25,
|
||||
"high": 10.25,
|
||||
"low": 10.08,
|
||||
"close": 10.12,
|
||||
"vol": 41151820.0
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
0569
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"count": 26885
|
||||
}
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"num_records": 1000,
|
||||
"first": {
|
||||
"code": "999999",
|
||||
"name": "上证指数",
|
||||
"pre_close": 11654847.33
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
01050100013630303030303e109d0f04040643acfaa60edd0f9889349483013a6ec94da7831cb2851841a6e2020001bd0788124102a8cd0187314203906aab114304a7b4018f1f4405ab96019812560f0000000000003e10
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"num_records": 1,
|
||||
"first": {
|
||||
"code": "600000",
|
||||
"pre_close": 9.93,
|
||||
"unknown_2": -1,
|
||||
"unknown_3": 22694
|
||||
}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
0a0083039e0f0000080083030000000800830300000008008303000000080083030000000800830300000008008303410000080083030000000800830300000008008403009483019f040200
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"num_records": 10,
|
||||
"first": {
|
||||
"hour": 14,
|
||||
"minute": 59,
|
||||
"price": 9.9,
|
||||
"vol": 0
|
||||
}
|
||||
}
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"num_records": 87,
|
||||
"first": {
|
||||
"year": 1999,
|
||||
"month": 11,
|
||||
"day": 10,
|
||||
"category": 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
"""离线 fixture 测试:将录制的原始 body 字节喂给各命令 parse_response,验证解析结果。
|
||||
|
||||
fixtures/ 目录下每个 .hex 文件是一次真实服务器响应的 body(已解压),
|
||||
对应的 .json 文件记录关键预期值,供手工核对。
|
||||
此测试文件直接断言解析结果,无需网络连接。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import pytest
|
||||
|
||||
FIXTURES = pathlib.Path(__file__).parent.parent / "fixtures"
|
||||
|
||||
|
||||
def load_hex(name: str) -> bytes:
|
||||
return bytes.fromhex((FIXTURES / f"{name}.hex").read_text().strip())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# security_count
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_security_count_parse():
|
||||
from xmtdx.commands.security_count import GetSecurityCountCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("security_count")
|
||||
cmd = GetSecurityCountCmd(Market.SH)
|
||||
count = cmd.parse_response(body)
|
||||
|
||||
assert isinstance(count, int)
|
||||
assert count > 0
|
||||
# 体积固定为 2 字节,结果与录制时完全一致
|
||||
assert count == 26885
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# security_list
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_security_list_parse():
|
||||
from xmtdx.commands.security_list import GetSecurityListCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("security_list")
|
||||
cmd = GetSecurityListCmd(Market.SH, 0)
|
||||
records = cmd.parse_response(body)
|
||||
|
||||
assert len(records) == 1000
|
||||
|
||||
r0 = records[0]
|
||||
assert r0.code == "999999"
|
||||
assert r0.name == "上证指数"
|
||||
# pre_close is a float; the index level is stored ×100 and has many decimal places
|
||||
assert abs(r0.pre_close - 11654847.33) < 1.0
|
||||
|
||||
# _raw present and non-empty for every record
|
||||
assert all(len(r._raw) > 0 for r in records)
|
||||
|
||||
|
||||
def test_security_list_gbk_no_crash():
|
||||
"""Bug #2 修复验证:GBK 解码不崩溃,所有记录均有 code。"""
|
||||
from xmtdx.commands.security_list import GetSecurityListCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("security_list")
|
||||
cmd = GetSecurityListCmd(Market.SH, 0)
|
||||
records = cmd.parse_response(body)
|
||||
assert all(r.code for r in records)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# security_bars
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_security_bars_parse():
|
||||
from xmtdx.commands.security_bars import GetSecurityBarsCmd
|
||||
from xmtdx.models.enums import Market, KlineCategory
|
||||
|
||||
body = load_hex("security_bars")
|
||||
cmd = GetSecurityBarsCmd(Market.SH, "600000", KlineCategory.DAY, 0, 5)
|
||||
bars = cmd.parse_response(body)
|
||||
|
||||
assert len(bars) == 5
|
||||
|
||||
b0 = bars[0]
|
||||
assert abs(b0.open - 10.25) < 0.01
|
||||
assert abs(b0.high - 10.25) < 0.01
|
||||
assert abs(b0.low - 10.08) < 0.01
|
||||
assert abs(b0.close - 10.12) < 0.01
|
||||
assert b0.vol > 0
|
||||
|
||||
# OHLC sanity: high ≥ open,close,low; low ≤ open,close
|
||||
for bar in bars:
|
||||
assert bar.high >= bar.open - 0.001
|
||||
assert bar.high >= bar.close - 0.001
|
||||
assert bar.low <= bar.open + 0.001
|
||||
assert bar.low <= bar.close + 0.001
|
||||
assert bar.vol > 0
|
||||
assert len(bar._raw) > 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# security_quotes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_security_quotes_parse():
|
||||
from xmtdx.commands.security_quotes import GetSecurityQuotesCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("security_quotes")
|
||||
cmd = GetSecurityQuotesCmd([(Market.SH, "600000")])
|
||||
quotes = cmd.parse_response(body)
|
||||
|
||||
assert len(quotes) == 1
|
||||
|
||||
q = quotes[0]
|
||||
assert q.code == "600000"
|
||||
assert abs(q.pre_close - 9.93) < 0.01
|
||||
|
||||
# unknown fields are captured (not discarded)
|
||||
assert hasattr(q, "unknown_2")
|
||||
assert hasattr(q, "unknown_3")
|
||||
assert hasattr(q, "unknown_5")
|
||||
assert hasattr(q, "unknown_6")
|
||||
assert hasattr(q, "unknown_7")
|
||||
assert hasattr(q, "unknown_8")
|
||||
assert hasattr(q, "rise_speed")
|
||||
assert len(q._raw) > 0
|
||||
|
||||
# fixed values from frozen fixture
|
||||
assert q.unknown_2 == -1
|
||||
assert q.unknown_3 == 22694
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# minute_time
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_minute_time_parse():
|
||||
from xmtdx.commands.minute_time import GetMinuteTimeDataCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("minute_time")
|
||||
cmd = GetMinuteTimeDataCmd(Market.SH, "600000")
|
||||
bars = cmd.parse_response(body)
|
||||
|
||||
assert len(bars) == 240
|
||||
|
||||
b0 = bars[0]
|
||||
assert isinstance(b0.price, float)
|
||||
assert isinstance(b0.vol, int)
|
||||
# Bug #5 fix: unknown_1 is preserved, not discarded
|
||||
assert hasattr(b0, "unknown_1")
|
||||
assert isinstance(b0.unknown_1, int)
|
||||
assert len(b0._raw) > 0
|
||||
|
||||
# fixed values
|
||||
assert abs(b0.price - 0.01) < 0.001
|
||||
assert b0.vol == 48
|
||||
assert b0.unknown_1 == 54
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# history_minute_time
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_history_minute_time_parse():
|
||||
from xmtdx.commands.minute_time import GetHistoryMinuteTimeDataCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("history_minute_time")
|
||||
cmd = GetHistoryMinuteTimeDataCmd(Market.SH, "600000", 20250108)
|
||||
bars = cmd.parse_response(body)
|
||||
|
||||
assert len(bars) == 240
|
||||
|
||||
b0 = bars[0]
|
||||
assert abs(b0.price - 10.29) < 0.01
|
||||
assert b0.vol == 10044
|
||||
assert hasattr(b0, "unknown_1")
|
||||
assert len(b0._raw) > 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# transaction (current day)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_transaction_parse():
|
||||
from xmtdx.commands.transaction import GetTransactionDataCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("transaction")
|
||||
cmd = GetTransactionDataCmd(Market.SH, "600000", 0, 10)
|
||||
recs = cmd.parse_response(body)
|
||||
|
||||
assert len(recs) == 10
|
||||
|
||||
r0 = recs[0]
|
||||
assert r0.hour == 14
|
||||
assert r0.minute == 59
|
||||
assert abs(r0.price - 9.9) < 0.01
|
||||
assert r0.vol == 0
|
||||
|
||||
# Bug #4 fix: unknown_last captured
|
||||
assert hasattr(r0, "unknown_last")
|
||||
assert len(r0._raw) > 0
|
||||
|
||||
# buyorsell: 0=buy, 1=sell, 2=neutral, 8=auction — field is an int
|
||||
for r in recs:
|
||||
assert isinstance(r.buyorsell, int)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# history_transaction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_history_transaction_parse():
|
||||
from xmtdx.commands.transaction import GetHistoryTransactionDataCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("history_transaction")
|
||||
cmd = GetHistoryTransactionDataCmd(Market.SH, "600000", 20250108, 0, 10)
|
||||
recs = cmd.parse_response(body)
|
||||
|
||||
assert len(recs) == 10
|
||||
|
||||
r0 = recs[0]
|
||||
assert r0.hour == 14
|
||||
assert r0.minute == 56
|
||||
assert abs(r0.price - 10.3) < 0.01
|
||||
assert r0.vol == 50
|
||||
|
||||
assert hasattr(r0, "unknown_last")
|
||||
assert len(r0._raw) > 0
|
||||
|
||||
for r in recs:
|
||||
assert isinstance(r.buyorsell, int)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# xdxr_info
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_xdxr_info_parse():
|
||||
from xmtdx.commands.xdxr_info import GetXdxrInfoCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("xdxr_info")
|
||||
cmd = GetXdxrInfoCmd(Market.SH, "600000")
|
||||
recs = cmd.parse_response(body)
|
||||
|
||||
assert len(recs) == 87
|
||||
|
||||
r0 = recs[0]
|
||||
assert r0.year == 1999
|
||||
assert r0.month == 11
|
||||
assert r0.day == 10
|
||||
assert r0.category == 5
|
||||
|
||||
# Bug #1 fix: each record has a unique date (not all reading from body[:7])
|
||||
dates = {(r.year, r.month, r.day) for r in recs}
|
||||
assert len(dates) > 1, "All records have the same date — Bug #1 not fixed!"
|
||||
|
||||
assert all(len(r._raw) > 0 for r in recs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# finance_info
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_finance_info_parse():
|
||||
from xmtdx.commands.finance_info import GetFinanceInfoCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("finance_info")
|
||||
cmd = GetFinanceInfoCmd(Market.SH, "600000")
|
||||
info = cmd.parse_response(body)
|
||||
|
||||
# Check key fields are present and reasonable
|
||||
assert info.liutong_guben > 0
|
||||
assert info.zong_guben > 0
|
||||
assert info.meigujing_zichan > 0
|
||||
|
||||
# Fixed values from frozen fixture
|
||||
assert abs(info.liutong_guben - 33305837500.0) < 1e6
|
||||
assert abs(info.zong_guben - 33305837500.0) < 1e6
|
||||
assert abs(info.meigujing_zichan - 22.13) < 0.1
|
||||
|
||||
assert len(info._raw) > 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# company_info_category
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_company_info_category_parse():
|
||||
from xmtdx.commands.company_info import GetCompanyInfoCategoryCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("company_info_category")
|
||||
cmd = GetCompanyInfoCategoryCmd(Market.SH, "600000")
|
||||
cats = cmd.parse_response(body)
|
||||
|
||||
assert len(cats) == 16
|
||||
|
||||
c0 = cats[0]
|
||||
assert c0.filename == "600000.txt"
|
||||
assert c0.start == 0
|
||||
assert c0.length == 11426
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# company_info_content
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_company_info_content_parse():
|
||||
from xmtdx.commands.company_info import GetCompanyInfoContentCmd
|
||||
from xmtdx.models.enums import Market
|
||||
|
||||
body = load_hex("company_info_content")
|
||||
cmd = GetCompanyInfoContentCmd(Market.SH, "600000", "600000.txt", 0, 11426)
|
||||
text = cmd.parse_response(body)
|
||||
|
||||
assert isinstance(text, str)
|
||||
assert len(text) == 8070
|
||||
assert "600000" in text
|
||||
assert "浦发银行" in text
|
||||
Reference in New Issue
Block a user