feat libs,sdk,trend
This commit is contained in:
@@ -1,75 +1,89 @@
|
||||
import sqlite3
|
||||
import ast
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import closing
|
||||
from dataclasses import asdict
|
||||
from dataclasses import asdict, fields
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock
|
||||
from libs.order import OrderBook as ActiveOrders
|
||||
from libs.orderbook import OrderBook
|
||||
from sdk.models import DealItem
|
||||
from sdk.models import Assets, DealItem, OrderItem, PositionItem
|
||||
from sdk.portfolio import PortfolioMixin
|
||||
|
||||
|
||||
class DealModelTests(unittest.TestCase):
|
||||
class ApiModelTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.raw = {
|
||||
'm_strOrderSysID': 'sys-123',
|
||||
'm_strInstrumentID': '600000',
|
||||
'm_strExchangeID': 'SH',
|
||||
'm_strInstrumentName': 'Test stock',
|
||||
'm_nOffsetFlag': '24',
|
||||
'm_nOrderStatus': '56',
|
||||
'm_nVolumeTotal': '0',
|
||||
'm_nVolumeTraded': '200',
|
||||
'm_nOrderTime': '101530',
|
||||
'm_strInsertDate': '20260907',
|
||||
'm_strInsertTime': '10:15:30',
|
||||
'm_strRemark': 'zt-t-sell-local1|zt',
|
||||
'm_dPrice': '15.8',
|
||||
'm_dTradePrice': '15.6',
|
||||
'm_dTradeAmount': '3120',
|
||||
}
|
||||
source = Path(__file__).resolve().parents[2] / 'api' / 'qmt_rest_new.py'
|
||||
names = {'format_assets', 'format_holding', 'format_orders', 'format_deals'}
|
||||
nodes = [n for n in ast.parse(source.read_text(encoding='utf-8')).body
|
||||
if isinstance(n, ast.FunctionDef) and n.name in names]
|
||||
ns = {'HTTPError': RuntimeError}
|
||||
exec(compile(ast.Module(body=nodes, type_ignores=[]), str(source), 'exec'), ns)
|
||||
attrs = {n.attr: '' if n.attr.startswith('m_str') else 0
|
||||
for node in nodes for n in ast.walk(node)
|
||||
if isinstance(n, ast.Attribute) and n.attr.startswith('m_')}
|
||||
attrs.update(m_strInstrumentID='600000', m_strExchangeID='SH',
|
||||
m_strOrderSysID='sys1', m_strRemark='trend-BUY-1|trend',
|
||||
m_nOffsetFlag=23, m_nOrderStatus=56, m_nVolume=100,
|
||||
m_nVolumeTraded=100, m_nVolumeTotalOriginal=100,
|
||||
m_dPrice=10.0, m_dTradeAmount=1000.0, m_dBalance=2000.0,
|
||||
m_dAvailable=1000.0, m_strInsertDate='20260907',
|
||||
m_strInsertTime='100000', m_strTradeDate='20260907', m_strTradeTime='100000')
|
||||
obj = SimpleNamespace(**attrs)
|
||||
self.assets = ns['format_assets']([obj])
|
||||
self.positions = ns['format_holding']([obj])
|
||||
self.orders = ns['format_orders']([obj])
|
||||
self.deals = ns['format_deals']([obj])
|
||||
self.client = PortfolioMixin()
|
||||
self.client._get_json = {
|
||||
'/api/portfolio/assets': self.assets, '/api/portfolio/positions': self.positions,
|
||||
'/api/portfolio/order': self.orders, '/api/portfolio/deal': self.deals,
|
||||
'/api/portfolio': {'assets': self.assets, 'positions': self.positions, 'orders': self.orders},
|
||||
}.__getitem__
|
||||
|
||||
def test_all_api_fields_are_parsed(self):
|
||||
self.assertEqual(asdict(DealItem.from_trade_detail(self.raw)), {
|
||||
'sys_order_id': 'sys-123', 'local_order_id': 'zt-t-sell-local1',
|
||||
'code': '600000.SH', 'instrument_id': '600000', 'exchange_id': 'SH',
|
||||
'name': 'Test stock', 'offset_flag': '24', 'side': 'SELL', 'status': '56',
|
||||
'remaining_volume': 0, 'traded_volume': 200, 'order_time': 101530,
|
||||
'insert_date': '20260907', 'insert_time': '10:15:30',
|
||||
'remark': 'zt-t-sell-local1|zt', 'price': 15.8,
|
||||
'trade_price': 15.6, 'trade_amount': 3120.0,
|
||||
})
|
||||
def test_models_exactly_match_api_keys_and_values(self):
|
||||
for model, row in ((Assets, self.assets), (PositionItem, self.positions['600000.SH']),
|
||||
(OrderItem, self.orders[0]), (DealItem, self.deals[0])):
|
||||
self.assertEqual({field.name for field in fields(model)}, set(row))
|
||||
self.assertEqual(asdict(model(**row)), row)
|
||||
|
||||
def test_api_deals_response_returns_deal_items(self):
|
||||
client = PortfolioMixin()
|
||||
for response in ({'deals': [self.raw]}, [self.raw]):
|
||||
with self.subTest(response_type=type(response).__name__):
|
||||
client._get_json = lambda path: response
|
||||
deals = client.deals()
|
||||
self.assertIsInstance(deals[0], DealItem)
|
||||
self.assertEqual(deals[0].sys_order_id, 'sys-123')
|
||||
self.assertEqual(deals[0].traded_volume, 200)
|
||||
def test_all_endpoints(self):
|
||||
self.assertEqual(asdict(self.client.assets()), self.assets)
|
||||
codes, positions = self.client.positions()
|
||||
self.assertEqual(codes, ['600000.SH'])
|
||||
self.assertEqual(asdict(positions[0]), self.positions[codes[0]])
|
||||
self.assertEqual(asdict(self.client.orders()[0]), self.orders[0])
|
||||
self.assertEqual(asdict(self.client.deals()[0]), self.deals[0])
|
||||
portfolio = self.client.portfolio()
|
||||
self.assertEqual(asdict(portfolio.positions[codes[0]]), self.positions[codes[0]])
|
||||
self.assertEqual(asdict(portfolio.orders[0]), self.orders[0])
|
||||
|
||||
def test_model_matches_sql_columns_and_restart(self):
|
||||
deal = DealItem.from_trade_detail(self.raw)
|
||||
def test_derived_properties_and_order_cache(self):
|
||||
order = self.client.orders()[0]
|
||||
self.assertEqual(order.side, 'BUY')
|
||||
self.assertEqual(order.local_order_id, 'trend-BUY-1')
|
||||
self.assertEqual(order.created_at, datetime(2026, 9, 7, 10))
|
||||
order.order_status = 50
|
||||
order.insert_date = '20000101'
|
||||
client = Mock()
|
||||
book = ActiveOrders('trend')
|
||||
book.refresh(client, [order])
|
||||
client.cancel_by_id.assert_called_once_with('sys1')
|
||||
self.assertTrue(book.busy('600000.SH', 'BUY'))
|
||||
|
||||
def test_storage_and_price_fallback(self):
|
||||
deal = self.client.deals()[0]
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
path = Path(tmp) / 'state.db'
|
||||
book = OrderBook(path)
|
||||
book.sync_deals([deal])
|
||||
with closing(sqlite3.connect(path)) as db:
|
||||
columns = {row[1] for row in db.execute('PRAGMA table_info(deals)')}
|
||||
self.assertEqual(columns, {'id', *asdict(deal)})
|
||||
loaded = OrderBook(path).deals['sys-123']
|
||||
expected = asdict(deal)
|
||||
expected['insert_date'] = '2026-09-07'
|
||||
self.assertEqual({key: value for key, value in loaded.items() if key != 'id'}, expected)
|
||||
|
||||
def test_missing_amount_uses_execution_price_not_order_price(self):
|
||||
deal = DealItem.from_trade_detail({**self.raw, 'm_dTradeAmount': '0'})
|
||||
row = OrderBook.deal_record(deal)
|
||||
self.assertEqual(row['trade_amount'], 3120)
|
||||
deal.trade_price = 0
|
||||
loaded = OrderBook(path).deals['sys1']
|
||||
self.assertEqual(loaded['volume'], deal.volume)
|
||||
self.assertEqual(loaded['trade_date'], '2026-09-07')
|
||||
deal.trade_amount = 0
|
||||
self.assertEqual(OrderBook.deal_record(deal)['trade_amount'], 1000)
|
||||
deal.price = 0
|
||||
with self.assertRaises(ValueError):
|
||||
OrderBook.deal_record(deal)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user