httptrading 1.0.1__py3-none-any.whl → 1.0.3__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
@@ -1,6 +1,7 @@
1
1
  from httptrading.broker.base import *
2
- from httptrading.broker.futu import *
2
+ from httptrading.broker.futu_sec import *
3
3
  from httptrading.broker.longbridge import *
4
4
  from httptrading.broker.tiger import *
5
5
  from httptrading.broker.interactive_brokers import *
6
- from httptrading.http_server import run
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)
@@ -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]
@@ -337,7 +383,7 @@ class Futu(SecuritiesBroker):
337
383
  )
338
384
  if ret != RET_OK:
339
385
  raise Exception(f'下单失败: {data}')
340
- orders = self.df_to_dict(data)
386
+ orders = self._df_to_list(data)
341
387
  assert len(orders) == 1
342
388
  order_id = orders[0]['order_id']
343
389
  assert order_id
@@ -365,10 +411,60 @@ class Futu(SecuritiesBroker):
365
411
  **kwargs
366
412
  ))
367
413
 
414
+ @classmethod
415
+ def _build_order(cls, futu_order: dict) -> Order:
416
+ from futu import OrderStatus
417
+ """
418
+ 富途证券的状态定义
419
+ NONE = "N/A" # 未知状态
420
+ UNSUBMITTED = "UNSUBMITTED" # 未提交
421
+ WAITING_SUBMIT = "WAITING_SUBMIT" # 等待提交
422
+ SUBMITTING = "SUBMITTING" # 提交中
423
+ SUBMIT_FAILED = "SUBMIT_FAILED" # 提交失败,下单失败
424
+ TIMEOUT = "TIMEOUT" # 处理超时,结果未知
425
+ SUBMITTED = "SUBMITTED" # 已提交,等待成交
426
+ FILLED_PART = "FILLED_PART" # 部分成交
427
+ FILLED_ALL = "FILLED_ALL" # 全部已成
428
+ CANCELLING_PART = "CANCELLING_PART" # 正在撤单_部分(部分已成交,正在撤销剩余部分)
429
+ CANCELLING_ALL = "CANCELLING_ALL" # 正在撤单_全部
430
+ CANCELLED_PART = "CANCELLED_PART" # 部分成交,剩余部分已撤单
431
+ CANCELLED_ALL = "CANCELLED_ALL" # 全部已撤单,无成交
432
+ FAILED = "FAILED" # 下单失败,服务拒绝
433
+ DISABLED = "DISABLED" # 已失效
434
+ DELETED = "DELETED" # 已删除,无成交的订单才能删除
435
+ FILL_CANCELLED = "FILL_CANCELLED" # 成交被撤销,一般遇不到,意思是已经成交的订单被回滚撤销,成交无效变为废单
436
+ """
437
+ canceled_endings = {OrderStatus.CANCELLED_ALL, OrderStatus.CANCELLED_PART, }
438
+ bad_endings = {
439
+ OrderStatus.SUBMIT_FAILED,
440
+ OrderStatus.FAILED,
441
+ OrderStatus.DISABLED,
442
+ OrderStatus.DELETED,
443
+ OrderStatus.FILL_CANCELLED, # 不清楚对于成交数量有何影响.
444
+ }
445
+ pending_cancel_sets = {OrderStatus.CANCELLING_PART, OrderStatus.CANCELLING_ALL, }
446
+
447
+ order_id = futu_order['order_id']
448
+ reason = ''
449
+ order_status: str = futu_order['order_status']
450
+ if order_status in bad_endings:
451
+ reason = order_status
452
+ is_canceled = order_status in canceled_endings
453
+ is_pending_cancel = order_status in pending_cancel_sets
454
+ return Order(
455
+ order_id=order_id,
456
+ currency=futu_order['currency'],
457
+ qty=int(futu_order['qty']),
458
+ filled_qty=int(futu_order['dealt_qty']),
459
+ avg_price=futu_order['dealt_avg_price'] or 0.0,
460
+ error_reason=reason,
461
+ is_canceled=is_canceled,
462
+ is_pending_cancel=is_pending_cancel,
463
+ )
464
+
368
465
  def _order(self, order_id: str) -> Order:
369
- from futu import RET_OK, OrderStatus
370
- error_set = {OrderStatus.FAILED, OrderStatus.DISABLED, OrderStatus.DELETED, }
371
- cancel_set = {OrderStatus.CANCELLED_PART, OrderStatus.CANCELLED_ALL, }
466
+ from futu import RET_OK
467
+
372
468
  with self._refresh_order_bucket:
373
469
  ret, data = self._trade_client.order_list_query(
374
470
  order_id=order_id,
@@ -377,22 +473,11 @@ class Futu(SecuritiesBroker):
377
473
  )
