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/http_server.py
CHANGED
@@ -1,326 +1,268 @@
|
|
1
|
-
import json
|
2
|
-
|
3
|
-
from
|
4
|
-
from
|
5
|
-
from
|
6
|
-
from httptrading.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
if
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
'
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
)
|
148
|
-
|
149
|
-
|
150
|
-
'
|
151
|
-
})
|
152
|
-
|
153
|
-
|
154
|
-
class
|
155
|
-
async def get(self):
|
156
|
-
broker = self.current_broker()
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
return
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
})
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
@
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/market/state', MarketStatusView),
|
269
|
-
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/market/quote', QuoteView),
|
270
|
-
]
|
271
|
-
return apis
|
272
|
-
|
273
|
-
def run(
|
274
|
-
host: str,
|
275
|
-
port: int,
|
276
|
-
brokers: list[BaseBroker],
|
277
|
-
std_apis: Callable[[], list[web.RouteDef]] = None,
|
278
|
-
extend_apis: list[web.RouteDef] = None,
|
279
|
-
**kwargs
|
280
|
-
) -> None:
|
281
|
-
"""
|
282
|
-
@param host: 监听地址
|
283
|
-
@param port: 监听端口
|
284
|
-
@param brokers: 需要控制的交易通道对象列表
|
285
|
-
@param std_apis: 如果需要替换默认提供的接口, 这里提供工厂函数的回调
|
286
|
-
@param extend_apis: 如果需要增加自定义接口, 这里传入 RouteDef 列表
|
287
|
-
@param kwargs: 其他的参数将传给 aiohttp.web.run_app 函数
|
288
|
-
"""
|
289
|
-
app = web.Application(
|
290
|
-
middlewares=[
|
291
|
-
auth_middleware,
|
292
|
-
exception_middleware,
|
293
|
-
],
|
294
|
-
)
|
295
|
-
|
296
|
-
apis = (std_api_factory if std_apis is None else std_apis)()
|
297
|
-
|
298
|
-
if extend_apis:
|
299
|
-
apis.extend(extend_apis)
|
300
|
-
|
301
|
-
app.add_routes(apis)
|
302
|
-
|
303
|
-
async def _on_startup(app):
|
304
|
-
HttpTradingView.set_brokers(brokers)
|
305
|
-
for broker in brokers:
|
306
|
-
await broker.start()
|
307
|
-
|
308
|
-
async def _on_shutdown(app):
|
309
|
-
for broker in brokers:
|
310
|
-
await broker.shutdown()
|
311
|
-
|
312
|
-
app.on_startup.append(_on_startup)
|
313
|
-
app.on_shutdown.append(_on_shutdown)
|
314
|
-
web.run_app(
|
315
|
-
app,
|
316
|
-
host=host,
|
317
|
-
port=port,
|
318
|
-
**kwargs
|
319
|
-
)
|
320
|
-
|
321
|
-
|
322
|
-
__all__ = [
|
323
|
-
'run',
|
324
|
-
'std_api_factory',
|
325
|
-
'HttpTradingView',
|
1
|
+
import json
|
2
|
+
import asyncio
|
3
|
+
from datetime import datetime, UTC
|
4
|
+
from typing import Callable
|
5
|
+
from aiohttp import web
|
6
|
+
from httptrading.broker.base import *
|
7
|
+
from httptrading.model import *
|
8
|
+
|
9
|
+
|
10
|
+
class HttpTradingView(web.View):
|
11
|
+
__BROKERS: list[BaseBroker] = list()
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
def set_brokers(cls, brokers: list[BaseBroker]):
|
15
|
+
HttpTradingView.__BROKERS = brokers
|
16
|
+
|
17
|
+
@classmethod
|
18
|
+
def brokers(cls):
|
19
|
+
return HttpTradingView.__BROKERS.copy()
|
20
|
+
|
21
|
+
def instance_id(self) -> str:
|
22
|
+
return self.request.match_info.get('instance_id', '')
|
23
|
+
|
24
|
+
def current_broker(self) -> BaseBroker:
|
25
|
+
broker = getattr(self.request, '__current_broker__', None)
|
26
|
+
if broker is None:
|
27
|
+
raise web.HTTPNotFound()
|
28
|
+
return broker
|
29
|
+
|
30
|
+
async def get_contract(self, from_json=False):
|
31
|
+
params: dict = await self.request.json() if from_json else self.request.query
|
32
|
+
trade_type = TradeType[params.get('tradeType', '--')]
|
33
|
+
region = params.get('region', '--')
|
34
|
+
ticker = params.get('ticker', '--')
|
35
|
+
contract = Contract(
|
36
|
+
trade_type=trade_type,
|
37
|
+
region=region,
|
38
|
+
ticker=ticker,
|
39
|
+
)
|
40
|
+
return contract
|
41
|
+
|
42
|
+
@classmethod
|
43
|
+
def dumps(cls, obj):
|
44
|
+
return json.dumps(obj, default=HtGlobalConfig.JSON_DEFAULT.json_default)
|
45
|
+
|
46
|
+
@classmethod
|
47
|
+
def response_obj(cls, obj):
|
48
|
+
return web.Response(text=cls.dumps(obj), content_type='application/json')
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def response_api(cls, broker: BaseBroker = None, args: dict = None, ex: Exception = None):
|
52
|
+
resp = {
|
53
|
+
'type': 'apiResponse',
|
54
|
+
'instanceId': broker.instance_id if broker else None,
|
55
|
+
'broker': broker.broker_name if broker else None,
|
56
|
+
'brokerDisplay': broker.broker_display if broker else None,
|
57
|
+
'time': datetime.now(UTC).isoformat(),
|
58
|
+
'ex': ex.__str__() if ex else None,
|
59
|
+
}
|
60
|
+
if args:
|
61
|
+
resp.update(args)
|
62
|
+
return cls.response_obj(resp)
|
63
|
+
|
64
|
+
|
65
|
+
class PlaceOrderView(HttpTradingView):
|
66
|
+
async def post(self):
|
67
|
+
broker = self.current_broker()
|
68
|
+
contract = await self.get_contract(from_json=True)
|
69
|
+
body_d: dict = await self.request.json()
|
70
|
+
price = body_d.get('price', 0)
|
71
|
+
qty = body_d.get('qty', 0)
|
72
|
+
order_type = OrderType[body_d.get('orderType', '')]
|
73
|
+
time_in_force = TimeInForce[body_d.get('timeInForce', '')]
|
74
|
+
lifecycle = Lifecycle[body_d.get('lifecycle', '')]
|
75
|
+
direction = body_d.get('direction', '')
|
76
|
+
if price:
|
77
|
+
price = float(price)
|
78
|
+
order_id: str = await broker.place_order(
|
79
|
+
contract=contract,
|
80
|
+
order_type=order_type,
|
81
|
+
time_in_force=time_in_force,
|
82
|
+
lifecycle=lifecycle,
|
83
|
+
direction=direction,
|
84
|
+
qty=qty,
|
85
|
+
price=price,
|
86
|
+
json=body_d,
|
87
|
+
)
|
88
|
+
return self.response_api(broker, {
|
89
|
+
'orderId': order_id,
|
90
|
+
'args': body_d,
|
91
|
+
})
|
92
|
+
|
93
|
+
|
94
|
+
class OrderStateView(HttpTradingView):
|
95
|
+
async def get(self):
|
96
|
+
broker = self.current_broker()
|
97
|
+
order_id = self.request.query.get('orderId', '')
|
98
|
+
order: Order = await broker.order(order_id=order_id)
|
99
|
+
_ = asyncio.create_task(broker.call_sync(lambda : broker.dump_order(order)))
|
100
|
+
return self.response_api(broker, {
|
101
|
+
'order': order,
|
102
|
+
})
|
103
|
+
|
104
|
+
|
105
|
+
class CancelOrderView(HttpTradingView):
|
106
|
+
async def post(self):
|
107
|
+
broker = self.current_broker()
|
108
|
+
body_d: dict = await self.request.json()
|
109
|
+
order_id = body_d.get('orderId', '')
|
110
|
+
assert order_id
|
111
|
+
await broker.cancel_order(order_id=order_id)
|
112
|
+
return self.response_api(broker, {
|
113
|
+
'canceled': True,
|
114
|
+
})
|
115
|
+
|
116
|
+
|
117
|
+
class CashView(HttpTradingView):
|
118
|
+
async def get(self):
|
119
|
+
broker = self.current_broker()
|
120
|
+
cash: Cash = await broker.cash()
|
121
|
+
return self.response_api(broker, {
|
122
|
+
'cash': cash,
|
123
|
+
})
|
124
|
+
|
125
|
+
|
126
|
+
class PositionView(HttpTradingView):
|
127
|
+
async def get(self):
|
128
|
+
broker = self.current_broker()
|
129
|
+
positions: list[Position] = await broker.positions()
|
130
|
+
return self.response_api(broker, {
|
131
|
+
'positions': positions,
|
132
|
+
})
|
133
|
+
|
134
|
+
|
135
|
+
class PlugInView(HttpTradingView):
|
136
|
+
async def get(self):
|
137
|
+
broker = self.current_broker()
|
138
|
+
pong = await broker.ping()
|
139
|
+
return self.response_api(broker, {
|
140
|
+
'pong': pong,
|
141
|
+
})
|
142
|
+
|
143
|
+
|
144
|
+
class QuoteView(HttpTradingView):
|
145
|
+
async def get(self):
|
146
|
+
contract = await self.get_contract()
|
147
|
+
broker = self.current_broker()
|
148
|
+
quote: Quote = await broker.quote(contract)
|
149
|
+
return self.response_api(broker, {
|
150
|
+
'quote': quote,
|
151
|
+
})
|
152
|
+
|
153
|
+
|
154
|
+
class MarketStatusView(HttpTradingView):
|
155
|
+
async def get(self):
|
156
|
+
broker = self.current_broker()
|
157
|
+
ms_dict = await broker.market_status()
|
158
|
+
ms_dict = {t.name.lower(): d for t, d in ms_dict.items()}
|
159
|
+
ms_dict['type'] = 'marketStatusMap'
|
160
|
+
return self.response_api(broker, {
|
161
|
+
'marketStatus': ms_dict,
|
162
|
+
})
|
163
|
+
|
164
|
+
|
165
|
+
@web.middleware
|
166
|
+
async def auth_middleware(request: web.Request, handler):
|
167
|
+
instance_id = request.match_info.get('instance_id', '')
|
168
|
+
token = request.headers.get('HT-TOKEN', '')
|
169
|
+
if not instance_id:
|
170
|
+
raise web.HTTPNotFound
|
171
|
+
if not token:
|
172
|
+
raise web.HTTPNotFound
|
173
|
+
if len(token) < 16 or len(token) > 64:
|
174
|
+
raise web.HTTPNotFound
|
175
|
+
for broker in HttpTradingView.brokers():
|
176
|
+
if broker.instance_id != instance_id:
|
177
|
+
continue
|
178
|
+
if token not in broker.tokens:
|
179
|
+
raise web.HTTPNotFound
|
180
|
+
setattr(request, '__current_broker__', broker)
|
181
|
+
break
|
182
|
+
else:
|
183
|
+
raise web.HTTPNotFound
|
184
|
+
response: web.Response = await handler(request)
|
185
|
+
delattr(request, '__current_broker__')
|
186
|
+
return response
|
187
|
+
|
188
|
+
|
189
|
+
@web.middleware
|
190
|
+
async def exception_middleware(request: web.Request, handler):
|
191
|
+
try:
|
192
|
+
response: web.Response = await handler(request)
|
193
|
+
return response
|
194
|
+
except BrokerError as ex:
|
195
|
+
return HttpTradingView.response_api(broker=ex.broker, ex=ex)
|
196
|
+
except Exception as ex:
|
197
|
+
broker = getattr(request, '__current_broker__', None)
|
198
|
+
return HttpTradingView.response_api(broker=broker, ex=ex)
|
199
|
+
|
200
|
+
|
201
|
+
def std_api_factory() -> list[web.RouteDef]:
|
202
|
+
apis = [
|
203
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/order/place', PlaceOrderView),
|
204
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/order/state', OrderStateView),
|
205
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/order/cancel', CancelOrderView),
|
206
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/cash/state', CashView),
|
207
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/position/state', PositionView),
|
208
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/ping/state', PlugInView),
|
209
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/market/state', MarketStatusView),
|
210
|
+
web.view(r'/httptrading/api/{instance_id:\w{16,32}}/market/quote', QuoteView),
|
211
|
+
]
|
212
|
+
return apis
|
213
|
+
|
214
|
+
|
215
|
+
def run(
|
216
|
+
host: str,
|
217
|
+
port: int,
|
218
|
+
brokers: list[BaseBroker],
|
219
|
+
std_apis: Callable[[], list[web.RouteDef]] = None,
|
220
|
+
extend_apis: list[web.RouteDef] = None,
|
221
|
+
**kwargs
|
222
|
+
) -> None:
|
223
|
+
"""
|
224
|
+
@param host: 监听地址
|
225
|
+
@param port: 监听端口
|
226
|
+
@param brokers: 需要控制的交易通道对象列表
|
227
|
+
@param std_apis: 如果需要替换默认提供的接口, 这里提供工厂函数的回调
|
228
|
+
@param extend_apis: 如果需要增加自定义接口, 这里传入 RouteDef 列表
|
229
|
+
@param kwargs: 其他的参数将传给 aiohttp.web.run_app 函数
|
230
|
+
"""
|
231
|
+
app = web.Application(
|
232
|
+
middlewares=[
|
233
|
+
auth_middleware,
|
234
|
+
exception_middleware,
|
235
|
+
],
|
236
|
+
)
|
237
|
+
|
238
|
+
apis = (std_api_factory if std_apis is None else std_apis)()
|
239
|
+
|
240
|
+
if extend_apis:
|
241
|
+
apis.extend(extend_apis)
|
242
|
+
|
243
|
+
app.add_routes(apis)
|
244
|
+
|
245
|
+
async def _on_startup(app):
|
246
|
+
HttpTradingView.set_brokers(brokers)
|
247
|
+
for broker in brokers:
|
248
|
+
await broker.start()
|
249
|
+
|
250
|
+
async def _on_shutdown(app):
|
251
|
+
for broker in brokers:
|
252
|
+
await broker.shutdown()
|
253
|
+
|
254
|
+
app.on_startup.append(_on_startup)
|
255
|
+
app.on_shutdown.append(_on_shutdown)
|
256
|
+
web.run_app(
|
257
|
+
app,
|
258
|
+
host=host,
|
259
|
+
port=port,
|
260
|
+
**kwargs
|
261
|
+
)
|
262
|
+
|
263
|
+
|
264
|
+
__all__ = [
|
265
|
+
'run',
|
266
|
+
'std_api_factory',
|
267
|
+
'HttpTradingView',
|
326
268
|
]
|