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/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.1
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
- "type": "apiResponse",
411
- "instanceId": "ggUqPZbSKuQ7Ewsk",
412
- "broker": "futu",
413
- "brokerDisplay": "富途证券",
414
- "time": "2025-05-28T05:59:29.984021+00:00",
415
- "ex": null,
416
- "order": {
417
- "type": "order",
418
- "orderId": "6278888",
419
- "currency": "USD",
420
- "qty": 12, // 订单数量
421
- "filledQty": 0, // 已成交数量
422
- "avgPrice": 0, // 成交价
423
- "errorReason": "", // 如果订单异常, 这里记录错误信息
424
- "isCanceled": false, // 是否已撤销
425
- "isFilled": false, // 是否全部成交
426
- "isCompleted": false // 全部成交 或者 有异常 或者 已撤销, 亦等价于不可撤的标志
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('myApi', 'XX证券')
507
- class MyTradingApi(BaseBroker):
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[str, dict[str, MarketStatus]]:
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,,