tradingSystem/scripts/test_batch39_units.py

124 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""本地:python scripts/unit_test_guard.py scripts/test_batch39_units.py。禁止联网。"""
import unit_test_guard # noqa: F401
import json
import unittest
from unittest.mock import patch
from test_wiring import install_fakes
from app.services import ledger_service as ls
from app.repo import downstream_repo as ds, qmt_repo as q
CODE = '600000.SH'
IID = 'INS_R_D01'
class ReplayOwnershipTest(unittest.TestCase):
def setUp(self):
self.fake = install_fakes(prices={CODE: 10.0})
self.fake.insert_instruction(instruction_id='INS_R', origin_type='plan', origin_id='P1',
ts_code=CODE, action='OPEN', side='buy', qty=100, status='DISPATCHED')
self.fake.instructions['INS_R']['created_at'] = '2026-10-15 09:00:00'
self.fake.qmt.enqueue_order(instruction_id=IID, parent_id='INS_R', ts_code=CODE,
side='buy', qty=100, limit_price=10, valid_until=0)
self.fake.qmt.orders[IID]['broker_order_id'] = 'BROKER-1'
self.fills = [dict(order_id=IID, ts_code=CODE, side='buy', qty=100, price=11,
done_time='2026-10-15 10:00:00', raw={'qmt_order_id': 'BROKER-1'})]
self.patcher = patch.object(ds, 'fetch_filled_orders', side_effect=lambda **kw: self.fills)
self.patcher.start()
self.addCleanup(self.patcher.stop)
def trade(self, seq=1, qty=100, side='buy'):
self.fake.qmt.put_trade(seq=seq, instruction_id=IID, ts_code=CODE,
side=side, qty=qty, price=10, fee=0)
def qty(self):
return self.fake.positions.get(CODE, {}).get('total_qty', 0)
def test_buy_in_both_sources_is_booked_once(self):
self.trade()
result = ls.replay_fills()
self.assertTrue(result['ok'], result)
self.assertEqual(self.qty(), 100)
self.assertEqual(self.fake.instructions['INS_R']['exec_qty'], 100)
self.assertEqual(ls.replay_fills()['actions'], 0)
self.assertEqual(self.qty(), 100)
def test_sell_in_both_sources_is_booked_once(self):
ls._apply_action(dict(kind='BUY', ts_code=CODE, qty=300, price=9))
self.fake.qmt.orders[IID]['side'] = 'sell'
self.fake.instructions['INS_R'].update(side='sell', action='EXIT')
self.fills[0]['side'] = 'sell'
self.trade(side='sell')
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 200)
def test_table_before_ws_waits_for_exact_trades(self):
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 0)
self.trade()
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 100)
def test_partial_ws_then_delayed_remainder(self):
self.trade(qty=73)
ls.replay_fills()
self.assertEqual(self.qty(), 73)
self.trade(seq=2, qty=27)
ls.replay_fills()
self.assertEqual(self.qty(), 100)
def test_external_fill_still_books(self):
self.trade()
self.fills.append(dict(self.fills[0], order_id='MANUAL-1', qty=200, raw={}))
result = ls.replay_fills()
self.assertTrue(result['ok'], result)
self.assertEqual(self.qty(), 300)
def test_broker_identity_works_when_table_id_differs(self):
self.fills[0]['order_id'] = 'DOWNSTREAM-1'
self.trade()
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 100)
def test_lookup_failure_leaves_fill_retryable(self):
with patch.object(q, 'orders_for_fill', side_effect=RuntimeError('unavailable')):
result = ls.replay_fills()
self.assertFalse(result['ok'])
self.assertEqual(self.qty(), 0)
self.assertNotIn(IID, json.loads(self.fake.params[ls.CURSOR_KEY])['seen'])
self.trade()
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 100)
def test_conflicting_code_does_not_fall_back_to_external(self):
self.fills[0]['ts_code'] = '600001.SH'
result = ls.replay_fills()
self.assertFalse(result['ok'])
self.assertFalse(self.fake.lots)
self.assertNotIn(IID, json.loads(self.fake.params[ls.CURSOR_KEY])['seen'])
def test_conflicting_broker_identity_is_held(self):
self.fills[0]['raw']['qmt_order_id'] = 'DIFFERENT'
self.assertFalse(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 0)
def test_smoke_table_mirror_cannot_reenter_book(self):
self.fake.qmt.orders[IID]['parent_id'] = 'SMOKE_1'
self.trade()
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 0)
def test_ownership_survives_dispatch_mode_change(self):
self.fake.params['PMS_DISPATCH_MODE'] = 'shadow'
self.trade()
self.assertTrue(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 100)
def test_two_owners_fail_closed(self):
self.fake.qmt.orders['OTHER'] = dict(self.fake.qmt.orders[IID], instruction_id='OTHER')
self.assertFalse(ls.replay_fills()['ok'])
self.assertEqual(self.qty(), 0)
if __name__ == '__main__':
unittest.main()