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 +3 -2
- httptrading/broker/base.py +24 -1
- httptrading/broker/{futu.py → futu_sec.py} +106 -21
- httptrading/broker/interactive_brokers.py +50 -26
- httptrading/broker/longbridge.py +55 -9
- httptrading/broker/tiger.py +63 -3
- httptrading/http_server.py +268 -297
- httptrading/model.py +87 -0
- {httptrading-1.0.1.dist-info → httptrading-1.0.3.dist-info}/METADATA +93 -25
- httptrading-1.0.3.dist-info/RECORD +18 -0
- httptrading-1.0.1.dist-info/RECORD +0 -18
- {httptrading-1.0.1.dist-info → httptrading-1.0.3.dist-info}/WHEEL +0 -0
- {httptrading-1.0.1.dist-info → httptrading-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {httptrading-1.0.1.dist-info → httptrading-1.0.3.dist-info}/top_level.txt +0 -0
httptrading/model.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import enum
|
2
|
+
from typing import Type
|
2
3
|
from datetime import datetime
|
3
4
|
from dataclasses import dataclass, field
|
4
5
|
|
@@ -119,6 +120,9 @@ class Order:
|
|
119
120
|
avg_price: float = field(default=0.0)
|
120
121
|
error_reason: str = field(default='')
|
121
122
|
is_canceled: bool = field(default=False)
|
123
|
+
# 如果交易通道存在"待取消""已提交取消"的订单状态,
|
124
|
+
# 这里需要改变默认值为 True
|
125
|
+
is_pending_cancel: bool = field(default=False)
|
122
126
|
|
123
127
|
@property
|
124
128
|
def is_filled(self) -> bool:
|
@@ -138,6 +142,11 @@ class Order:
|
|
138
142
|
is_completed = True
|
139
143
|
return is_completed
|
140
144
|
|
145
|
+
@property
|
146
|
+
def is_cancelable(self) -> bool:
|
147
|
+
is_completed = self.is_completed
|
148
|
+
return not is_completed and not self.is_pending_cancel
|
149
|
+
|
141
150
|
|
142
151
|
@dataclass(frozen=True)
|
143
152
|
class DetectPkg:
|
@@ -156,6 +165,82 @@ class BrokerMeta:
|
|
156
165
|
detect_package: DetectPkg = None
|
157
166
|
|
158
167
|
|
168
|
+
class JsonDefault:
|
169
|
+
@classmethod
|
170
|
+
def json_default(cls, obj):
|
171
|
+
if isinstance(obj, Position):
|
172
|
+
return {
|
173
|
+
'type': 'position',
|
174
|
+
'broker': obj.broker,
|
175
|
+
'brokerDisplay': obj.broker_display,
|
176
|
+
'contract': cls.json_default(obj.contract),
|
177
|
+
'unit': obj.unit.name,
|
178
|
+
'currency': obj.currency,
|
179
|
+
'qty': obj.qty,
|
180
|
+
}
|
181
|
+
if isinstance(obj, Contract):
|
182
|
+
return {
|
183
|
+
'type': 'contract',
|
184
|
+
'tradeType': obj.trade_type.name,
|
185
|
+
'region': obj.region,
|
186
|
+
'ticker': obj.ticker,
|
187
|
+
}
|
188
|
+
if isinstance(obj, Cash):
|
189
|
+
return {
|
190
|
+
'type': 'cash',
|
191
|
+
'currency': obj.currency,
|
192
|
+
'amount': obj.amount,
|
193
|
+
}
|
194
|
+
if isinstance(obj, MarketStatus):
|
195
|
+
return {
|
196
|
+
'type': 'marketStatus',
|
197
|
+
'region': obj.region,
|
198
|
+
'originStatus': obj.origin_status,
|
199
|
+
'unifiedStatus': obj.unified_status.name,
|
200
|
+
}
|
201
|
+
if isinstance(obj, Quote):
|
202
|
+
return {
|
203
|
+
'type': 'quote',
|
204
|
+
'contract': cls.json_default(obj.contract),
|
205
|
+
'currency': obj.currency,
|
206
|
+
'isTradable': obj.is_tradable,
|
207
|
+
'latest': obj.latest,
|
208
|
+
'preClose': obj.pre_close,
|
209
|
+
'highPrice': obj.high_price,
|
210
|
+
'lowPrice': obj.low_price,
|
211
|
+
'openPrice': obj.open_price,
|
212
|
+
'timestamp': int(obj.time.timestamp() * 1000),
|
213
|
+
}
|
214
|
+
if isinstance(obj, Order):
|
215
|
+
return {
|
216
|
+
'type': 'order',
|
217
|
+
'orderId': obj.order_id,
|
218
|
+
'currency': obj.currency,
|
219
|
+
'qty': obj.qty,
|
220
|
+
'filledQty': obj.filled_qty,
|
221
|
+
'avgPrice': obj.avg_price,
|
222
|
+
'errorReason': obj.error_reason,
|
223
|
+
'isCanceled': obj.is_canceled,
|
224
|
+
'isFilled': obj.is_filled,
|
225
|
+
'isCompleted': obj.is_completed,
|
226
|
+
'isCancelable': obj.is_cancelable,
|
227
|
+
}
|
228
|
+
raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
|
229
|
+
|
230
|
+
|
231
|
+
class HtGlobalConfig:
|
232
|
+
# 提供一个静态文件目录, 使得当收到推送的订单时将订单落盘为 json 文件.
|
233
|
+
# 对于一些没有提供永久查询单个订单接口的交易通道, 访问静态文件可以是查询历史订单的击穿方法.
|
234
|
+
# 注意, 落盘的文件名是"{实例ID}-{订单号}.json"格式, 不确定同样一家交易通道, 不同的交易品种之间是否会撞号.
|
235
|
+
# 例如, 假如平台先做的证券交易, 后面组了一个期货团队, 然后开放接口团队接入的是两套没有交集的订单系统, 没做订单号码二次映射.
|
236
|
+
STREAM_DUMP_FOLDER: str = None
|
237
|
+
# 对于一些没有提供永久查询单个订单接口的交易通道, 可以在启动服务时把当时的活动订单更新一遍.
|
238
|
+
# 比如重启服务后担心漏掉订单的推送.
|
239
|
+
DUMP_ACTIVE_ORDERS: bool = False
|
240
|
+
# 如果需要定制接口返回对象的行为, 这里替换为自定义的类
|
241
|
+
JSON_DEFAULT: Type[JsonDefault] = JsonDefault
|
242
|
+
|
243
|
+
|
159
244
|
__all__ = [
|
160
245
|
'TradeType',
|
161
246
|
'Unit',
|
@@ -171,4 +256,6 @@ __all__ = [
|
|
171
256
|
'Order',
|
172
257
|
'DetectPkg',
|
173
258
|
'BrokerMeta',
|
259
|
+
'JsonDefault',
|
260
|
+
'HtGlobalConfig',
|
174
261
|
]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: httptrading
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.3
|
4
4
|
Summary: 统一交易通道的接口服务
|
5
5
|
Author-email: songwei <github@songwei.name>
|
6
6
|
License: MIT
|
@@ -10,6 +10,7 @@ License-File: LICENSE
|
|
10
10
|
Requires-Dist: aiohttp>=3.12.6
|
11
11
|
Requires-Dist: humanize>=4.12.3
|
12
12
|
Requires-Dist: tomlkit>=0.13.2
|
13
|
+
Requires-Dist: nest-asyncio>=1.6.0
|
13
14
|
Dynamic: license-file
|
14
15
|
|
15
16
|
# httptrading
|
@@ -397,6 +398,10 @@ POST /httptrading/api/{instanceId}/order/cancel
|
|
397
398
|
}
|
398
399
|
```
|
399
400
|
|
401
|
+
```
|
402
|
+
⚠️ 撤单接口仅完成对交易通道的撤单接口调用, 不代表在较短时间后订单可以进入撤销的状态. 一个例子是假日发起撤单, 通道不一定执行撤单而是进入已请求撤单的状态.
|
403
|
+
```
|
404
|
+
|
400
405
|
|
401
406
|
### 查询单个订单
|
402
407
|
|
@@ -407,30 +412,35 @@ GET /httptrading/api/{instanceId}/order/state?orderId={订单号}
|
|
407
412
|
|
408
413
|
```json lines
|
409
414
|
{
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
415
|
+
"type": "apiResponse",
|
416
|
+
"instanceId": "ggUqPZbSKuQ7Ewsk",
|
417
|
+
"broker": "futu",
|
418
|
+
"brokerDisplay": "富途证券",
|
419
|
+
"time": "2025-05-28T05:59:29.984021+00:00",
|
420
|
+
"ex": null,
|
421
|
+
"order": {
|
422
|
+
"type": "order",
|
423
|
+
"orderId": "6278888",
|
424
|
+
"currency": "USD",
|
425
|
+
"qty": 12, // 订单数量
|
426
|
+
"filledQty": 0, // 已成交数量
|
427
|
+
"avgPrice": 0, // 成交价
|
428
|
+
"errorReason": "", // 如果订单异常, 这里记录错误信息
|
429
|
+
"isCanceled": false, // 是否已撤销
|
430
|
+
"isFilled": false, // 是否全部成交
|
431
|
+
"isCompleted": false, // 全部成交 或者 有订单异常 或者 已撤销
|
432
|
+
"isCancelable": true // 是否可撤的标志, 等价于 not isCompleted and not isPendingCancel
|
433
|
+
}
|
428
434
|
}
|
429
435
|
```
|
436
|
+
⚠️ 有些交易通道不支持查询单个订单状态, 而是查询活动订单的列表来实现, 意味着订单在结束周期的交易日之后, 将查不到订单.
|
430
437
|
|
431
|
-
|
432
|
-
|
433
|
-
|
438
|
+
| 交易通道 | 支持查单个订单 |
|
439
|
+
|------|---------|
|
440
|
+
| 盈透证券 | ❌ |
|
441
|
+
| 富途证券 | ❌ |
|
442
|
+
| 长桥证券 | ✅ |
|
443
|
+
| 老虎证券 | ✅ |
|
434
444
|
|
435
445
|
交易通道的参数
|
436
446
|
------------
|
@@ -503,8 +513,8 @@ from httptrading import *
|
|
503
513
|
from httptrading.model import *
|
504
514
|
|
505
515
|
|
506
|
-
@broker_register('
|
507
|
-
class
|
516
|
+
@broker_register('myBroker', 'XX证券')
|
517
|
+
class MyBroker(BaseBroker):
|
508
518
|
# 根据需要的功能实现接口
|
509
519
|
# 如果 sdk 提供的方式会阻塞 eventloop, 需要使用 self.call_sync 方法传入阻塞方法
|
510
520
|
async def place_order(
|
@@ -538,6 +548,64 @@ class MyTradingApi(BaseBroker):
|
|
538
548
|
async def quote(self, contract: Contract) -> Quote:
|
539
549
|
raise NotImplementedError
|
540
550
|
|
541
|
-
async def market_status(self) -> dict[
|
551
|
+
async def market_status(self) -> dict[TradeType, dict[str, MarketStatus] | str]:
|
542
552
|
raise NotImplementedError
|
543
553
|
```
|
554
|
+
|
555
|
+
|
556
|
+
编写新接口
|
557
|
+
--------
|
558
|
+
|
559
|
+
```python
|
560
|
+
import aiohttp.web
|
561
|
+
import httptrading
|
562
|
+
|
563
|
+
class MyApi(httptrading.HttpTradingView):
|
564
|
+
async def get(self):
|
565
|
+
broker = self.current_broker()
|
566
|
+
return self.response_api(
|
567
|
+
broker=broker,
|
568
|
+
args={
|
569
|
+
'method': 'GET',
|
570
|
+
'hello': 'world',
|
571
|
+
},
|
572
|
+
)
|
573
|
+
|
574
|
+
async def post(self):
|
575
|
+
body_d: dict = await self.request.json()
|
576
|
+
broker = self.current_broker()
|
577
|
+
return self.response_api(
|
578
|
+
broker=broker,
|
579
|
+
args={
|
580
|
+
'method': 'POST',
|
581
|
+
'hello': 'world',
|
582
|
+
'body': body_d,
|
583
|
+
},
|
584
|
+
)
|
585
|
+
|
586
|
+
httptrading.run(
|
587
|
+
host='127.0.0.1',
|
588
|
+
port=8080,
|
589
|
+
brokers=list(),
|
590
|
+
extend_apis=[aiohttp.web.view(r'/httptrading/api/{instance_id:\w{16,32}}/hello/world', MyApi), ],
|
591
|
+
)
|
592
|
+
```
|
593
|
+
|
594
|
+
改变默认的接口
|
595
|
+
-----------
|
596
|
+
|
597
|
+
```python
|
598
|
+
import httptrading
|
599
|
+
|
600
|
+
def my_std_apis():
|
601
|
+
std_apis = httptrading.std_api_factory()
|
602
|
+
apis = [api for api in std_apis if 1 == 1]
|
603
|
+
return apis
|
604
|
+
|
605
|
+
httptrading.run(
|
606
|
+
host='127.0.0.1',
|
607
|
+
port=8080,
|
608
|
+
brokers=list(),
|
609
|
+
std_apis=my_std_apis,
|
610
|
+
)
|
611
|
+
```
|
@@ -0,0 +1,18 @@
|
|
1
|
+
httptrading/__init__.py,sha256=H2kepS94z3YQWMyvM7R3WVbP4iDYgwGssLcYG24dnPQ,306
|
2
|
+
httptrading/http_server.py,sha256=K2_KVZ5dU5UZilLtx51OYVRClrf_nTThIBVIDmU_v3k,8718
|
3
|
+
httptrading/model.py,sha256=xa-psfYJuEzyR5aB776ErlX1CjhXDsAE2LuR6XI6h2I,7717
|
4
|
+
httptrading/broker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
httptrading/broker/base.py,sha256=31oS32afiLG7aQa_x-6HwfDAAb2V67AFi3V497-7R0E,5627
|
6
|
+
httptrading/broker/futu_sec.py,sha256=eYYBqx62w31tUBuMVhMkpTs5RWi7BMfYnywv1L-GTds,19973
|
7
|
+
httptrading/broker/interactive_brokers.py,sha256=jyMNy2HZsLJiSJs77tZBI-XmICdOwcn1Rje4U9gwP-w,13232
|
8
|
+
httptrading/broker/longbridge.py,sha256=whavvn4gC0K-iHy1cf4W0MjZxdG-t-JY1Gf9QeD5ioI,16111
|
9
|
+
httptrading/broker/tiger.py,sha256=6z3DA888bcFf_r1_NraIAzODQrv_BCP0CnqyOddZDT8,16177
|
10
|
+
httptrading/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
httptrading/tool/leaky_bucket.py,sha256=Bvab3Fkn16hhZ1-WLCFcPkbFH_bHHhQ9boLv8HrmGSE,2817
|
12
|
+
httptrading/tool/locate.py,sha256=vSIzd09FWKmckkgY3mWtFXQm2Z0VIKv4FCXHT44e61s,2748
|
13
|
+
httptrading/tool/time.py,sha256=7eVmZ_td72JLjsBRLjMOHklxltNbOxeN97uSLi7wvIA,2188
|
14
|
+
httptrading-1.0.3.dist-info/licenses/LICENSE,sha256=KfMSrfnpo-TOqpCTJqnbcZNl0w7ErxadsMQf8uas_tY,1093
|
15
|
+
httptrading-1.0.3.dist-info/METADATA,sha256=mDEuve30H-9uTJ_CLI4NpOfoHeK99O2GrR3-Mzp-icY,17519
|
16
|
+
httptrading-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
17
|
+
httptrading-1.0.3.dist-info/top_level.txt,sha256=rsLGrGN6QubO9ILQRktwrWtxfGsGAmWUnQq7XksKu-4,12
|
18
|
+
httptrading-1.0.3.dist-info/RECORD,,
|
@@ -1,18 +0,0 @@
|
|
1
|
-
httptrading/__init__.py,sha256=NIcpQPR6bj9_p0yN5RRaLvx5Q5zSfDekLp3fYLQY78A,258
|
2
|
-
httptrading/http_server.py,sha256=x9jrMaTwoOHFbivJRj9-OPuEg_L4gBJn-rR3ol4LMzA,9757
|
3
|
-
httptrading/model.py,sha256=_j4NsBF5Ra5ach9ZMg_ZsBU6c57doYTOy7kKxHMaECo,3949
|
4
|
-
httptrading/broker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
httptrading/broker/base.py,sha256=fqxfLTVavHxfFZDPt9GqhjT_C_9n_xcERahxbeuDywU,4923
|
6
|
-
httptrading/broker/futu.py,sha256=RSEP8PrevY53QitfH9X4rc9X07gahXYpEDGXRqoEtFI,15934
|
7
|
-
httptrading/broker/interactive_brokers.py,sha256=i-O7_22dklakAwfqqh9zThmKLABOxAJrPsZbo48Qq-s,12151
|
8
|
-
httptrading/broker/longbridge.py,sha256=alpPmPz_DbbWvg4SbL9-5WLpgzLHVk1SgXVcV6rOl4U,14139
|
9
|
-
httptrading/broker/tiger.py,sha256=mSvxLXOh6CrVFdx_ELrkX3k59csyLKYTXDRrmj4V8ms,13600
|
10
|
-
httptrading/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
httptrading/tool/leaky_bucket.py,sha256=Bvab3Fkn16hhZ1-WLCFcPkbFH_bHHhQ9boLv8HrmGSE,2817
|
12
|
-
httptrading/tool/locate.py,sha256=vSIzd09FWKmckkgY3mWtFXQm2Z0VIKv4FCXHT44e61s,2748
|
13
|
-
httptrading/tool/time.py,sha256=7eVmZ_td72JLjsBRLjMOHklxltNbOxeN97uSLi7wvIA,2188
|
14
|
-
httptrading-1.0.1.dist-info/licenses/LICENSE,sha256=KfMSrfnpo-TOqpCTJqnbcZNl0w7ErxadsMQf8uas_tY,1093
|
15
|
-
httptrading-1.0.1.dist-info/METADATA,sha256=FQjVneFtsS7jkyQ1NzpDdXHHic95I2tt3-qEw5-msLI,15664
|
16
|
-
httptrading-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
17
|
-
httptrading-1.0.1.dist-info/top_level.txt,sha256=rsLGrGN6QubO9ILQRktwrWtxfGsGAmWUnQq7XksKu-4,12
|
18
|
-
httptrading-1.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|