httptrading 1.0.2__py3-none-any.whl → 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
httptrading/__init__.py CHANGED
@@ -4,3 +4,4 @@ from httptrading.broker.longbridge import *
4
4
  from httptrading.broker.tiger import *
5
5
  from httptrading.broker.interactive_brokers import *
6
6
  from httptrading.http_server import *
7
+ from httptrading.model import HtGlobalConfig
@@ -1,8 +1,11 @@
1
+ import os
2
+ import json
1
3
  import asyncio
2
4
  import importlib
3
5
  from abc import ABC
4
6
  from typing import Type, Callable, Any
5
7
  from httptrading.model import *
8
+ from httptrading.tool.locate import *
6
9
 
7
10
 
8
11
  class BaseBroker(ABC):
@@ -22,7 +25,10 @@ class BaseBroker(ABC):
22
25
  return BrokerRegister.get_meta(type(self)).display
23
26
 
24
27
  def detect_package(self):
25
- pkg_info = BrokerRegister.get_meta(type(self)).detect_package
28
+ meta = BrokerRegister.get_meta(type(self))
29
+ if not meta:
30
+ return
31
+ pkg_info = meta.detect_package
26
32
  if pkg_info is None:
27
33
  return
28
34
  try:
@@ -36,6 +42,23 @@ class BaseBroker(ABC):
36
42
  async def shutdown(self):
37
43
  pass
38
44
 
45
+ def dump_order(self, order: Order):
46
+ if not isinstance(order, Order):
47
+ return
48
+ folder = HtGlobalConfig.STREAM_DUMP_FOLDER
49
+ if not folder:
50
+ return
51
+ if not os.path.isdir(folder):
52
+ return
53
+ json_str = json.dumps(
54
+ order,
55
+ indent=2,
56
+ default=HtGlobalConfig.JSON_DEFAULT.json_default,
57
+ )
58
+ filename = f'{self.instance_id}-{order.order_id}.json'
59
+ full_path = os.path.join(folder, filename)
60
+ LocateTools.write_file(full_path, json_str)
61
+
39
62
  async def call_sync(self, f: Callable[[], Any]):
40
63
  try:
41
64
  r = await asyncio.get_running_loop().run_in_executor(None, f)
@@ -59,6 +82,7 @@ class BaseBroker(ABC):
59
82
  direction: str,
60
83
  qty: int,
61
84
  price: float = None,
85
+ full_args: dict = None,
62
86
  **kwargs
63
87
  ) -> str:
64
88
  raise NotImplementedError
@@ -30,6 +30,7 @@ class Futu(SecuritiesBroker):
30
30
 
31
31
  def _on_init(self):
32
32
  from futu import SysConfig, OpenQuoteContext, OpenSecTradeContext, SecurityFirm, TrdMarket, TrdEnv
33
+
33
34
  config_dict = self.broker_args
34
35
  self._trd_env: str = config_dict.get('trade_env', TrdEnv.REAL) or TrdEnv.REAL
35
36
  pk_path = config_dict.get('pk_path', '')
@@ -49,6 +50,7 @@ class Futu(SecuritiesBroker):
49
50
  )
50
51
  trade_ctx.set_sync_query_connect_timeout(6.0)
51
52
  self._trade_client = trade_ctx
53
+ self._when_create_client()
52
54
  if self._quote_client is None:
53
55
  SysConfig.set_all_thread_daemon(True)
54
56
  host = config_dict.get('host', '127.0.0.1')
@@ -57,6 +59,50 @@ class Futu(SecuritiesBroker):
57
59
  quote_ctx.set_sync_query_connect_timeout(6.0)
58
60
  self._quote_client = quote_ctx
59
61
 
