httptrading 1.0.6__py3-none-any.whl → 1.0.8__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
@@ -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 = {OrderStatus.Canceled, }
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 = {OrderStatus.PendingCancel, }
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, OrderStatus
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', ]
httptrading/model.py CHANGED
@@ -7,6 +7,9 @@ from dataclasses import dataclass, field
7
7
  class TradeType(enum.Enum):
8
8
  Securities = enum.auto()
9
9
  Cryptocurrencies = enum.auto()
10
+ Indexes = enum.auto() # 指数
11
+ Currencies = enum.auto() # 货币对
12
+ Yields = enum.auto() # 利率
10
13
 
11
14
 
12
15
  class Unit(enum.Enum):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httptrading
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: 统一交易通道的接口服务
5
5
  Author-email: songwei <github@songwei.name>
6
6
  License: MIT
@@ -1,18 +1,19 @@
1
- httptrading/__init__.py,sha256=H2kepS94z3YQWMyvM7R3WVbP4iDYgwGssLcYG24dnPQ,306
1
+ httptrading/__init__.py,sha256=UCsukrBlPmdDKkxN7WQQN0uRLdeAUOu6GXoKW8LVUM0,351
2
2
  httptrading/http_server.py,sha256=jj6mVhKly5C6fT1Ew6AMu4rG-7rlofs5FSvcCeIMJmk,9124
3
- httptrading/model.py,sha256=2oWz3VIIMEiIEX6Jg4itBYdZtJbKEgTMzudvVk1cIJ8,7717
3
+ httptrading/model.py,sha256=EaQoKeMJ_gluRFxWoUjSDwAYzX0cSU0UsdzPtCN8T1U,7834
4
4
  httptrading/broker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  httptrading/broker/base.py,sha256=P149UlFyy277y40qHPH9DdvbEy-j1x26QDllXV6XPMg,5664
6
6
  httptrading/broker/futu_sec.py,sha256=ztK_vpKAs7qNIfNXcqv_FBWs4gvxoMdaWNjGfb7vodE,20081
7
- httptrading/broker/interactive_brokers.py,sha256=Myl9dvPKRJ6EQ5nDFYzCLdkGc2k-wpzP6ISlImXkcaY,13218
8
- httptrading/broker/longbridge.py,sha256=x_QqfiloCJ7bQl47zc6lXmApbRjA782LVOkdfqeYVpI,16282
7
+ httptrading/broker/interactive_brokers.py,sha256=iOqR-9t3l1YPlTUlnQ2_bTWmesSQC6DHCfUJLD4rD9o,13239
8
+ httptrading/broker/longbridge.py,sha256=le3KvXb7LzYz0m170E5VH6ip1Mxuy7AbzSQ6HaA7r-A,16269
9
+ httptrading/broker/moomoo_sec.py,sha256=Wsv-pNcHDyoOzsMkVn-faxU4oen3mqbevsxOs7uKWhw,16118
9
10
  httptrading/broker/tiger.py,sha256=tJpkZEayInOlxo2FSKVIHp7nXWBsVC4i6qrSsciNYeA,16226
10
11
  httptrading/tool/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
12
  httptrading/tool/leaky_bucket.py,sha256=Bvab3Fkn16hhZ1-WLCFcPkbFH_bHHhQ9boLv8HrmGSE,2817
12
13
  httptrading/tool/locate.py,sha256=vSIzd09FWKmckkgY3mWtFXQm2Z0VIKv4FCXHT44e61s,2748
13
14
  httptrading/tool/time.py,sha256=7eVmZ_td72JLjsBRLjMOHklxltNbOxeN97uSLi7wvIA,2188
14
- httptrading-1.0.6.dist-info/licenses/LICENSE,sha256=KfMSrfnpo-TOqpCTJqnbcZNl0w7ErxadsMQf8uas_tY,1093
15
- httptrading-1.0.6.dist-info/METADATA,sha256=wtg2mMM7OJ4IsILR9IEt48Zn645wVyTITqkoC6sqWvs,17519
16
- httptrading-1.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
- httptrading-1.0.6.dist-info/top_level.txt,sha256=rsLGrGN6QubO9ILQRktwrWtxfGsGAmWUnQq7XksKu-4,12
18
- httptrading-1.0.6.dist-info/RECORD,,
15
+ httptrading-1.0.8.dist-info/licenses/LICENSE,sha256=KfMSrfnpo-TOqpCTJqnbcZNl0w7ErxadsMQf8uas_tY,1093
16
+ httptrading-1.0.8.dist-info/METADATA,sha256=n68WI85Maif_xIyFYKnYNKB6fcyeOrIzyhY1Zc9NTbQ,17519
17
+ httptrading-1.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
+ httptrading-1.0.8.dist-info/top_level.txt,sha256=rsLGrGN6QubO9ILQRktwrWtxfGsGAmWUnQq7XksKu-4,12
19
+ httptrading-1.0.8.dist-info/RECORD,,