378
474
  if ret != RET_OK:
379
475
  raise Exception(f'调用获取订单失败, 订单: {order_id}')
380
- orders = self.df_to_dict(data)
476
+ orders = self._df_to_list(data)
381
477
  if len(orders) != 1:
382
478
  raise Exception(f'找不到订单(未完成), 订单: {order_id}')
383
479
  futu_order = orders[0]
384
- reason = ''
385
- if futu_order['order_status'] in error_set:
386
- reason = futu_order['order_status']
387
- return Order(
388
- order_id=order_id,
389
- currency=futu_order['currency'],
390
- qty=int(futu_order['qty']),
391
- filled_qty=int(futu_order['dealt_qty']),
392
- avg_price=futu_order['dealt_avg_price'] or 0.0,
393
- error_reason=reason,
394
- is_canceled=futu_order['order_status'] in cancel_set,
395
- )
480
+ return self._build_order(futu_order)
396
481
 
397
482
  async def order(self, order_id: str) -> Order:
398
483
  return await self.call_sync(lambda : self._order(order_id=order_id))
@@ -4,8 +4,8 @@ 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
- from collections import defaultdict
9
9
  from httptrading.tool.leaky_bucket import *
10
10
  from httptrading.tool.time import *
11
11
  from httptrading.broker.base import *
@@ -30,9 +30,19 @@ class InteractiveBrokers(SecuritiesBroker):
30
30
  def _on_init(self):
31
31
  self._account_id = self.broker_args.get('account_id')
32
32
  self._client_id = self.broker_args.get('client_id')
33
+ nest_asyncio.apply()
33
34
 
34
35
  async def start(self):
35
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}')
36
46
 
37
47
  async def shutdown(self):
38
48
  ib_socket = self._client
@@ -87,7 +97,11 @@ class InteractiveBrokers(SecuritiesBroker):
87
97
  client: ib_insync.IB = client
88
98
 
89
99
  def _order_status_changed(trade: ib_insync.Trade):
90
- 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}')
91
105
 
92
106
  client.orderStatusEvent += _order_status_changed
93
107
 
@@ -194,7 +208,7 @@ class InteractiveBrokers(SecuritiesBroker):
194
208
  **kwargs
195
209
  ) -> str:
196
210
  import ib_insync
197
- with self._order_bucket:
211
+ async with self._order_bucket:
198
212
  client = self._client
199
213
  ib_contract = await self.contract_to_ib_contract(contract)
200
214
 
@@ -275,7 +289,7 @@ class InteractiveBrokers(SecuritiesBroker):
275
289
 
276
290
  async def _cancel_order(self, order_id: str):
277
291
  order_id_int = int(order_id)
278
- with self._order_bucket:
292
+ async with self._order_bucket:
279
293
  client = self._client
280
294
  trades = client.trades()
281
295
  for ib_trade in trades:
@@ -288,8 +302,12 @@ class InteractiveBrokers(SecuritiesBroker):
288
302
  async def cancel_order(self, order_id: str):
289
303
  await self.call_async(self._cancel_order(order_id=order_id))
290
304
 
291
- async def _order(self, order_id: str) -> Order:
305
+ @classmethod
306
+ def _build_order(cls, ib_trade):
292
307
  import ib_insync
308
+ canceled_endings = {ib_insync.OrderStatus.Cancelled, ib_insync.OrderStatus.ApiCancelled, }
309
+ bad_endings = {ib_insync.OrderStatus.Inactive, }
310
+ pending_cancel_sets = {ib_insync.OrderStatus.PendingCancel, }
293
311
 
294
312
  def _total_fills(trade) -> int:
295
313
  return int(trade.filled())
@@ -301,7 +319,31 @@ class InteractiveBrokers(SecuritiesBroker):
301
319
  cap = sum([fill.execution.shares * fill.execution.avgPrice for fill in trade.fills], 0.0)
302
320
  return round(cap / total_fills, 5)
303
321
 
304
- with self._order_bucket:
322
+ qty = int(_total_fills(ib_trade) + ib_trade.remaining())
323
+ filled_qty = _total_fills(ib_trade)
324
+ qty = qty or filled_qty
325
+ assert qty >= filled_qty
326
+ avg_fill_price = _avg_price(ib_trade)
327
+ reason = ''
328
+ if ib_trade.orderStatus.status in bad_endings:
329
+ reason = ib_trade.orderStatus.status
330
+ is_canceled = ib_trade.orderStatus.status in canceled_endings
331
+ is_pending_cancel = ib_trade.orderStatus.status in pending_cancel_sets
332
+ order_id = str(ib_trade.order.permId)
333
+ order = Order(
334
+ order_id=order_id,
335
+ currency=ib_trade.contract.currency,
336
+ qty=qty,
337
+ filled_qty=filled_qty,
338
+ avg_price=avg_fill_price,
339
+ error_reason=reason,
340
+ is_canceled=is_canceled,
341
+ is_pending_cancel=is_pending_cancel,
342
+ )
343
+ return order
344
+
345
+ async def _order(self, order_id: str) -> Order:
346
+ async with self._order_bucket:
305
347
  client = self._client