62
+ def _when_create_client(self):
63
+ from futu import TradeOrderHandlerBase, RET_OK, OpenSecTradeContext
64
+
65
+ client: OpenSecTradeContext = self._trade_client
66
+
67
+ def _on_recv_rsp(content):
68
+ for _futu_order in self._df_to_list(content):
69
+ try:
70
+ _order = self._build_order(_futu_order)
71
+ self.dump_order(_order)
72
+ except Exception as _ex:
73
+ print(f'[{self.__class__.__name__}]_on_recv_rsp: {_ex}\norder: {_futu_order}')
74
+
75
+ class TradeOrderHandler(TradeOrderHandlerBase):
76
+ def on_recv_rsp(self, rsp_pb):
77
+ ret, content = super().on_recv_rsp(rsp_pb)
78
+ if ret == RET_OK:
79
+ _on_recv_rsp(content)
80
+ return ret, content
81
+
82
+ client.set_handler(TradeOrderHandler())
83
+
84
+ async def start(self):
85
+ from futu import RET_OK, OpenSecTradeContext
86
+
87
+ client: OpenSecTradeContext = self._trade_client
88
+ if HtGlobalConfig.DUMP_ACTIVE_ORDERS:
89
+ try:
90
+ ret, data = client.order_list_query(
91
+ refresh_cache=True,
92
+ trd_env=self._trd_env,
93
+ )
94
+ except Exception as e:
95
+ print(f'[{self.__class__.__name__}]DUMP_ACTIVE_ORDERS: {e}')
96
+ else:
97
+ if ret == RET_OK:
98
+ futu_orders = self._df_to_list(data)
99
+ for futu_order in futu_orders:
100
+ try:
101
+ order = self._build_order(futu_order)
102
+ await self.call_sync(lambda : self.dump_order(order))
103
+ except Exception as ex:
104
+ print(f'[{self.__class__.__name__}]DUMP_ACTIVE_ORDERS: {ex}\norder: {futu_order}')
105
+
60
106
  @classmethod
61
107
  def code_to_contract(cls, code) -> Contract | None:
62
108
  region = ''
@@ -98,7 +144,7 @@ class Futu(SecuritiesBroker):
98
144
  return code
99
145
 
100
146
  @classmethod
101
- def df_to_dict(cls, df) -> dict:
147
+ def _df_to_list(cls, df) -> list[dict]:
102
148
  return df.to_dict(orient='records')
103
149
 
104
150
  def _positions(self):
@@ -111,7 +157,7 @@ class Futu(SecuritiesBroker):
111
157
  )
112
158
  if resp != RET_OK:
113
159
  raise Exception(f'返回失败: {resp}')
114
- positions = self.df_to_dict(data)
160
+ positions = self._df_to_list(data)
115
161
  for d in positions:
116
162
  code = d.get('code')
117
163
  currency = d.get('currency')
@@ -153,7 +199,7 @@ class Futu(SecuritiesBroker):
153
199
  )
154
200
  if resp != RET_OK:
155
201
  raise Exception(f'可用资金信息获取失败: {data}')
156
- assets = self.df_to_dict(data)
202
+ assets = self._df_to_list(data)
157
203
  if len(assets) == 1:
158
204
  cash = Cash(
159
205
  currency='USD',
@@ -219,7 +265,7 @@ class Futu(SecuritiesBroker):
219
265
  ret, data = self._quote_client.get_market_snapshot([code, ])
220
266
  if ret != RET_OK:
221
267
  raise ValueError(f'快照接口调用失败: {data}')
222
- table = self.df_to_dict(data)
268
+ table = self._df_to_list(data)
223
269
  if len(table) != 1:
224
270
  raise ValueError(f'快照接口调用无数据: {data}')
225
271
  d = table[0]
@@ -274,6 +320,7 @@ class Futu(SecuritiesBroker):
274
320
  direction: str,
275
321
  qty: int,
276
322
  price: float = None,
323
+ full_args: dict = None,
277
324
  **kwargs
278
325
  ) -> str:
279
326
  from futu import RET_OK, TrdSide, OrderType as FutuOrderType, TimeInForce as FutuTimeInForce, Session
@@ -337,7 +384,7 @@ class Futu(SecuritiesBroker):
337
384
  )
338
385
  if ret != RET_OK:
339
386
  raise Exception(f'下单失败: {data}')
340
- orders = self.df_to_dict(data)
387
+ orders = self._df_to_list(data)
341
388
  assert len(orders) == 1
