feat libs,sdk,trend

This commit is contained in:
2026-09-07 14:04:26 +08:00
parent d37f9edefc
commit e320de3241
30 changed files with 515 additions and 507 deletions

View File

@@ -2,6 +2,7 @@ import sqlite3
import tempfile
import unittest
from contextlib import closing
from dataclasses import asdict, fields
from pathlib import Path
from datetime import datetime
from unittest.mock import patch
@@ -23,18 +24,12 @@ class OrderBookTests(unittest.TestCase):
def deal(self, kind, sys_order_id, qty, price, date='2026-09-01'):
prefix = {'base': 'zt-base-', 'sell': 'zt-t-sell-', 'buy': 'zt-t-buy-'}[kind]
return DealItem.from_trade_detail({
'm_strOrderSysID': sys_order_id,
'm_strInstrumentID': '600000', 'm_strExchangeID': 'SH',
'm_strInstrumentName': 'Test stock',
'm_nOffsetFlag': '24' if kind == 'sell' else '23',
'm_nOrderStatus': '56', 'm_nVolumeTotal': '0',
'm_nVolumeTraded': str(qty), 'm_nOrderTime': '100000',
'm_strInsertDate': date, 'm_strInsertTime': '10:00:00',
'm_strRemark': prefix + 'order1|zt',
'm_dPrice': str(price + 1), 'm_dTradePrice': str(price),
'm_dTradeAmount': str(qty * price),
})
return DealItem(
order_sys_id=sys_order_id, stock_code='600000.SH',
offset_flag=24 if kind == 'sell' else 23,
volume=qty, price=price, trade_amount=qty * price,
trade_date=date, trade_time='10:00:00', remark=prefix + 'order1|zt',
)
def test_partial_fills_restart_dedup_and_daily_cycle(self):
state = TState(self.path)
@@ -83,10 +78,12 @@ class OrderBookTests(unittest.TestCase):
insert.assert_called_once()
self.assertEqual(len(insert.call_args.args[1]), 2)
self.assertEqual(book.deals_sys_ids, {'d1', 'd2'})
self.assertEqual(book.deals['d1']['insert_date'], '2026-09-01')
self.assertEqual(book.deals['d2']['traded_volume'], 60)
self.assertEqual(book.deals['d1']['trade_date'], '2026-09-01')
self.assertEqual(book.deals['d2']['volume'], 60)
self.assertEqual(book.deals['d2']['order_local_id'], 'zt-base-order1')
book = OrderBook(self.path)
self.assertEqual(book.deals_sys_ids, {'d1', 'd2'})
self.assertEqual(book.deals['d1']['order_local_id'], 'zt-base-order1')
with patch.object(book, '_insert_deals') as insert:
book.sync_deals([first, second])
book.sync_deals([])
@@ -97,25 +94,25 @@ class OrderBookTests(unittest.TestCase):
book = OrderBook(self.path)
first = self.deal('base', 'd1', 100, 10)
invalid = self.deal('base', 'd2', 100, 10)
invalid.side = 'INVALID'
invalid.offset_flag = -1
with self.assertRaises(sqlite3.IntegrityError):
book.sync_deals([first, invalid])
self.assertEqual(book.deals, {})
self.assertEqual(book.deals_sys_ids, set())
self.assertEqual(OrderBook(self.path).deals, {})
invalid.side = 'BUY'
invalid.offset_flag = 23
book.sync_deals([first, invalid])
self.assertEqual(book.deals_sys_ids, {'d1', 'd2'})
def test_load_refreshes_all_caches(self):
book = OrderBook(self.path)
writer = OrderBook(self.path)
writer.save({'600000.SH': {'code': '600000.SH', 'status': READY}}, [])
writer.sync_positions([PositionItem(stock_code='600000.SH', volume=100)])
writer.sync_deals([self.deal('base', 'd1', 100, 10)])
book.load()
self.assertEqual(book.positions['600000.SH']['status'], READY)
self.assertEqual(book.positions['600000.SH']['volume'], 100)
self.assertEqual(book.deals_sys_ids, {'d1'})
self.assertEqual(book.deals['d1']['local_order_id'], 'zt-base-order1')
self.assertEqual(book.deals['d1']['remark'], 'zt-base-order1|zt')
def test_first_start_after_full_sale_keeps_buyback_quantity(self):
state = TState(self.path)
@@ -148,12 +145,17 @@ class OrderBookTests(unittest.TestCase):
tables = {row[0] for row in db.execute("SELECT name FROM sqlite_master WHERE type='table'")}
self.assertEqual(tables, {'positions', 'deals', 'sqlite_sequence'})
columns = {row[1] for row in db.execute('PRAGMA table_info(deals)')}
self.assertFalse({'kind', 'confirmed_date', 'deal_ids', 'deal_id', 'order_id', 'qty', 'filled_qty', 'filled_cost', 'amount', 'trade_date', 'trade_time'} & columns)
self.assertTrue({'sys_order_id', 'local_order_id'} <= columns)
self.assertEqual(columns, {'id', 'order_local_id', *(field.name for field in fields(DealItem))})
indexes = {row[0] for row in db.execute("SELECT name FROM sqlite_master WHERE type='index'")}
self.assertTrue({'idx_positions_code', 'idx_positions_base_order_id',
'idx_positions_added_order_id', 'idx_deals_local_order_id',
'idx_deals_code_date', 'idx_deals_date_time'} <= indexes)
self.assertTrue({'idx_positions_stock_code', 'idx_deals_order_sys_id',
'idx_deals_order_ref', 'idx_deals_stock_code_date', 'idx_deals_date_time'} <= indexes)
for index, expected in (
('idx_deals_order_sys_id', ['order_sys_id']),
('idx_deals_order_ref', ['order_local_id']),
('idx_deals_stock_code_date', ['stock_code']),
('idx_deals_date_time', ['trade_date']),
):
self.assertEqual([row[2] for row in db.execute(f'PRAGMA index_info({index})')], expected)
self.assertNotIn('kind', state.deals[0])
self.assertEqual(TState(self.path).deals, state.deals)
state.deals.append(dict(state.deals[0]))
@@ -168,29 +170,23 @@ class OrderBookTests(unittest.TestCase):
def test_position_columns_defaults_indexes_and_stable_id(self):
store = OrderBook(self.path)
with closing(sqlite3.connect(self.path)) as db:
columns = [row[1] for row in db.execute('PRAGMA table_info(positions)')]
self.assertEqual(columns, [
'id', 'code', 'base_order_id', 'base_qty', 'base_cost',
'added_order_id', 'added_num', 'added_qty', 'added_cost', 'status',
])
columns = {row[1] for row in db.execute('PRAGMA table_info(positions)')}
self.assertEqual(columns, {'id', *(field.name for field in fields(PositionItem))})
indexes = {row[1] for row in db.execute('PRAGMA index_list(positions)')}
self.assertEqual(indexes, {
'idx_positions_code', 'idx_positions_base_order_id', 'idx_positions_added_order_id',
})
store.save({'600000.SH': {'code': '600000.SH', 'status': READY}}, [])
position = store.positions['600000.SH']
first_id = position['id']
self.assertGreater(first_id, 0)
self.assertEqual(position['base_order_id'], '')
self.assertEqual(position['base_qty'], 0)
self.assertEqual(position['added_cost'], 0.0)
position.update(base_order_id='base1', base_qty=200, base_cost=10.5,
added_order_id='add1', added_num=1, added_qty=100, added_cost=9.0,
status='ACTIVE')
store.save({'600000.SH': position}, [])
self.assertEqual(store.positions['600000.SH'], position)
store.save({}, [])
store.save({'600001.SH': {'code': '600001.SH', 'status': READY}}, [])
self.assertEqual(indexes, {'idx_positions_stock_code'})
position = PositionItem(stock_code='600000.SH', volume=100, open_price=10,
stock_name='stock', can_use_volume=100, float_profit=-2.5)
store.sync_positions([position])
saved = store.positions[position.stock_code]
first_id = saved['id']
self.assertEqual({k: v for k, v in saved.items() if k != 'id'}, asdict(position))
position.volume = 200
store.sync_positions([position])
self.assertEqual(store.positions[position.stock_code]['id'], first_id)
self.assertEqual(store.positions[position.stock_code]['volume'], 200)
store.sync_positions([])
self.assertEqual(store.positions, {})
store.sync_positions([PositionItem(stock_code='600001.SH')])
self.assertGreater(store.positions['600001.SH']['id'], first_id)
def test_base_split_fills_and_snapshot_do_not_double_count(self):
@@ -207,12 +203,12 @@ class OrderBookTests(unittest.TestCase):
state = TState(self.path)
first = self.deal('base', 'd1', 100, 10, '20260901')
other = self.deal('base', 'd2', 100, 10)
other.local_order_id = 'trend-base-order'
other.remark = 'trend-base-order'
state.reconcile([], [first, other])
first.insert_date = '2026-09-01'
first.trade_date = '2026-09-01'
state.reconcile([], [first])
self.assertEqual(len(state.deals), 1)
self.assertEqual(state.deals[0]['insert_date'], '2026-09-01')
self.assertEqual(state.deals[0]['trade_date'], '2026-09-01')
if __name__ == '__main__':