feat: add Quant OS A-share baseline

This commit is contained in:
2026-07-26 12:54:04 +08:00
commit 48c5f64bbd
98 changed files with 31874 additions and 0 deletions
+859
View File
@@ -0,0 +1,859 @@
import datetime as dt
import sys
import threading
import unittest
from pathlib import Path
from types import SimpleNamespace
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "src"))
from adapters.xttrader_live import (
IdempotencyConflict,
LiveOrderBlocked,
NotConnectedError,
ReconnectFailed,
XtTraderLiveAdapter,
map_order_status,
)
class Constants:
STOCK_BUY = 23
STOCK_SELL = 24
FIX_PRICE = 11
class FakeTrader:
def __init__(self, connect_code=0, orders=None):
self.connect_code = connect_code
self.callback = None
self.started = False
self.stopped = False
self.order_calls = []
self.cancel_calls = []
self.orders = list(orders or [])
def register_callback(self, callback):
self.callback = callback
def start(self):
self.started = True
def connect(self):
return self.connect_code
def subscribe(self, account):
self.account = account
return 0
def stop(self):
self.stopped = True
def query_stock_asset(self, account):
del account
return SimpleNamespace(
account_id="test", cash=10_000, market_value=20_000, total_asset=30_000
)
def query_stock_orders(self, account, cancelable_only=False):
del account, cancelable_only
return list(self.orders)
def query_stock_positions(self, account):
del account
return [
SimpleNamespace(
account_id="test",
stock_code="600000.SH",
volume=1000,
can_use_volume=700,
market_value=10_000,
open_price=10,
)
]
def query_stock_trades(self, account):
del account
return []
def order_stock_async(self, *args):
self.order_calls.append(args)
return 101
def cancel_order_stock(self, account, order_id):
self.cancel_calls.append((account, order_id))
return 0
def passing_live_decision(**overrides):
decision = {
"authorization_id": "auth-test-001",
"snapshot_id": "snapshot-test-001",
"as_of": dt.datetime.now(dt.timezone.utc).isoformat(),
"reconciliation_safe": True,
"account_allowed": True,
"within_notional_limit": True,
"within_order_rate_limit": True,
"operator_approved": True,
"kill_switch_ready": True,
"compliance_approved": True,
}
decision.update(overrides)
return decision
def adapter_for(trader, allow=False, live_guard=None):
if allow is True and live_guard is None:
live_guard = lambda intent: passing_live_decision()
return XtTraderLiveAdapter(
trader_factory=lambda: trader,
account=SimpleNamespace(account_id="test"),
constants=Constants,
allow_live_orders=allow,
live_guard=live_guard,
)
class XtTraderLiveAdapterTest(unittest.TestCase):
def test_real_status_mapping_uses_domain_spelling(self):
self.assertEqual(map_order_status(48), "NEW")
self.assertEqual(map_order_status(50), "ACCEPTED")
self.assertEqual(map_order_status(51), "CANCEL_PENDING")
self.assertEqual(map_order_status(54), "CANCELLED")
self.assertEqual(map_order_status(55), "PARTIALLY_FILLED")
self.assertEqual(map_order_status(56), "FILLED")
self.assertEqual(map_order_status(57), "REJECTED")
self.assertEqual(map_order_status(999), "UNKNOWN")
def test_default_is_dry_run_and_idempotent_without_broker_call(self):
trader = FakeTrader()
adapter = adapter_for(trader)
first = adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
)
second = adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
)
self.assertEqual(first, second)
self.assertEqual(first["state"], "DRY_RUN")
self.assertEqual(trader.order_calls, [])
def test_allow_live_orders_requires_a_real_bool(self):
trader = FakeTrader()
for value in ("False", "true", 1, None):
with self.subTest(value=value):
with self.assertRaisesRegex(TypeError, "must be a bool"):
adapter_for(trader, allow=value)
self.assertEqual(trader.order_calls, [])
def test_live_mode_requires_callable_guard_but_dry_run_does_not(self):
trader = FakeTrader()
dry_run = XtTraderLiveAdapter(
trader_factory=lambda: trader,
account=SimpleNamespace(account_id="test"),
constants=Constants,
)
self.assertFalse(dry_run.allow_live_orders)
with self.assertRaisesRegex(TypeError, "requires a callable live_guard"):
XtTraderLiveAdapter(
trader_factory=lambda: trader,
account=SimpleNamespace(account_id="test"),
constants=Constants,
allow_live_orders=True,
)
with self.assertRaisesRegex(TypeError, "live_guard must be callable"):
XtTraderLiveAdapter(
trader_factory=lambda: trader,
account=SimpleNamespace(account_id="test"),
constants=Constants,
allow_live_orders=True,
live_guard="allow",
)
def test_idempotency_key_conflict_is_rejected(self):
adapter = adapter_for(FakeTrader())
adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
)
with self.assertRaises(IdempotencyConflict):
adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=200,
limit_price=10.5,
)
def test_non_finite_live_price_is_rejected_before_broker_call(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
for value in (float("nan"), float("inf"), float("-inf")):
with self.subTest(value=value):
with self.assertRaisesRegex(ValueError, "finite and positive"):
adapter.submit_order(
client_order_id="bad-price",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=value,
confirm_live=True,
)
self.assertEqual(trader.order_calls, [])
def test_limit_price_rejects_bool_non_real_and_non_positive(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
for value in (True, False, "10.5", None, 10 + 0j, 0, -1):
with self.subTest(value=value):
with self.assertRaisesRegex(ValueError, "limit_price"):
adapter.submit_order(
client_order_id="strict-price",
symbol="600000.SH",
side="SELL",
quantity=100,
limit_price=value,
confirm_live=True,
)
self.assertEqual(trader.order_calls, [])
def test_submit_confirm_live_requires_a_real_bool(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
for value in ("False", "true", 1, None):
with self.subTest(value=value):
with self.assertRaisesRegex(TypeError, "must be a bool"):
adapter.submit_order(
client_order_id="strict-confirm",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=value,
)
self.assertEqual(trader.order_calls, [])
def test_quantity_rejects_bool_fraction_float_and_non_positive(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
for value in (True, False, 100.0, 100.5, "100", None, 0, -100):
with self.subTest(value=value):
with self.assertRaisesRegex(ValueError, "positive integer"):
adapter.submit_order(
client_order_id="strict-quantity",
symbol="600000.SH",
side="BUY",
quantity=value,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(trader.order_calls, [])
def test_real_order_requires_double_opt_in_and_remark_round_trip(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
with self.assertRaises(LiveOrderBlocked):
adapter.submit_order(
client_order_id="12345678901234567890",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
)
result = adapter.submit_order(
client_order_id="12345678901234567890",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(result["state"], "NEW")
self.assertEqual(len(trader.order_calls), 1)
remark = trader.order_calls[0][-1]
self.assertEqual(remark, "q60:12345678901234567890")
self.assertLessEqual(len(remark), 24)
self.assertEqual(
result["live_policy"]["authorization_id"], "auth-test-001"
)
def test_live_guard_receives_normalized_submit_and_cancel_intents(self):
intents = []
def guard(intent):
intents.append(intent)
return passing_live_decision(
authorization_id=f"auth-{len(intents)}"
)
trader = FakeTrader()
adapter = adapter_for(trader, allow=True, live_guard=guard)
adapter.connect()
result = adapter.submit_order(
client_order_id="guard-intent",
symbol="600000.SH",
side="buy",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
adapter.cancel_order(88, confirm_live=True)
self.assertEqual(
intents,
[
{
"action": "SUBMIT_ORDER",
"account_id": "test",
"client_order_id": "guard-intent",
"symbol": "600000.XSHG",
"side": "BUY",
"quantity": 100,
"limit_price": 10.5,
"notional": 1050.0,
"strategy_name": "quant60",
},
{
"action": "CANCEL_ORDER",
"account_id": "test",
"broker_order_id": 88,
"strategy_name": "quant60",
},
],
)
self.assertEqual(result["live_policy"]["authorization_id"], "auth-1")
self.assertEqual(len(trader.order_calls), 1)
self.assertEqual(len(trader.cancel_calls), 1)
audit = adapter.live_authorization_audit
self.assertEqual(len(audit), 2)
self.assertEqual(audit[0]["decision"]["account_id"], "test")
self.assertEqual(audit[1]["intent"]["action"], "CANCEL_ORDER")
self.assertEqual(audit[1]["outcome"], "BROKER_ACCEPTED")
def test_live_guard_is_bound_to_a_non_empty_account_id(self):
trader = FakeTrader()
calls = []
adapter = XtTraderLiveAdapter(
trader_factory=lambda: trader,
account=SimpleNamespace(account_id=" "),
constants=Constants,
allow_live_orders=True,
live_guard=lambda intent: (
calls.append(intent) or passing_live_decision()
),
)
with self.assertRaisesRegex(NotConnectedError, "account_id"):
adapter.connect()
self.assertEqual(calls, [])
self.assertEqual(trader.order_calls, [])
def test_live_guard_missing_false_non_bool_or_exception_blocks_submit(self):
def raising_guard(intent):
del intent
raise RuntimeError("policy backend unavailable")
cases = [
(
"missing",
lambda intent: {
key: value
for key, value in passing_live_decision().items()
if key != "compliance_approved"
},
),
(
"false",
lambda intent: passing_live_decision(
reconciliation_safe=False
),
),
(
"non-bool",
lambda intent: passing_live_decision(account_allowed=1),
),
(
"empty-id",
lambda intent: passing_live_decision(
authorization_id=" "
),
),
("exception", raising_guard),
]
for name, guard in cases:
with self.subTest(name=name):
trader = FakeTrader()
adapter = adapter_for(
trader, allow=True, live_guard=guard
)
adapter.connect()
with self.assertRaises(LiveOrderBlocked):
adapter.submit_order(
client_order_id=f"blocked-{name}",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(trader.order_calls, [])
def test_live_guard_rejects_stale_naive_and_future_decisions(self):
now = dt.datetime.now(dt.timezone.utc)
cases = {
"stale": (now - dt.timedelta(seconds=61)).isoformat(),
"naive": now.replace(tzinfo=None).isoformat(),
"future": (now + dt.timedelta(seconds=6)).isoformat(),
}
for name, as_of in cases.items():
with self.subTest(name=name):
trader = FakeTrader()
adapter = adapter_for(
trader,
allow=True,
live_guard=lambda intent, value=as_of: (
passing_live_decision(as_of=value)
),
)
adapter.connect()
with self.assertRaises(LiveOrderBlocked):
adapter.submit_order(
client_order_id=f"clock-{name}",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(trader.order_calls, [])
def test_live_guard_failure_blocks_cancel_before_broker(self):
trader = FakeTrader()
adapter = adapter_for(
trader,
allow=True,
live_guard=lambda intent: passing_live_decision(
kill_switch_ready=False
),
)
adapter.connect()
with self.assertRaisesRegex(
LiveOrderBlocked, "kill_switch_ready"
):
adapter.cancel_order(88, confirm_live=True)
self.assertEqual(trader.cancel_calls, [])
def test_concurrent_same_client_id_submits_once(self):
class BlockingTrader(FakeTrader):
def __init__(self):
super().__init__()
self.submit_entered = threading.Event()
self.release_submit = threading.Event()
def order_stock_async(self, *args):
self.order_calls.append(args)
self.submit_entered.set()
if not self.release_submit.wait(2):
raise AssertionError("test did not release broker submit")
return 101
trader = BlockingTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
results = []
failures = []
def submit():
try:
results.append(
adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
)
except BaseException as exc:
failures.append(exc)
first = threading.Thread(target=submit)
first.start()
self.assertTrue(trader.submit_entered.wait(1))
# The broker boundary is inside the idempotency critical section.
acquired = adapter._lock.acquire(blocking=False)
if acquired:
adapter._lock.release()
self.assertFalse(acquired)
second = threading.Thread(target=submit)
second.start()
trader.release_submit.set()
first.join(2)
second.join(2)
self.assertFalse(first.is_alive())
self.assertFalse(second.is_alive())
self.assertEqual(failures, [])
self.assertEqual(len(trader.order_calls), 1)
self.assertEqual(len(results), 2)
self.assertEqual(results[0], results[1])
def test_synchronous_order_error_matches_pre_submit_reservation(self):
class SynchronousErrorTrader(FakeTrader):
def order_stock_async(self, *args):
self.order_calls.append(args)
self.callback.on_order_error(
SimpleNamespace(
order_id=0,
order_remark=args[-1],
error_id=42,
error_msg="synchronous rejection",
)
)
return 101
trader = SynchronousErrorTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
result = adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(result["state"], "REJECTED")
self.assertEqual(result["error_id"], 42)
self.assertEqual(len(trader.order_calls), 1)
def test_synchronous_order_error_by_seq_is_drained_from_orphans(self):
class SynchronousErrorTrader(FakeTrader):
def order_stock_async(self, *args):
self.order_calls.append(args)
self.callback.on_order_error(
SimpleNamespace(
order_id=None,
order_remark="",
seq=101,
error_id=43,
error_msg="fast seq rejection",
)
)
return 101
trader = SynchronousErrorTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
result = adapter.submit_order(
client_order_id="decision-002",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(result["state"], "REJECTED")
self.assertEqual(result["error_id"], 43)
self.assertEqual(adapter._orphan_order_errors, [])
def test_malformed_async_seq_never_leaves_submitting_reservation(self):
class MalformedSeqTrader(FakeTrader):
def __init__(self, seq):
super().__init__()
self.seq = seq
def order_stock_async(self, *args):
self.order_calls.append(args)
return self.seq
for index, seq in enumerate(("bad", 1.5, True, object())):
with self.subTest(seq=repr(seq)):
trader = MalformedSeqTrader(seq)
adapter = adapter_for(trader, allow=True)
adapter.connect()
client_id = f"bad-seq-{index}"
with self.assertRaisesRegex(
Exception, "malformed async request sequence"
):
adapter.submit_order(
client_order_id=client_id,
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
record = adapter.journal.get(client_id)
self.assertEqual(record["state"], "UNKNOWN")
self.assertNotEqual(record["state"], "SUBMITTING")
self.assertEqual(len(trader.order_calls), 1)
def test_query_normalizes_position_and_disconnect_fails_fast(self):
trader = FakeTrader()
adapter = adapter_for(trader)
adapter.connect()
position = adapter.query_positions()[0]
self.assertEqual(position["sellable"], 700)
self.assertEqual(position["symbol"], "600000.XSHG")
trader.callback.on_disconnected()
with self.assertRaises(NotConnectedError):
adapter.query_orders()
def test_connect_recovers_idempotency_from_broker_remark(self):
broker_order = SimpleNamespace(
account_id="test",
order_id=88,
order_sysid="sys-88",
order_remark="q60:decision-001",
stock_code="600000.SH",
order_type=23,
order_volume=100,
traded_volume=0,
price=10.5,
traded_price=0,
order_status=50,
status_msg="",
)
trader = FakeTrader(orders=[broker_order])
adapter = adapter_for(trader, allow=True)
adapter.connect()
recovered = adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
self.assertEqual(recovered["broker_order_id"], 88)
self.assertEqual(recovered["state"], "ACCEPTED")
self.assertEqual(trader.order_calls, [])
def test_broker_payload_conflict_for_same_remark_fails_closed(self):
orders = [
SimpleNamespace(
account_id="test",
order_id=88 + index,
order_sysid="sys",
order_remark="q60:decision-001",
stock_code="600000.SH",
order_type=23,
order_volume=quantity,
traded_volume=0,
price=10.5,
traded_price=0,
order_status=50,
status_msg="",
)
for index, quantity in enumerate((100, 200))
]
adapter = adapter_for(FakeTrader(orders=orders), allow=True)
with self.assertRaises(IdempotencyConflict):
adapter.connect()
self.assertNotEqual(adapter.state, "READY")
def test_order_error_updates_matching_journal_to_rejected(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
adapter.submit_order(
client_order_id="decision-001",
symbol="600000.SH",
side="BUY",
quantity=100,
limit_price=10.5,
confirm_live=True,
)
trader.callback.on_order_error(
SimpleNamespace(
order_id=999,
order_remark="q60:decision-001",
error_id=42,
error_msg="rejected",
)
)
record = adapter.journal.get("decision-001")
self.assertEqual(record["state"], "REJECTED")
self.assertEqual(record["error_id"], 42)
def test_cancel_none_is_not_reported_as_success(self):
trader = FakeTrader()
trader.cancel_order_stock = lambda account, order_id: None
adapter = adapter_for(trader, allow=True)
adapter.connect()
with self.assertRaisesRegex(Exception, "cancel failed"):
adapter.cancel_order(88, confirm_live=True)
def test_cancel_confirm_live_requires_a_real_bool(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
for value in ("False", "true", 1, None):
with self.subTest(value=value):
with self.assertRaisesRegex(TypeError, "must be a bool"):
adapter.cancel_order(88, confirm_live=value)
self.assertEqual(trader.cancel_calls, [])
def test_cancel_order_id_requires_exact_positive_integer(self):
trader = FakeTrader()
adapter = adapter_for(trader, allow=True)
adapter.connect()
for value in (True, False, 88.0, 88.9, "88", None, 0, -1):
with self.subTest(value=value):
with self.assertRaisesRegex(
ValueError, "positive integer"
):
adapter.cancel_order(value, confirm_live=True)
self.assertEqual(trader.cancel_calls, [])
def test_stale_order_callback_cannot_regress_filled_terminal_state(self):
filled = SimpleNamespace(
account_id="test",
order_id=88,
order_sysid="sys-88",
order_remark="q60:decision-001",
stock_code="600000.SH",
order_type=23,
order_volume=100,
traded_volume=100,
price=10.5,
traded_price=10.5,
order_status=56,
status_msg="filled",
)
stale = SimpleNamespace(
account_id="test",
order_id=88,
order_sysid="sys-88",
order_remark="q60:decision-001",
stock_code="600000.SH",
order_type=23,
order_volume=100,
traded_volume=0,
price=10.5,
traded_price=0,
order_status=50,
status_msg="stale accepted",
)
trader = FakeTrader(orders=[filled])
adapter = adapter_for(trader)
adapter.connect()
# An exact duplicate is harmless and idempotent.
trader.callback.on_stock_order(filled)
with self.assertRaisesRegex(
IdempotencyConflict, "cumulative fill regressed"
):
trader.callback.on_stock_order(stale)
record = adapter.journal.get("decision-001")
self.assertEqual(adapter.state, "DEGRADED")
self.assertEqual(record["state"], "FILLED")
self.assertEqual(record["filled_quantity"], 100)
self.assertEqual(adapter._orders[88]["state"], "FILLED")
def test_configured_account_anchors_every_broker_query(self):
class WrongAccountTrader(FakeTrader):
def query_stock_asset(self, account):
del account
return SimpleNamespace(
account_id="wrong",
cash=10_000,
market_value=20_000,
total_asset=30_000,
)
trader = WrongAccountTrader()
adapter = adapter_for(trader)
with self.assertRaisesRegex(
NotConnectedError,
"configured account",
):
adapter.connect()
self.assertEqual(adapter.state, "FAILED")
self.assertTrue(trader.stopped)
def test_trade_query_preserves_account_side_and_stable_trade_id(self):
class TradeTrader(FakeTrader):
def query_stock_trades(self, account):
del account
return [
SimpleNamespace(
account_id="test",
traded_id="trade-1",
order_id=88,
stock_code="600000.SH",
order_type=23,
traded_volume=100,
traded_price=10.5,
traded_amount=1050,
)
]
adapter = adapter_for(TradeTrader())
adapter.connect()
self.assertEqual(
adapter.query_trades(),
[
{
"account_id": "test",
"trade_id": "trade-1",
"broker_order_id": 88,
"symbol": "600000.XSHG",
"side": "BUY",
"quantity": 100,
"price": 10.5,
"amount": 1050.0,
}
],
)
def test_reconnect_uses_fresh_instance_and_exhausts_bounded_attempts(self):
created = []
def factory():
trader = FakeTrader(connect_code=9)
created.append(trader)
return trader
adapter = XtTraderLiveAdapter(
trader_factory=factory,
account=SimpleNamespace(account_id="test"),
constants=Constants,
)
with self.assertRaises(ReconnectFailed):
adapter.reconnect(attempts=2)
self.assertEqual(len(created), 2)
self.assertEqual(adapter.state, "FAILED")
def test_bj_is_explicitly_outside_v1_live_adapter(self):
adapter = adapter_for(FakeTrader())
with self.assertRaises(ValueError):
adapter.submit_order(
client_order_id="bj",
symbol="430001.BJ",
side="BUY",
quantity=100,
limit_price=10,
)
if __name__ == "__main__":
unittest.main()