342
389
  order_id = orders[0]['order_id']
343
390
  assert order_id
@@ -352,6 +399,7 @@ class Futu(SecuritiesBroker):
352
399
  direction: str,
353
400
  qty: int,
354
401
  price: float = None,
402
+ full_args: dict = None,
355
403
  **kwargs
356
404
  ) -> str:
357
405
  return await self.call_sync(lambda : self._place_order(
@@ -362,12 +410,13 @@ class Futu(SecuritiesBroker):
362
410
  direction=direction,
363
411
  qty=qty,
364
412
  price=price,
413
+ full_args=full_args,
365
414
  **kwargs
366
415
  ))
367
416
 
368
- def _order(self, order_id: str) -> Order:
369
- from futu import RET_OK, OrderStatus
370
-
417
+ @classmethod
418
+ def _build_order(cls, futu_order: dict) -> Order:
419
+ from futu import OrderStatus
371
420
  """
372
421
  富途证券的状态定义
373
422
  NONE = "N/A" # 未知状态
@@ -394,22 +443,11 @@ class Futu(SecuritiesBroker):
394
443
  OrderStatus.FAILED,
395
444
  OrderStatus.DISABLED,
396
445
  OrderStatus.DELETED,
397
- OrderStatus.FILL_CANCELLED, # 不清楚对于成交数量有何影响.
446
+ OrderStatus.FILL_CANCELLED, # 不清楚对于成交数量有何影响.
398
447
  }
399
448
  pending_cancel_sets = {OrderStatus.CANCELLING_PART, OrderStatus.CANCELLING_ALL, }
400
449
 
401
- with self._refresh_order_bucket:
402
- ret, data = self._trade_client.order_list_query(
403
- order_id=order_id,
404
- refresh_cache=True,
405
- trd_env=self._trd_env,
406
- )
407
- if ret != RET_OK:
408
- raise Exception(f'调用获取订单失败, 订单: {order_id}')
409
- orders = self.df_to_dict(data)
410
- if len(orders) != 1:
411
- raise Exception(f'找不到订单(未完成), 订单: {order_id}')
412
- futu_order = orders[0]
450
+ order_id = futu_order['order_id']
413
451
  reason = ''
414
452
  order_status: str = futu_order['order_status']
415
453
  if order_status in bad_endings:
@@ -427,6 +465,23 @@ class Futu(SecuritiesBroker):
427
465
  is_pending_cancel=is_pending_cancel,
428
466
  )
429
467
 
468
+ def _order(self, order_id: str) -> Order:
469
+ from futu import RET_OK
470
+
471
+ with self._refresh_order_bucket:
472
+ ret, data = self._trade_client.order_list_query(
473
+ order_id=order_id,
474
+ refresh_cache=True,
475
+ trd_env=self._trd_env,
476
+ )
477
+ if ret != RET_OK:
478
+ raise Exception(f'调用获取订单失败, 订单: {order_id}')
479
+ orders = self._df_to_list(data)
480
+ if len(orders) != 1:
481
+ raise Exception(f'找不到订单(未完成), 订单: {order_id}')
482
+ futu_order = orders[0]
483
+ return self._build_order(futu_order)
484
+
430
485
  async def order(self, order_id: str) -> Order:
431
486
  return await self.call_sync(lambda : self._order(order_id=order_id))
432
487
 
@@ -4,6 +4,7 @@ https://ib-insync.readthedocs.io/readme.html
4
4
  """
5
5
  import re
6
6
  import asyncio
7
+ import nest_asyncio
7
8
  from typing import Any
8
9
  from httptrading.tool.leaky_bucket import *
9
10
  from httptrading.tool.time import *
@@ -29,9 +30,19 @@ class InteractiveBrokers(SecuritiesBroker):
29
30
  def _on_init(self):
30
31
  self._account_id = self.broker_args.get('account_id')
31
32
  self._client_id = self.broker_args.get('client_id')
33
+ nest_asyncio.apply()
32
34
 
33
35
  async def start(self):
34
36
  await self._try_create_client()
37
+ client = self._client
38
+ if HtGlobalConfig.DUMP_ACTIVE_ORDERS:
39
+ trades = client.trades()
40
+ for trade in trades:
41
+ try:
42
+ order = self._build_order(trade)
43
+ await self.call_sync(lambda: self.dump_order(order))
44
+ except Exception as ex:
45
+ print(f'[{self.__class__.__name__}]DUMP_ACTIVE_ORDERS: {ex}\norder: {trade}')
35
46
 
36
47
  async def shutdown(self):
37
48
  ib_socket = self._client
@@ -86,7 +97,11 @@ class InteractiveBrokers(SecuritiesBroker):
86
97
  client: ib_insync.IB = client
87
98
 
88
99
  def _order_status_changed(trade: ib_insync.Trade):
89
- pass
100
+ try:
101
+ order = self._build_order(trade)
102
+ self.dump_order(order)
103
+ except Exception as e:
104
+ print(f'[{self.__class__.__name__}]_order_status_changed: {e}\ntrade: {trade}')
90
105
 
91
106
  client.orderStatusEvent += _order_status_changed
92
107
 
@@ -190,10 +205,11 @@ class InteractiveBrokers(SecuritiesBroker):
190
205
  direction: str,
191
206
  qty: int,
192
207
  price: float = None,
208
+ full_args: dict = None,
193
209
  **kwargs
194
210
  ) -> str:
195
211
  import ib_insync
196
- with self._order_bucket:
212
+ async with self._order_bucket:
197
213
  client = self._client
198
214
  ib_contract = await self.contract_to_ib_contract(contract)
199
215
 
@@ -259,6 +275,7 @@ class InteractiveBrokers(SecuritiesBroker):
259
275
  direction: str,
260
276
  qty: int,
261
277
  price: float = None,
278
+ full_args: dict = None,
262
279
  **kwargs
263
280
  ) -> str:
264
281
  return await self.call_async(self._place_order(
@@ -269,12 +286,13 @@ class InteractiveBrokers(SecuritiesBroker):
269
286
  direction=direction,
270
287
  qty=qty,
271
288
  price=price,
289
+ full_args=full_args,
272
290
  **kwargs
273
291
  ))
274
292
 
275
293
  async def _cancel_order(self, order_id: str):
276
294
  order_id_int = int(order_id)
277
- with self._order_bucket:
295
+ async with self._order_bucket:
278
296
  client = self._client
279
297
  trades = client.trades()
280
298
  for ib_trade in trades:
@@ -287,9 +305,9 @@ class InteractiveBrokers(SecuritiesBroker):
287
305
  async def cancel_order(self, order_id: str):
288
306
  await self.call_async(self._cancel_order(order_id=order_id))
289
307
 
290
- async def _order(self, order_id: str) -> Order:
308
+ @classmethod
309
+ def _build_order(cls, ib_trade):
291
310
  import ib_insync
292
-
293
311
  canceled_endings = {ib_insync.OrderStatus.Cancelled, ib_insync.OrderStatus.ApiCancelled, }
294
312
  bad_endings = {ib_insync.OrderStatus.Inactive, }
295
313
  pending_cancel_sets = {ib_insync.OrderStatus.PendingCancel, }
@@ -304,7 +322,31 @@ class InteractiveBrokers(SecuritiesBroker):
304
322
  cap = sum([fill.execution.shares * fill.execution.avgPrice for fill in trade.fills], 0.0)
305
323
  return round(cap / total_fills, 5)
306
324
 
307
- with self._order_bucket:
325
+ qty = int(_total_fills(ib_trade) + ib_trade.remaining())
326
+ filled_qty = _total_fills(ib_trade)
327
+ qty = qty or filled_qty
328
+ assert qty >= filled_qty
329
+ avg_fill_price = _avg_price(ib_trade)
330
+ reason = ''
331
+ if ib_trade.orderStatus.status in bad_endings:
332
+ reason = ib_trade.orderStatus.status
333
+ is_canceled = ib_trade.orderStatus.status in canceled_endings
334
+ is_pending_cancel = ib_trade.orderStatus.status in pending_cancel_sets
335
+ order_id = str(ib_trade.order.permId)
336
+ order = Order(
337
+ order_id=order_id,
338
+ currency=ib_trade.contract.currency,
339
+ qty=qty,
340
+ filled_qty=filled_qty,
341
+ avg_price=avg_fill_price,
342
+ error_reason=reason,
343
+ is_canceled=is_canceled,
344
+ is_pending_cancel=is_pending_cancel,
345
+ )
346
+ return order
347
+
348
+ async def _order(self, order_id: str) -> Order:
349
+ async with self._order_bucket:
308
350
  client = self._client
