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.
@@ -1,326 +1,268 @@
1
- import json
2
- from datetime import datetime, UTC
3
- from typing import Callable
4
- from aiohttp import web
5
- from httptrading.broker.base import *
6
- from httptrading.model import *
7
-
8
-
9
- class HttpTradingView(web.View):
10
- __BROKERS: list[BaseBroker] = list()
11
-
12
- @classmethod
13
- def set_brokers(cls, brokers: list[BaseBroker]):
14
- HttpTradingView.__BROKERS = brokers
15
-
16
- @classmethod
17
- def brokers(cls):
18
- return HttpTradingView.__BROKERS.copy()
19
-
20
- def instance_id(self) -> str:
21
- return self.request.match_info.get('instance_id', '')
22
-
23
- def current_broker(self) -> BaseBroker:
24
- broker = getattr(self.request, '__current_broker__', None)
25
- if broker is None:
26
- raise web.HTTPNotFound()
27
- return broker
28
-
29
- async def get_contract(self, from_json=False):
30
- params: dict = await self.request.json() if from_json else self.request.query
31
- trade_type = TradeType[params.get('tradeType', '--')]
32
- region = params.get('region', '--')
33
- ticker = params.get('ticker', '--')
34
- contract = Contract(
35
- trade_type=trade_type,
36
- region=region,
37
- ticker=ticker,
38
- )
39
- return contract
40
-
41
- @classmethod
42
- def json_default(cls, obj):
43
- if isinstance(obj, Position):
44
- return {
45
- 'type': 'position',
46
- 'broker': obj.broker,
47
- 'brokerDisplay': obj.broker_display,
48
- 'contract': cls.json_default(obj.contract),
49
- 'unit': obj.unit.name,
50
- 'currency': obj.currency,
51
- 'qty': obj.qty,
52
- }
53
- if isinstance(obj, Contract):
54
- return {
55
- 'type': 'contract',
56
- 'tradeType': obj.trade_type.name,
57
- 'region': obj.region,
58
- 'ticker': obj.ticker,
59
- }
60
- if isinstance(obj, Cash):
61
- return {
62
- 'type': 'cash',
63
- 'currency': obj.currency,
64
- 'amount': obj.amount,
65
- }
66
- if isinstance(obj, MarketStatus):
67
- return {
68
- 'type': 'marketStatus',
69
- 'region': obj.region,
70
- 'originStatus': obj.origin_status,
71
- 'unifiedStatus': obj.unified_status.name,
72
- }
73
- if isinstance(obj, Quote):
74
- return {
75
- 'type': 'quote',
76
- 'contract': cls.json_default(obj.contract),
77
- 'currency': obj.currency,
78
- 'isTradable': obj.is_tradable,
79
- 'latest': obj.latest,
80
- 'preClose': obj.pre_close,
81
- 'highPrice': obj.high_price,
82
- 'lowPrice': obj.low_price,
83
- 'openPrice': obj.open_price,
84
- 'timestamp': int(obj.time.timestamp() * 1000),
85
- }
86
- if isinstance(obj, Order):
87
- return {
88
- 'type': 'order',
89
- 'orderId': obj.order_id,
90
- 'currency': obj.currency,
91
- 'qty': obj.qty,
92
- 'filledQty': obj.filled_qty,
93
- 'avgPrice': obj.avg_price,
94
- 'errorReason': obj.error_reason,
95
- 'isCanceled': obj.is_canceled,
96
- 'isFilled': obj.is_filled,
97
- 'isCompleted': obj.is_completed,
98
- 'isCancelable': obj.is_cancelable,
99
- }
100
- raise TypeError(f"Object of type {obj.__class__.__name__} is not JSON serializable")
101
-
102
- @classmethod
103
- def dumps(cls, obj):
104
- return json.dumps(obj, default=cls.json_default)
105
-
106
- @classmethod
107
- def response_obj(cls, obj):
108
- return web.Response(text=cls.dumps(obj), content_type='application/json')
109
-
110
- @classmethod
111
- def response_api(cls, broker: BaseBroker = None, args: dict = None, ex: Exception = None):
112
- resp = {
113
- 'type': 'apiResponse',
114
- 'instanceId': broker.instance_id if broker else None,
115
- 'broker': broker.broker_name if broker else None,
116
- 'brokerDisplay': broker.broker_display if broker else None,
117
- 'time': datetime.now(UTC).isoformat(),
118
- 'ex': ex.__str__() if ex else None,
119
- }
120
- if args:
121
- resp.update(args)
122
- return cls.response_obj(resp)
123
-
124
-
125
- class PlaceOrderView(HttpTradingView):
126
- async def post(self):
127
- broker = self.current_broker()
128
- contract = await self.get_contract(from_json=True)
129
- body_d: dict = await self.request.json()
130
- price = body_d.get('price', 0)
131
- qty = body_d.get('qty', 0)
132
- order_type = OrderType[body_d.get('orderType', '')]
133
- time_in_force = TimeInForce[body_d.get('timeInForce', '')]
134
- lifecycle = Lifecycle[body_d.get('lifecycle', '')]
135
- direction = body_d.get('direction', '')
136
- if price:
137
- price = float(price)
138
- order_id: str = await broker.place_order(
139
- contract=contract,
140
- order_type=order_type,
141
- time_in_force=time_in_force,
142
- lifecycle=lifecycle,
143
- direction=direction,
144
- qty=qty,
145
- price=price,
146
- json=body_d,
147
- )
148
- return self.response_api(broker, {
149
- 'orderId': order_id,
150
- 'args': body_d,
151
- })
152
-
153
-
154
- class OrderStateView(HttpTradingView):
155
- async def get(self):
156
- broker = self.current_broker()
157
- order_id = self.request.query.get('orderId', '')
158
- order: Order = await broker.order(order_id=order_id)
159
- return self.response_api(broker, {
160
- 'order': order,
161
- })
162
-
163
-
164
- class CancelOrderView(HttpTradingView):
165
- async def post(self):
166
- broker = self.current_broker()
167
- body_d: dict = await self.request.json()
168
- order_id = body_d.get('orderId', '')
169
- assert order_id
170
- await broker.cancel_order(order_id=order_id)
171
- return self.response_api(broker, {
172
- 'canceled': True,
173
- })
174
-
175
-
176
- class CashView(HttpTradingView):
177
- async def get(self):
178
- broker = self.current_broker()
179
- cash: Cash = await broker.cash()
180
- return self.response_api(broker, {
181
- 'cash': cash,
182
- })
183
-
184
-
185
- class PositionView(HttpTradingView):
186
- async def get(self):
187
- broker = self.current_broker()
188
- positions: list[Position] = await broker.positions()
189
- return self.response_api(broker, {
190
- 'positions': positions,
191
- })
192
-
193
-
194
- class PlugInView(HttpTradingView):
195
- async def get(self):
196
- broker = self.current_broker()
197
- pong = await broker.ping()
198
- return self.response_api(broker, {
199
- 'pong': pong,
200
- })
201
-
202
-
203
- class QuoteView(HttpTradingView):
204
- async def get(self):
205
- contract = await self.get_contract()
206
- broker = self.current_broker()
207
- quote: Quote = await broker.quote(contract)
208
- return self.response_api(broker, {
209
- 'quote': quote,
210
- })
211
-
212
-
213
- class MarketStatusView(HttpTradingView):
214
- async def get(self):
215
- broker = self.current_broker()
216
- ms_dict = await broker.market_status()
217
- ms_dict = {t.name.lower(): d for t, d in ms_dict.items()}
218
- ms_dict['type'] = 'marketStatusMap'
219
- return self.response_api(broker, {
220
- 'marketStatus': ms_dict,
221
- })
222
-
223
-
224
- @web.middleware
225
- async def auth_middleware(request: web.Request, handler):
226
- instance_id = request.match_info.get('instance_id', '')
227
- token = request.headers.get('HT-TOKEN', '')
228
- if not instance_id:
229
- raise web.HTTPNotFound
230
- if not token:
231
- raise web.HTTPNotFound
232
- if len(token) < 16 or len(token) > 64:
233
- raise web.HTTPNotFound
234
- for broker in HttpTradingView.brokers():
235
- if broker.instance_id != instance_id:
236
- continue
237
- if token not in broker.tokens:
238
- raise web.HTTPNotFound
239
- setattr(request, '__current_broker__', broker)
240
- break
241
- else:
242
- raise web.HTTPNotFound
243
- response: web.Response = await handler(request)
244
- delattr(request, '__current_broker__')
245
- return response
246
-
247
-
248
- @web.middleware
249
- async def exception_middleware(request: web.Request, handler):
250
- try:
251
- response: web.Response = await handler(request)
252
- return response
253
- except BrokerError as ex:
254
- return HttpTradingView.response_api(broker=ex.broker, ex=ex)
255
- except Exception as ex:
256
- broker = getattr(request, '__current_broker__', None)
257
- return HttpTradingView.response_api(broker=broker, ex=ex)
258
-
259
-
260
- def std_api_factory() -> list[web.RouteDef]:
261
- apis = [
262
- web.view(r'/httptrading/api/{instance_id:\w{16,32}}/order/place', PlaceOrderView),
263
- web.view(r'/httptrading/api/{instance_id:\w{16,32}}/order/state', OrderStateView),
264
- web.view(r'/httptrading/api/{instance_id:\w{16,32}}/order/cancel', CancelOrderView),
265
- web.view(r'/httptrading/api/{instance_id:\w{16,32}}/cash/state', CashView),
266
- web.view(r'/httptrading/api/{instance_id:\w{16,32}}/position/state', PositionView),
267
- web.view(r'/httptrading/api/{instance_id:\w{16,32}}/ping/state', PlugInView),
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
  ]