httptrading 1.0.2__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 +1 -0
- httptrading/broker/base.py +24 -1
- httptrading/broker/futu_sec.py +73 -21
- httptrading/broker/interactive_brokers.py +46 -26
- httptrading/broker/longbridge.py +53 -17
- httptrading/broker/tiger.py +58 -15
- httptrading/http_server.py +267 -325
- httptrading/model.py +79 -0
- {httptrading-1.0.2.dist-info → httptrading-1.0.3.dist-info}/METADATA +16 -7
- httptrading-1.0.3.dist-info/RECORD +18 -0
- httptrading-1.0.2.dist-info/RECORD +0 -18
- {httptrading-1.0.2.dist-info → httptrading-1.0.3.dist-info}/WHEEL +0 -0
- {httptrading-1.0.2.dist-info → httptrading-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {httptrading-1.0.2.dist-info → httptrading-1.0.3.dist-info}/top_level.txt +0 -0
httptrading/__init__.py
CHANGED
httptrading/broker/base.py
CHANGED
@@ -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
|
-
|
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)
|
httptrading/broker/futu_sec.py
CHANGED
@@ -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
|
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.
|
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.
|
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.
|
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.
|
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,9 +411,9 @@ class Futu(SecuritiesBroker):
|
|
365
411
|
**kwargs
|
366
412
|
))
|
367
413
|
|
368
|
-
|
369
|
-
|
370
|
-
|
414
|
+
@classmethod
|
415
|
+
def _build_order(cls, futu_order: dict) -> Order:
|
416
|
+
from futu import OrderStatus
|
371
417
|
"""
|
372
418
|
富途证券的状态定义
|
373
419
|
NONE = "N/A" # 未知状态
|
@@ -394,22 +440,11 @@ class Futu(SecuritiesBroker):
|
|
394
440
|
OrderStatus.FAILED,
|
395
441
|
OrderStatus.DISABLED,
|
396
442
|
OrderStatus.DELETED,
|
397
|
-
OrderStatus.FILL_CANCELLED,
|
443
|
+
OrderStatus.FILL_CANCELLED, # 不清楚对于成交数量有何影响.
|
398
444
|
}
|
399
445
|
pending_cancel_sets = {OrderStatus.CANCELLING_PART, OrderStatus.CANCELLING_ALL, }
|
400
446
|
|
401
|
-
|
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]
|
447
|
+
order_id = futu_order['order_id']
|
413
448
|
reason = ''
|
414
449
|
order_status: str = futu_order['order_status']
|
415
450
|
if order_status in bad_endings:
|
@@ -427,6 +462,23 @@ class Futu(SecuritiesBroker):
|
|
427
462
|
is_pending_cancel=is_pending_cancel,
|
428
463
|
)
|
429
464
|
|
465
|
+
def _order(self, order_id: str) -> Order:
|
466
|
+
from futu import RET_OK
|
467
|
+
|
468
|
+
with self._refresh_order_bucket:
|
469
|
+
ret, data = self._trade_client.order_list_query(
|
470
|
+
order_id=order_id,
|
471
|
+
refresh_cache=True,
|
472
|
+
trd_env=self._trd_env,
|
473
|
+
)
|
474
|
+
if ret != RET_OK:
|
475
|
+
raise Exception(f'调用获取订单失败, 订单: {order_id}')
|
476
|
+
orders = self._df_to_list(data)
|
477
|
+
if len(orders) != 1:
|
478
|
+
raise Exception(f'找不到订单(未完成), 订单: {order_id}')
|
479
|
+
futu_order = orders[0]
|
480
|
+
return self._build_order(futu_order)
|
481
|
+
|
430
482
|
async def order(self, order_id: str) -> Order:
|
431
483
|
return await self.call_sync(lambda : self._order(order_id=order_id))
|
432
484
|
|
@@ -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
|
-
|
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
|
|
@@ -193,7 +208,7 @@ class InteractiveBrokers(SecuritiesBroker):
|
|
193
208
|
**kwargs
|
194
209
|
) -> str:
|
195
210
|
import ib_insync
|
196
|
-
with self._order_bucket:
|
211
|
+
async with self._order_bucket:
|
197
212
|
client = self._client
|
198
213
|
ib_contract = await self.contract_to_ib_contract(contract)
|
199
214
|
|
@@ -274,7 +289,7 @@ class InteractiveBrokers(SecuritiesBroker):
|
|
274
289
|
|
275
290
|
async def _cancel_order(self, order_id: str):
|
276
291
|
order_id_int = int(order_id)
|
277
|
-
with self._order_bucket:
|
292
|
+
async with self._order_bucket:
|
278
293
|
client = self._client
|
279
294
|
trades = client.trades()
|
280
295
|
for ib_trade in trades:
|
@@ -287,9 +302,9 @@ class InteractiveBrokers(SecuritiesBroker):
|
|
287
302
|
async def cancel_order(self, order_id: str):
|
288
303
|
await self.call_async(self._cancel_order(order_id=order_id))
|
289
304
|
|
290
|
-
|
305
|
+
@classmethod
|
306
|
+
def _build_order(cls, ib_trade):
|
291
307
|
import ib_insync
|
292
|
-
|
293
308
|
canceled_endings = {ib_insync.OrderStatus.Cancelled, ib_insync.OrderStatus.ApiCancelled, }
|
294
309
|
bad_endings = {ib_insync.OrderStatus.Inactive, }
|
295
310
|
pending_cancel_sets = {ib_insync.OrderStatus.PendingCancel, }
|
@@ -304,7 +319,31 @@ class InteractiveBrokers(SecuritiesBroker):
|
|
304
319
|
cap = sum([fill.execution.shares * fill.execution.avgPrice for fill in trade.fills], 0.0)
|
305
320
|
return round(cap / total_fills, 5)
|
306
321
|
|
307
|
-
|
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:
|
308
347
|
client = self._client
|
309
348
|
trades = client.trades()
|
310
349
|
order_id_int = int(order_id)
|
@@ -312,26 +351,7 @@ class InteractiveBrokers(SecuritiesBroker):
|
|
312
351
|
ib_order = ib_trade.order
|
313
352
|
if ib_order.permId != order_id_int:
|
314
353
|
continue
|
315
|
-
|
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
|
-
)
|
354
|
+
order = self._build_order(ib_trade)
|
335
355
|
return order
|
336
356
|
raise Exception(f'查询不到订单{order_id}')
|
337
357
|
|
httptrading/broker/longbridge.py
CHANGED
@@ -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,26 +396,10 @@ class LongBridge(SecuritiesBroker):
|
|
344
396
|
))
|
345
397
|
|
346
398
|
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
399
|
with self._assets_bucket:
|
360
400
|
self._try_refresh()
|
361
401
|
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
|
402
|
+
reason, is_canceled, is_pending_cancel = self._order_status(resp)
|
367
403
|
return Order(
|
368
404
|
order_id=order_id,
|
369
405
|
currency=resp.currency,
|
httptrading/broker/tiger.py
CHANGED
@@ -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,27 +374,14 @@ class Tiger(SecuritiesBroker):
|
|
318
374
|
))
|
319
375
|
|
320
376
|
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, }
|
377
|
+
from tigeropen.trade.domain.order import Order as TigerOrder
|
332
378
|
|
333
379
|
with self._order_bucket:
|
334
380
|
tiger_order: TigerOrder = self._trade_client.get_order(id=int(order_id))
|
335
381
|
if tiger_order is None:
|
336
382
|
raise Exception(f'查询不到订单{order_id}')
|
337
383
|
|
338
|
-
|
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
|
384
|
+
reason, is_canceled, is_pending_cancel = self._order_status(tiger_order)
|
342
385
|
return Order(
|
343
386
|
order_id=order_id,
|
344
387
|
currency=tiger_order.contract.currency,
|