309
351
  trades = client.trades()
310
352
  order_id_int = int(order_id)
@@ -312,26 +354,7 @@ class InteractiveBrokers(SecuritiesBroker):
312
354
  ib_order = ib_trade.order
313
355
  if ib_order.permId != order_id_int:
314
356
  continue
315
- qty = int(_total_fills(ib_trade) + ib_trade.remaining())
316
- filled_qty = _total_fills(ib_trade)
317
- qty = qty or filled_qty
318
- assert qty >= filled_qty
319
- avg_fill_price = _avg_price(ib_trade)
320
- reason = ''
321
- if ib_trade.orderStatus.status in bad_endings:
322
- reason = ib_trade.orderStatus.status
323
- is_canceled = ib_trade.orderStatus.status in canceled_endings
324
- is_pending_cancel = ib_trade.orderStatus.status in pending_cancel_sets
325
- order = Order(
326
- order_id=order_id,
327
- currency=ib_trade.contract.currency,
328
- qty=qty,
329
- filled_qty=filled_qty,
330
- avg_price=avg_fill_price,
331
- error_reason=reason,
332
- is_canceled=is_canceled,
333
- is_pending_cancel=is_pending_cancel,
334
- )
357
+ order = self._build_order(ib_trade)
335
358
  return order
336
359
  raise Exception(f'查询不到订单{order_id}')
337
360
 
@@ -116,6 +116,58 @@ class LongBridge(SecuritiesBroker):
116
116
  self._lp_config = config
117
117
  self._quote_client = quote_ctx
118
118
  self._trade_client = trade_ctx
119
+ self._when_create_client()
120
+
121
+ @classmethod
122
+ def _order_status(cls, lp_order):
123
+ # 订单状态定义见
124
+ # https://open.longportapp.com/zh-CN/docs/trade/trade-definition#orderstatus
125
+ from longport.openapi import OrderStatus, PushOrderChanged, OrderDetail
126
+
127
+ canceled_endings = {OrderStatus.Canceled, }
128
+ bad_endings = {
129
+ OrderStatus.Rejected,
130
+ OrderStatus.Expired,
131
+ OrderStatus.PartialWithdrawal,
132
+ }
133
+ pending_cancel_sets = {OrderStatus.PendingCancel, }
134
+
135
+ if isinstance(lp_order, PushOrderChanged):
136
+ status = lp_order.status
137
+ elif isinstance(lp_order, OrderDetail):
138
+ status = lp_order.status
139
+ else:
140
+ raise Exception(f'{lp_order}对象不是已知可解析订单状态的类型')
141
+ reason = ''
142
+ if status in bad_endings:
143
+ reason = str(status)
144
+ is_canceled = status in canceled_endings
145
+ is_pending_cancel = status in pending_cancel_sets
146
+ return reason, is_canceled, is_pending_cancel
147
+
148
+ def _when_create_client(self):
149
+ from longport.openapi import PushOrderChanged, TopicType, OrderStatus
150
+
151
+ def _on_order_changed(event: PushOrderChanged):
152
+ reason, is_canceled, is_pending_cancel = self._order_status(event)
153
+ try:
154
+ order = Order(
155
+ order_id=event.order_id,
156
+ currency=event.currency,
157
+ qty=int(event.executed_quantity),
158
+ filled_qty=int(event.executed_quantity),
159
+ avg_price=float(event.executed_price) if event.executed_price else 0.0,
160
+ error_reason=reason,
161
+ is_canceled=is_canceled,
162
+ is_pending_cancel=is_pending_cancel,
163
+ )
164
+ self.dump_order(order)
165
+ except Exception as e:
166
+ print(f'[{self.__class__.__name__}]_on_order_changed: {e}\norder: {event}')
167
+
168
+ trade_client = self._trade_client
169
+ trade_client.set_on_order_changed(_on_order_changed)
170
+ trade_client.subscribe([TopicType.Private])
119
171
 
120
172
  def _try_refresh(self):
121
173
  if not self._auto_refresh_token:
@@ -258,6 +310,7 @@ class LongBridge(SecuritiesBroker):
258
310
  direction: str,
259
311
  qty: int,
260
312
  price: float = None,
313
+ full_args: dict = None,
261
314
  **kwargs
262
315
  ) -> str:
263
316
  from longport.openapi import OrderType as LbOrderType, OrderSide, TimeInForceType, OutsideRTH
@@ -330,6 +383,7 @@ class LongBridge(SecuritiesBroker):
330
383
  direction: str,
331
384
  qty: int,
332
385
  price: float = None,
386
+ full_args: dict = None,
333
387
  **kwargs
334
388
  ) -> str:
335
389
  return await self.call_sync(lambda : self._place_order(
@@ -340,30 +394,15 @@ class LongBridge(SecuritiesBroker):
340
394
  direction=direction,
341
395
  qty=qty,
342
396
  price=price,
397
+ full_args=full_args,
343
398
  **kwargs
344
399
  ))
345
400
 
346
401
  def _order(self, order_id: str) -> Order:
347
- # 订单状态定义见
348
- # https://open.longportapp.com/zh-CN/docs/trade/trade-definition#orderstatus
349
- from longport.openapi import OrderStatus
350
-
351
- canceled_endings = {OrderStatus.Canceled, }
352
- bad_endings = {
353
- OrderStatus.Rejected,
354
- OrderStatus.Expired,
355
- OrderStatus.PartialWithdrawal,
356
- }
357
- pending_cancel_sets = {OrderStatus.PendingCancel, }
358
-
359
402
  with self._assets_bucket:
360
403
  self._try_refresh()
361
404
  resp = self._trade_client.order_detail(order_id=order_id)