306
348
  trades = client.trades()
307
349
  order_id_int = int(order_id)
@@ -309,26 +351,8 @@ class InteractiveBrokers(SecuritiesBroker):
309
351
  ib_order = ib_trade.order
310
352
  if ib_order.permId != order_id_int:
311
353
  continue
312
- qty = int(_total_fills(ib_trade) + ib_trade.remaining())
313
- filled_qty = _total_fills(ib_trade)
314
- qty = qty or filled_qty
315
- assert qty >= filled_qty
316
- avg_fill_price = _avg_price(ib_trade)
317
- reason = ''
318
- if ib_trade.orderStatus.status == ib_insync.OrderStatus.Inactive:
319
- reason = 'Inactive'
320
-
321
- cancel_status = {ib_insync.OrderStatus.Cancelled, ib_insync.OrderStatus.ApiCancelled, }
322
- is_cancelled = ib_trade.orderStatus.status in cancel_status
323
- return Order(
324
- order_id=order_id,
325
- currency=ib_trade.contract.currency,
326
- qty=qty,
327
- filled_qty=filled_qty,
328
- avg_price=avg_fill_price,
329
- error_reason=reason,
330
- is_canceled=is_cancelled,
331
- )
354
+ order = self._build_order(ib_trade)
355
+ return order
332
356
  raise Exception(f'查询不到订单{order_id}')
333
357
 
334
358
  async def order(self, order_id: str) -> Order:
@@ -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:
@@ -344,17 +396,10 @@ class LongBridge(SecuritiesBroker):
344
396
  ))
345
397
 
346
398
  def _order(self, order_id: str) -> Order:
347
- from longport.openapi import OrderStatus
348
399
  with self._assets_bucket:
349
400
  self._try_refresh()
350
401
  resp = self._trade_client.order_detail(order_id=order_id)
351
- reason = ''
352
- if resp.status == OrderStatus.Rejected:
353
- reason = '已拒绝'
354
- if resp.status == OrderStatus.Expired:
355
- reason = '已过期'
356
- if resp.status == OrderStatus.PartialWithdrawal:
357
- reason = '部分撤单'
402
+ reason, is_canceled, is_pending_cancel = self._order_status(resp)
358
403
  return Order(
359
404
  order_id=order_id,
360
405
  currency=resp.currency,
@@ -362,7 +407,8 @@ class LongBridge(SecuritiesBroker):
362
407
  filled_qty=int(resp.executed_quantity),
363
408
  avg_price=float(resp.executed_price) if resp.executed_price else 0.0,
364
409
  error_reason=reason,
365
- is_canceled=resp.status == OrderStatus.Canceled,
410
+ is_canceled=is_canceled,
411
+ is_pending_cancel=is_pending_cancel,
366
412
  )
367
413
 
368
414
  async def order(self, order_id: str) -> Order:
@@ -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:
@@ -318,19 +374,23 @@ class Tiger(SecuritiesBroker):
318
374
  ))
319
375
 
320
376
  def _order(self, order_id: str) -> Order:
321
- from tigeropen.trade.domain.order import Order as TigerOrder, OrderStatus
377
+ from tigeropen.trade.domain.order import Order as TigerOrder
378
+
322
379
  with self._order_bucket:
323
380
  tiger_order: TigerOrder = self._trade_client.get_order(id=int(order_id))
324
381
  if tiger_order is None:
325
382
  raise Exception(f'查询不到订单{order_id}')
383
+
384
+ reason, is_canceled, is_pending_cancel = self._order_status(tiger_order)
326
385
  return Order(
327
386
  order_id=order_id,
328
387
  currency=tiger_order.contract.currency,
329
388
  qty=tiger_order.quantity or 0,
330
389
  filled_qty=tiger_order.filled or 0,
331
390
  avg_price=tiger_order.avg_fill_price or 0.0,
332
- error_reason=tiger_order.reason,
333
- is_canceled=tiger_order.status == OrderStatus.CANCELLED,
391
+ error_reason=reason,
392
+ is_canceled=is_canceled,
393
+ is_pending_cancel=is_pending_cancel,
334
394
  )
335
395
 
336
396
  async def order(self, order_id: str) -> Order: