httptrading 1.0.6__tar.gz → 1.0.8__tar.gz
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-1.0.6 → httptrading-1.0.8}/PKG-INFO +1 -1
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/__init__.py +1 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/broker/interactive_brokers.py +1 -1
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/broker/longbridge.py +5 -5
- httptrading-1.0.8/httptrading/broker/moomoo_sec.py +395 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/model.py +3 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading.egg-info/PKG-INFO +1 -1
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading.egg-info/SOURCES.txt +1 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/pyproject.toml +1 -1
- {httptrading-1.0.6 → httptrading-1.0.8}/LICENSE +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/README.md +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/broker/__init__.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/broker/base.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/broker/futu_sec.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/broker/tiger.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/http_server.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/tool/__init__.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/tool/leaky_bucket.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/tool/locate.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading/tool/time.py +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading.egg-info/dependency_links.txt +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading.egg-info/requires.txt +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/httptrading.egg-info/top_level.txt +0 -0
- {httptrading-1.0.6 → httptrading-1.0.8}/setup.cfg +0 -0
@@ -3,5 +3,6 @@ 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.broker.moomoo_sec import *
|
6
7
|
from httptrading.http_server import *
|
7
8
|
from httptrading.model import HtGlobalConfig
|
@@ -107,7 +107,7 @@ class InteractiveBrokers(SecuritiesBroker):
|
|
107
107
|
ib_socket = self._client
|
108
108
|
if ib_socket:
|
109
109
|
try:
|
110
|
-
ib_dt = await ib_socket.reqCurrentTimeAsync()
|
110
|
+
ib_dt = await asyncio.wait_for(ib_socket.reqCurrentTimeAsync(), 2)
|
111
111
|
now = TimeTools.utc_now()
|
112
112
|
if TimeTools.timedelta(ib_dt, seconds=self.timeout) <= now:
|
113
113
|
raise TimeoutError
|
@@ -124,13 +124,13 @@ class LongBridge(SecuritiesBroker):
|
|
124
124
|
# https://open.longportapp.com/zh-CN/docs/trade/trade-definition#orderstatus
|
125
125
|
from longport.openapi import OrderStatus, PushOrderChanged, OrderDetail
|
126
126
|
|
127
|
-
canceled_endings =
|
128
|
-
bad_endings =
|
127
|
+
canceled_endings = [OrderStatus.Canceled, ]
|
128
|
+
bad_endings = [
|
129
129
|
OrderStatus.Rejected,
|
130
130
|
OrderStatus.Expired,
|
131
131
|
OrderStatus.PartialWithdrawal,
|
132
|
-
|
133
|
-
pending_cancel_sets =
|
132
|
+
]
|
133
|
+
pending_cancel_sets = [OrderStatus.PendingCancel, ]
|
134
134
|
|
135
135
|
if isinstance(lp_order, PushOrderChanged):
|
136
136
|
status = lp_order.status
|
@@ -146,7 +146,7 @@ class LongBridge(SecuritiesBroker):
|
|
146
146
|
return reason, is_canceled, is_pending_cancel
|
147
147
|
|
148
148
|
def _when_create_client(self):
|
149
|
-
from longport.openapi import PushOrderChanged, TopicType
|
149
|
+
from longport.openapi import PushOrderChanged, TopicType
|
150
150
|
|
151
151
|
def _on_order_changed(event: PushOrderChanged):
|
152
152
|
reason, is_canceled, is_pending_cancel = self._order_status(event)
|
@@ -0,0 +1,395 @@
|
|
1
|
+
"""
|
2
|
+
接入moomoo的API文档
|
3
|
+
https://openapi.moomoo.com/moomoo-api-doc/
|
4
|
+
"""
|
5
|
+
from datetime import datetime
|
6
|
+
from httptrading.broker.base import *
|
7
|
+
from httptrading.broker.futu_sec import Futu
|
8
|
+
from httptrading.model import *
|
9
|
+
from httptrading.tool.time import *
|
10
|
+
|
11
|
+
|
12
|
+
@broker_register(name='moomoo', display='moomoo', detect_pkg=DetectPkg('moomoo-api', 'moomoo'))
|
13
|
+
class Moomoo(Futu):
|
14
|
+
def _on_init(self):
|
15
|
+
from moomoo import SysConfig, OpenQuoteContext, OpenSecTradeContext, SecurityFirm, TrdMarket, TrdEnv
|
16
|
+
|
17
|
+
config_dict = self.broker_args
|
18
|
+
self._trd_env: str = config_dict.get('trade_env', TrdEnv.REAL) or TrdEnv.REAL
|
19
|
+
pk_path = config_dict.get('pk_path', '')
|
20
|
+
self._unlock_pin = config_dict.get('unlock_pin', '')
|
21
|
+
if pk_path:
|
22
|
+
SysConfig.enable_proto_encrypt(is_encrypt=True)
|
23
|
+
SysConfig.set_init_rsa_file(pk_path)
|
24
|
+
if self._trade_client is None:
|
25
|
+
SysConfig.set_all_thread_daemon(True)
|
26
|
+
host = config_dict.get('host', '127.0.0.1')
|
27
|
+
port = config_dict.get('port', 11111)
|
28
|
+
trade_ctx = OpenSecTradeContext(
|
29
|
+
filter_trdmarket=TrdMarket.US,
|
30
|
+
host=host,
|
31
|
+
port=port,
|
32
|
+
security_firm=SecurityFirm.FUTUINC,
|
33
|
+
)
|
34
|
+
trade_ctx.set_sync_query_connect_timeout(6.0)
|
35
|
+
self._trade_client = trade_ctx
|
36
|
+
self._when_create_client()
|
37
|
+
if self._quote_client is None:
|
38
|
+
SysConfig.set_all_thread_daemon(True)
|
39
|
+
host = config_dict.get('host', '127.0.0.1')
|
40
|
+
port = config_dict.get('port', 11111)
|
41
|
+
quote_ctx = OpenQuoteContext(host=host, port=port)
|
42
|
+
quote_ctx.set_sync_query_connect_timeout(6.0)
|
43
|
+
self._quote_client = quote_ctx
|
44
|
+
|
45
|
+
def _when_create_client(self):
|
46
|
+
from moomoo import TradeOrderHandlerBase, RET_OK, OpenSecTradeContext
|
47
|
+
|
48
|
+
client: OpenSecTradeContext = self._trade_client
|
49
|
+
|
50
|
+
def _on_recv_rsp(content):
|
51
|
+
for _futu_order in self._df_to_list(content):
|
52
|
+
try:
|
53
|
+
_order = self._build_order(_futu_order)
|
54
|
+
self.dump_order(_order)
|
55
|
+
except Exception as _ex:
|
56
|
+
print(f'[{self.__class__.__name__}]_on_recv_rsp: {_ex}\norder: {_futu_order}')
|
57
|
+
|
58
|
+
class TradeOrderHandler(TradeOrderHandlerBase):
|
59
|
+
def on_recv_rsp(self, rsp_pb):
|
60
|
+
ret, content = super().on_recv_rsp(rsp_pb)
|
61
|
+
if ret == RET_OK:
|
62
|
+
_on_recv_rsp(content)
|
63
|
+
return ret, content
|
64
|
+
|
65
|
+
client.set_handler(TradeOrderHandler())
|
66
|
+
|
67
|
+
async def start(self):
|
68
|
+
from moomoo import RET_OK, OpenSecTradeContext
|
69
|
+
|
70
|
+
client: OpenSecTradeContext = self._trade_client
|
71
|
+
if HtGlobalConfig.DUMP_ACTIVE_ORDERS:
|
72
|
+
try:
|
73
|
+
ret, data = client.order_list_query(
|
74
|
+
refresh_cache=True,
|
75
|
+
trd_env=self._trd_env,
|
76
|
+
)
|
77
|
+
except Exception as e:
|
78
|
+
print(f'[{self.__class__.__name__}]DUMP_ACTIVE_ORDERS: {e}')
|
79
|
+
else:
|
80
|
+
if ret == RET_OK:
|
81
|
+
futu_orders = self._df_to_list(data)
|
82
|
+
for futu_order in futu_orders:
|
83
|
+
try:
|
84
|
+
order = self._build_order(futu_order)
|
85
|
+
await self.call_sync(lambda : self.dump_order(order))
|
86
|
+
except Exception as ex:
|
87
|
+
print(f'[{self.__class__.__name__}]DUMP_ACTIVE_ORDERS: {ex}\norder: {futu_order}')
|
88
|
+
|
89
|
+
def _positions(self):
|
90
|
+
result = list()
|
91
|
+
from moomoo import RET_OK
|
92
|
+
with self._position_bucket:
|
93
|
+
resp, data = self._trade_client.position_list_query(
|
94
|
+
trd_env=self._trd_env,
|
95
|
+
refresh_cache=True,
|
96
|
+
)
|
97
|
+
if resp != RET_OK:
|
98
|
+
raise Exception(f'返回失败: {resp}')
|
99
|
+
positions = self._df_to_list(data)
|
100
|
+
for d in positions:
|
101
|
+
code = d.get('code')
|
102
|
+
currency = d.get('currency')
|
103
|
+
if not code or not currency:
|
104
|
+
continue
|
105
|
+
contract = self.code_to_contract(code)
|
106
|
+
if not contract:
|
107
|
+
continue
|
108
|
+
qty = int(d['qty'])
|
109
|
+
position = Position(
|
110
|
+
broker=self.broker_name,
|
111
|
+
broker_display=self.broker_display,
|
112
|
+
contract=contract,
|
113
|
+
unit=Unit.Share,
|
114
|
+
currency=currency,
|
115
|
+
qty=qty,
|
116
|
+
)
|
117
|
+
result.append(position)
|
118
|
+
return result
|
119
|
+
|
120
|
+
def _cash(self) -> Cash:
|
121
|
+
from moomoo import RET_OK, Currency
|
122
|
+
with self._assets_bucket:
|
123
|
+
resp, data = self._trade_client.accinfo_query(
|
124
|
+
trd_env=self._trd_env,
|
125
|
+
refresh_cache=True,
|
126
|
+
currency=Currency.USD,
|
127
|
+
)
|
128
|
+
if resp != RET_OK:
|
129
|
+
raise Exception(f'可用资金信息获取失败: {data}')
|
130
|
+
assets = self._df_to_list(data)
|
131
|
+
if len(assets) == 1:
|
132
|
+
cash = Cash(
|
133
|
+
currency='USD',
|
134
|
+
amount=assets[0]['cash'],
|
135
|
+
)
|
136
|
+
return cash
|
137
|
+
else:
|
138
|
+
raise Exception(f'可用资金信息获取不到记录')
|
139
|
+
|
140
|
+
def _market_status(self) -> dict[TradeType, dict[str, MarketStatus] | str]:
|
141
|
+
# 各个市场的状态定义见:
|
142
|
+
# https://openapi.futunn.com/futu-api-doc/qa/quote.html#2090
|
143
|
+
from moomoo import RET_OK
|
144
|
+
sec_result = dict()
|
145
|
+
region_map = {
|
146
|
+
'market_sh': 'CN',
|
147
|
+
'market_hk': 'HK',
|
148
|
+
'market_us': 'US',
|
149
|
+
}
|
150
|
+
status_map = {
|
151
|
+
'CLOSED': UnifiedStatus.CLOSED,
|
152
|
+
'PRE_MARKET_BEGIN': UnifiedStatus.PRE_HOURS,
|
153
|
+
'MORNING': UnifiedStatus.RTH,
|
154
|
+
'AFTERNOON': UnifiedStatus.RTH,
|
155
|
+
'AFTER_HOURS_BEGIN': UnifiedStatus.AFTER_HOURS,
|
156
|
+
'AFTER_HOURS_END': UnifiedStatus.CLOSED, # 根据文档, 盘后收盘时段跟夜盘时段重合
|
157
|
+
'OVERNIGHT': UnifiedStatus.OVERNIGHT,
|
158
|
+
# 这些映射是A股港股市场的映射
|
159
|
+
'REST': UnifiedStatus.REST,
|
160
|
+
'HK_CAS': UnifiedStatus.CLOSED,
|
161
|
+
}
|
162
|
+
client = self._quote_client
|
163
|
+
with self._market_status_bucket:
|
164
|
+
ret, data = client.get_global_state()
|
165
|
+
if ret != RET_OK:
|
166
|
+
raise Exception(f'市场状态接口调用失败: {data}')
|
167
|
+
for k, origin_status in data.items():
|
168
|
+
if k not in region_map:
|
169
|
+
continue
|
170
|
+
region = region_map[k]
|
171
|
+
unified_status = status_map.get(origin_status, UnifiedStatus.UNKNOWN)
|
172
|
+
sec_result[region] = MarketStatus(
|
173
|
+
region=region,
|
174
|
+
origin_status=origin_status,
|
175
|
+
unified_status=unified_status,
|
176
|
+
)
|
177
|
+
return {
|
178
|
+
TradeType.Securities: sec_result,
|
179
|
+
}
|
180
|
+
|
181
|
+
def _quote(self, contract: Contract):
|
182
|
+
from moomoo import RET_OK
|
183
|
+
tz = self.contract_to_tz(contract)
|
184
|
+
code = self.contract_to_code(contract)
|
185
|
+
currency = self.contract_to_currency(contract)
|
186
|
+
with self._snapshot_bucket:
|
187
|
+
ret, data = self._quote_client.get_market_snapshot([code, ])
|
188
|
+
if ret != RET_OK:
|
189
|
+
raise ValueError(f'快照接口调用失败: {data}')
|
190
|
+
table = self._df_to_list(data)
|
191
|
+
if len(table) != 1:
|
192
|
+
raise ValueError(f'快照接口调用无数据: {data}')
|
193
|
+
d = table[0]
|
194
|
+
"""
|
195
|
+
格式:yyyy-MM-dd HH:mm:ss
|
196
|
+
港股和 A 股市场默认是北京时间,美股市场默认是美东时间
|
197
|
+
"""
|
198
|
+
update_time: str = d['update_time']
|
199
|
+
update_dt = datetime.strptime(update_time, '%Y-%m-%d %H:%M:%S')
|
200
|
+
update_dt = TimeTools.from_params(
|
201
|
+
year=update_dt.year,
|
202
|
+
month=update_dt.month,
|
203
|
+
day=update_dt.day,
|
204
|
+
hour=update_dt.hour,
|
205
|
+
minute=update_dt.minute,
|
206
|
+
second=update_dt.second,
|
207
|
+
tz=tz,
|
208
|
+
)
|
209
|
+
is_tradable = d['sec_status'] == 'NORMAL'
|
210
|
+
return Quote(
|
211
|
+
contract=contract,
|
212
|
+
currency=currency,
|
213
|
+
is_tradable=is_tradable,
|
214
|
+
latest=d['last_price'],
|
215
|
+
pre_close=d['prev_close_price'],
|
216
|
+
open_price=d['open_price'],
|
217
|
+
high_price=d['high_price'],
|
218
|
+
low_price=d['low_price'],
|
219
|
+
time=update_dt,
|
220
|
+
)
|
221
|
+
|
222
|
+
def _try_unlock(self):
|
223
|
+
from moomoo import RET_OK, TrdEnv
|
224
|
+
if self._trd_env != TrdEnv.REAL:
|
225
|
+
return
|
226
|
+
if not self._unlock_pin:
|
227
|
+
return
|
228
|
+
with self._unlock_bucket:
|
229
|
+
ret, data = self._trade_client.unlock_trade(password_md5=self._unlock_pin)
|
230
|
+
if ret != RET_OK:
|
231
|
+
raise Exception(f'解锁交易失败: {data}')
|
232
|
+
|
233
|
+
def _place_order(
|
234
|
+
self,
|
235
|
+
contract: Contract,
|
236
|
+
order_type: OrderType,
|
237
|
+
time_in_force: TimeInForce,
|
238
|
+
lifecycle: Lifecycle,
|
239
|
+
direction: str,
|
240
|
+
qty: int,
|
241
|
+
price: float = None,
|
242
|
+
full_args: dict = None,
|
243
|
+
**kwargs
|
244
|
+
) -> str:
|
245
|
+
from moomoo import RET_OK, TrdSide, OrderType as FutuOrderType, TimeInForce as FutuTimeInForce, Session
|
246
|
+
if contract.trade_type != TradeType.Securities:
|
247
|
+
raise Exception(f'不支持的下单品种: {contract.trade_type}')
|
248
|
+
if contract.region == 'US' and order_type == OrderType.Market and lifecycle != Lifecycle.RTH:
|
249
|
+
raise Exception(f'交易时段不支持市价单')
|
250
|
+
code = self.contract_to_code(contract)
|
251
|
+
assert qty > 0
|
252
|
+
assert price > 0
|
253
|
+
|
254
|
+
def _map_trade_side():
|
255
|
+
match direction:
|
256
|
+
case 'BUY':
|
257
|
+
return TrdSide.BUY
|
258
|
+
case 'SELL':
|
259
|
+
return TrdSide.SELL
|
260
|
+
case _:
|
261
|
+
raise Exception(f'不支持的买卖方向: {direction}')
|
262
|
+
|
263
|
+
def _map_order_type():
|
264
|
+
match order_type:
|
265
|
+
case OrderType.Market:
|
266
|
+
return FutuOrderType.MARKET
|
267
|
+
case OrderType.Limit:
|
268
|
+
return FutuOrderType.NORMAL
|
269
|
+
case _:
|
270
|
+
raise Exception(f'不支持的订单类型: {order_type}')
|
271
|
+
|
272
|
+
def _map_time_in_force():
|
273
|
+
match time_in_force:
|
274
|
+
case TimeInForce.DAY:
|
275
|
+
return FutuTimeInForce.DAY
|
276
|
+
case TimeInForce.GTC:
|
277
|
+
return FutuTimeInForce.GTC
|
278
|
+
case _:
|
279
|
+
raise Exception(f'不支持的订单有效期: {time_in_force}')
|
280
|
+
|
281
|
+
def _map_lifecycle():
|
282
|
+
match lifecycle:
|
283
|
+
case Lifecycle.RTH:
|
284
|
+
return Session.RTH
|
285
|
+
case Lifecycle.ETH:
|
286
|
+
return Session.ETH
|
287
|
+
case Lifecycle.OVERNIGHT:
|
288
|
+
return Session.OVERNIGHT
|
289
|
+
case _:
|
290
|
+
raise Exception(f'不支持的交易时段: {lifecycle}')
|
291
|
+
|
292
|
+
self._try_unlock()
|
293
|
+
with self._place_order_bucket:
|
294
|
+
ret, data = self._trade_client.place_order(
|
295
|
+
code=code,
|
296
|
+
price=price or 10.0, # 富途必须要填充这个字段
|
297
|
+
qty=qty,
|
298
|
+
trd_side=_map_trade_side(),
|
299
|
+
order_type=_map_order_type(),
|
300
|
+
time_in_force=_map_time_in_force(),
|
301
|
+
trd_env=self._trd_env,
|
302
|
+
session=_map_lifecycle(),
|
303
|
+
)
|
304
|
+
if ret != RET_OK:
|
305
|
+
raise Exception(f'下单失败: {data}')
|
306
|
+
orders = self._df_to_list(data)
|
307
|
+
assert len(orders) == 1
|
308
|
+
order_id = orders[0]['order_id']
|
309
|
+
assert order_id
|
310
|
+
return order_id
|
311
|
+
|
312
|
+
@classmethod
|
313
|
+
def _build_order(cls, futu_order: dict) -> Order:
|
314
|
+
from moomoo import OrderStatus
|
315
|
+
"""
|
316
|
+
富途证券的状态定义
|
317
|
+
NONE = "N/A" # 未知状态
|
318
|
+
UNSUBMITTED = "UNSUBMITTED" # 未提交
|
319
|
+
WAITING_SUBMIT = "WAITING_SUBMIT" # 等待提交
|
320
|
+
SUBMITTING = "SUBMITTING" # 提交中
|
321
|
+
SUBMIT_FAILED = "SUBMIT_FAILED" # 提交失败,下单失败
|
322
|
+
TIMEOUT = "TIMEOUT" # 处理超时,结果未知
|
323
|
+
SUBMITTED = "SUBMITTED" # 已提交,等待成交
|
324
|
+
FILLED_PART = "FILLED_PART" # 部分成交
|
325
|
+
FILLED_ALL = "FILLED_ALL" # 全部已成
|
326
|
+
CANCELLING_PART = "CANCELLING_PART" # 正在撤单_部分(部分已成交,正在撤销剩余部分)
|
327
|
+
CANCELLING_ALL = "CANCELLING_ALL" # 正在撤单_全部
|
328
|
+
CANCELLED_PART = "CANCELLED_PART" # 部分成交,剩余部分已撤单
|
329
|
+
CANCELLED_ALL = "CANCELLED_ALL" # 全部已撤单,无成交
|
330
|
+
FAILED = "FAILED" # 下单失败,服务拒绝
|
331
|
+
DISABLED = "DISABLED" # 已失效
|
332
|
+
DELETED = "DELETED" # 已删除,无成交的订单才能删除
|
333
|
+
FILL_CANCELLED = "FILL_CANCELLED" # 成交被撤销,一般遇不到,意思是已经成交的订单被回滚撤销,成交无效变为废单
|
334
|
+
"""
|
335
|
+
canceled_endings = {OrderStatus.CANCELLED_ALL, OrderStatus.CANCELLED_PART, }
|
336
|
+
bad_endings = {
|
337
|
+
OrderStatus.SUBMIT_FAILED,
|
338
|
+
OrderStatus.FAILED,
|
339
|
+
OrderStatus.DISABLED,
|
340
|
+
OrderStatus.DELETED,
|
341
|
+
OrderStatus.FILL_CANCELLED, # 不清楚对于成交数量有何影响.
|
342
|
+
}
|
343
|
+
pending_cancel_sets = {OrderStatus.CANCELLING_PART, OrderStatus.CANCELLING_ALL, }
|
344
|
+
|
345
|
+
order_id = futu_order['order_id']
|
346
|
+
reason = ''
|
347
|
+
order_status: str = futu_order['order_status']
|
348
|
+
if order_status in bad_endings:
|
349
|
+
reason = order_status
|
350
|
+
is_canceled = order_status in canceled_endings
|
351
|
+
is_pending_cancel = order_status in pending_cancel_sets
|
352
|
+
return Order(
|
353
|
+
order_id=order_id,
|
354
|
+
currency=futu_order['currency'],
|
355
|
+
qty=int(futu_order['qty']),
|
356
|
+
filled_qty=int(futu_order['dealt_qty']),
|
357
|
+
avg_price=futu_order['dealt_avg_price'] or 0.0,
|
358
|
+
error_reason=reason,
|
359
|
+
is_canceled=is_canceled,
|
360
|
+
is_pending_cancel=is_pending_cancel,
|
361
|
+
)
|
362
|
+
|
363
|
+
def _order(self, order_id: str) -> Order:
|
364
|
+
from moomoo import RET_OK
|
365
|
+
|
366
|
+
with self._refresh_order_bucket:
|
367
|
+
ret, data = self._trade_client.order_list_query(
|
368
|
+
order_id=order_id,
|
369
|
+
refresh_cache=True,
|
370
|
+
trd_env=self._trd_env,
|
371
|
+
)
|
372
|
+
if ret != RET_OK:
|
373
|
+
raise Exception(f'调用获取订单失败, 订单: {order_id}')
|
374
|
+
orders = self._df_to_list(data)
|
375
|
+
if len(orders) != 1:
|
376
|
+
raise Exception(f'找不到订单(未完成), 订单: {order_id}')
|
377
|
+
futu_order = orders[0]
|
378
|
+
return self._build_order(futu_order)
|
379
|
+
|
380
|
+
def _cancel_order(self, order_id: str):
|
381
|
+
from moomoo import RET_OK, ModifyOrderOp
|
382
|
+
self._try_unlock()
|
383
|
+
with self._cancel_order_bucket:
|
384
|
+
ret, data = self._trade_client.modify_order(
|
385
|
+
modify_order_op=ModifyOrderOp.CANCEL,
|
386
|
+
order_id=order_id,
|
387
|
+
qty=100,
|
388
|
+
price=10.0,
|
389
|
+
trd_env=self._trd_env,
|
390
|
+
)
|
391
|
+
if ret != RET_OK:
|
392
|
+
raise Exception(f'撤单失败, 订单: {order_id}, 原因: {data}')
|
393
|
+
|
394
|
+
|
395
|
+
__all__ = ['Moomoo', ]
|
@@ -14,6 +14,7 @@ httptrading/broker/base.py
|
|
14
14
|
httptrading/broker/futu_sec.py
|
15
15
|
httptrading/broker/interactive_brokers.py
|
16
16
|
httptrading/broker/longbridge.py
|
17
|
+
httptrading/broker/moomoo_sec.py
|
17
18
|
httptrading/broker/tiger.py
|
18
19
|
httptrading/tool/__init__.py
|
19
20
|
httptrading/tool/leaky_bucket.py
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|