87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
"""委托簿:在途状态、方向锁、以及撤单范围。"""
|
||
|
||
import unittest
|
||
from datetime import datetime, timedelta
|
||
from unittest.mock import Mock
|
||
|
||
from libs.order import BUSY_STATUSES, OrderBook, TRACKED_STATUSES
|
||
from sdk import OrderItem
|
||
|
||
|
||
def order(index, remark, status=50, side=23, age_minutes=30):
|
||
stamp = datetime.now() - timedelta(minutes=age_minutes)
|
||
return OrderItem(stock_code=f'60000{index}.SH', order_sys_id=f'sys{index}',
|
||
remark=remark, order_status=status, offset_flag=side,
|
||
insert_date=stamp.strftime('%Y%m%d'),
|
||
insert_time=stamp.strftime('%H%M%S'))
|
||
|
||
|
||
def cancelled(client):
|
||
return [call.args[0] for call in client.cancel_by_id.call_args_list]
|
||
|
||
|
||
class CancelScopeTests(unittest.TestCase):
|
||
"""撤单必须限本策略前缀;防重则继续看全账户在途。"""
|
||
|
||
def test_zt_prefix_cancels_only_its_own_orders(self):
|
||
orders = [order(0, 'zt-base-own'), order(1, 'zt-SELL-own'),
|
||
order(2, 'zt-entry-own'), order(3, 'IPO-new'),
|
||
order(4, ''), order(5, 'TREN-BUY-other')]
|
||
client = Mock()
|
||
book = OrderBook()
|
||
book.refresh(client, orders, cancel_prefix='zt-')
|
||
self.assertEqual(cancelled(client), ['sys0', 'sys1', 'sys2'])
|
||
self.assertEqual(book.data, orders) # 撤单后仍保留在途锁
|
||
self.assertTrue(all(book.busy(o.stock_code, 'BUY') for o in orders))
|
||
|
||
def test_default_prefix_still_cancels_every_non_ipo_order(self):
|
||
orders = [order(0, 'zt-base-own'), order(1, 'TREN-BUY-other'),
|
||
order(2, 'IPO-new'), order(3, '')]
|
||
client = Mock()
|
||
OrderBook().refresh(client, orders)
|
||
self.assertEqual(cancelled(client), ['sys0', 'sys1', 'sys3'])
|
||
|
||
def test_fresh_and_unreportable_orders_are_never_cancelled(self):
|
||
# 48(未报)在跟踪集合内但不可撤;刚提交的委托也不撤。
|
||
orders = [order(0, 'zt-entry-fresh', age_minutes=0),
|
||
order(1, 'zt-entry-filled', status=56),
|
||
order(2, 'zt-entry-unreported', status=48)]
|
||
client = Mock()
|
||
book = OrderBook()
|
||
book.refresh(client, orders, cancel_prefix='zt-')
|
||
client.cancel_by_id.assert_not_called()
|
||
self.assertEqual(book.data, orders)
|
||
|
||
|
||
class BusyLockTests(unittest.TestCase):
|
||
def test_only_busy_statuses_lock_a_direction(self):
|
||
book = OrderBook()
|
||
client = Mock()
|
||
book.refresh(client, [order(0, 'zt-entry-a', status=50)], cancel_prefix='zt-')
|
||
self.assertTrue(book.busy('600000.SH', 'BUY'))
|
||
book.refresh(client, [order(1, 'zt-entry-b', status=56)], cancel_prefix='zt-')
|
||
self.assertFalse(book.busy('600001.SH', 'BUY'))
|
||
|
||
def test_busy_and_tracked_status_sets_are_disjoint_as_designed(self):
|
||
self.assertNotIn('56', BUSY_STATUSES)
|
||
self.assertTrue(BUSY_STATUSES <= TRACKED_STATUSES)
|
||
self.assertIn('56', TRACKED_STATUSES)
|
||
|
||
def test_place_marks_the_direction_busy_before_submitting(self):
|
||
book = OrderBook()
|
||
book.busy_cache.set('BUY-600000.SH', True, timeout=180)
|
||
self.assertTrue(book.busy('600000.SH', 'BUY'))
|
||
self.assertFalse(book.busy('600000.SH', 'SELL'))
|
||
|
||
def test_unknown_offset_flag_never_places_an_order(self):
|
||
from libs.order import PlaceOrderRequest
|
||
book = OrderBook()
|
||
client = Mock()
|
||
self.assertFalse(book.place(client, PlaceOrderRequest(99, '600000.SH', 100,
|
||
'zt-x', 'zt')))
|
||
client.passorder.assert_not_called()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
unittest.main()
|