362
- reason = ''
363
- if resp.status in bad_endings:
364
- reason = str(resp.status)
365
- is_canceled = resp.status in canceled_endings
366
- is_pending_cancel = resp.status in pending_cancel_sets
405
+ reason, is_canceled, is_pending_cancel = self._order_status(resp)
367
406
  return Order(
368
407
  order_id=order_id,
369
408
  currency=resp.currency,
@@ -46,6 +46,62 @@ class Tiger(SecuritiesBroker):
46
46
  self._trade_client = self._trade_client or TradeClient(client_config)
47
47
  protocol, host, port = client_config.socket_host_port
48
48
  self._push_client = self._push_client or PushClient(host, port, use_ssl=(protocol == 'ssl'))
49
+ self._when_create_client()
50
+
51
+ @classmethod
52
+ def _order_status(cls, tiger_order):
53
+ # 订单状态定义见
54
+ # https://quant.itigerup.com/openapi/zh/python/appendix2/overview.html#%E8%AE%A2%E5%8D%95%E7%8A%B6%E6%80%81
55
+ # 注意文档比 SDK 定义要少
56
+ from tigeropen.trade.domain.order import Order as TigerOrder, OrderStatus
57
+ from tigeropen.push.pb.OrderStatusData_pb2 import OrderStatusData
58
+ from tigeropen.common.util.order_utils import get_order_status
59
+
60
+ canceled_endings = {OrderStatus.CANCELLED, }
61
+ bad_endings = {
62
+ OrderStatus.REJECTED,
63
+ OrderStatus.EXPIRED,
64
+ }
65
+ pending_cancel_sets = {OrderStatus.PENDING_CANCEL, }
66
+
67
+ if isinstance(tiger_order, TigerOrder):
68
+ status = tiger_order.status
69
+ elif isinstance(tiger_order, OrderStatusData):
70
+ status = get_order_status(tiger_order.status)
71
+ else:
72
+ raise Exception(f'{tiger_order}对象不是已知可解析订单状态的类型')
73
+ reason = ''
74
+ if status in bad_endings:
75
+ reason = str(status)
76
+ is_canceled = status in canceled_endings
77
+ is_pending_cancel = status in pending_cancel_sets
78
+ return reason, is_canceled, is_pending_cancel
79
+
80
+ def _when_create_client(self):
81
+ from tigeropen.push.pb.OrderStatusData_pb2 import OrderStatusData
82
+
83
+ def _on_order_changed(frame: OrderStatusData):
84
+ try:
85
+ reason, is_canceled, is_pending_cancel = self._order_status(frame)
86
+ order = Order(
87
+ order_id=str(frame.id),
88
+ currency=frame.currency,
89
+ qty=frame.totalQuantity or 0,
90
+ filled_qty=frame.filledQuantity or 0,
91
+ avg_price=frame.avgFillPrice or 0.0,
92
+ error_reason=reason,
93
+ is_canceled=is_canceled,
94
+ is_pending_cancel=is_pending_cancel,
95
+ )
96
+ self.dump_order(order)
97
+ except Exception as e:
98
+ print(f'[{self.__class__.__name__}]_on_order_changed: {e}\norder: {frame}')
99
+
100
+ client_config = self._config
101
+ push_client = self._push_client
102
+ push_client.order_changed = _on_order_changed
103
+ push_client.connect(client_config.tiger_id, client_config.private_key)
104
+ push_client.subscribe_order(account=client_config.account)
49
105
 
50
106
  def _grab_quote(self):
51
107
  with self._grab_lock:
@@ -225,6 +281,7 @@ class Tiger(SecuritiesBroker):
225
281
  direction: str,
226
282
  qty: int,
227
283
  price: float = None,
284
+ full_args: dict = None,
228
285
  **kwargs
229
286
  ) -> str:
230
287
  if contract.trade_type != TradeType.Securities:
@@ -304,6 +361,7 @@ class Tiger(SecuritiesBroker):
304
361
  direction: str,
305
362
  qty: int,
306
363
  price: float = None,
364
+ full_args: dict = None,
307
365
  **kwargs
308
366
  ) -> str:
309
367
  return await self.call_sync(lambda: self._place_order(
@@ -314,31 +372,19 @@ class Tiger(SecuritiesBroker):
314
372
  direction=direction,
315
373
  qty=qty,
316
374
  price=price,
375
+ full_args=full_args,
317
376
  **kwargs
318
377
  ))
319
378
 
320
379
  def _order(self, order_id: str) -> Order:
321
- # 订单状态定义见
322
- # https://quant.itigerup.com/openapi/zh/python/appendix2/overview.html#%E8%AE%A2%E5%8D%95%E7%8A%B6%E6%80%81
323
- # 注意文档比 SDK 定义要少
324
- from tigeropen.trade.domain.order import Order as TigerOrder, OrderStatus
325
-
326
- canceled_endings = {OrderStatus.CANCELLED, }
327
- bad_endings = {
328
- OrderStatus.REJECTED,
329
- OrderStatus.EXPIRED,
330
- }
331
- pending_cancel_sets = {OrderStatus.PENDING_CANCEL, }
380
+ from tigeropen.trade.domain.order import Order as TigerOrder
332
381
 
333
382
  with self._order_bucket:
334
383
  tiger_order: TigerOrder = self._trade_client.get_order(id=int(order_id))
335
384
  if tiger_order is None:
336
385
  raise Exception(f'查询不到订单{order_id}')
337
386
 
338
- order_status = tiger_order.status
339
- reason = tiger_order.reason or (order_status.name if order_status in bad_endings else None)
340
- is_canceled = order_status in canceled_endings
341
- is_pending_cancel = order_status in pending_cancel_sets
387
+ reason, is_canceled, is_pending_cancel = self._order_status(tiger_order)
342
388
  return Order(
343
389
  order_id=order_id,
344
390
  currency=tiger_order.contract.currency,