Skip to content

Commit 514bc29

Browse files
committed
feat: websocket order lists multiple response
1 parent 27def96 commit 514bc29

File tree

9 files changed

+121
-29
lines changed

9 files changed

+121
-29
lines changed

cryptomarket/dataclasses/report.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class Report:
3131
post_only: bool
3232
created_at: str
3333
updated_at: str
34-
report_type: ReportType
34+
report_type: Optional[ReportType] = None
3535
price: Optional[str] = None
3636
stop_price: Optional[str] = None
3737
expire_time: Optional[str] = None
Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
1+
from typing import Callable, Dict, Optional
2+
from cryptomarket.websockets.reusable_callback import ReusableCallback
3+
4+
15
class CallbackCache:
26
def __init__(self):
3-
self.callbacks = dict()
7+
self.callbacks: Dict[str, ReusableCallback] = dict()
48
self._id = 1
5-
9+
610
def next_id(self):
711
self._id += 1
812
if self._id < 1:
913
self._id = 1
1014
return self._id
1115

12-
def store_callback(self, callback: callable) -> int:
16+
def save_callback(self, callback: callable, call_count: int = 1) -> int:
1317
id = self.next_id()
14-
self.callbacks[id] = callback
18+
self.callbacks[id] = ReusableCallback(callback, call_count)
1519
return id
1620

17-
def pop_callback(self, id: int) -> callable:
18-
if id not in self.callbacks: return None
19-
callback = self.callbacks[id]
20-
del self.callbacks[id]
21+
def get_callback(self, id: int) -> Optional[Callable]:
22+
if id not in self.callbacks:
23+
return None
24+
reusable_callback = self.callbacks[id]
25+
callback, done = reusable_callback.get_callback()
26+
if done:
27+
del self.callbacks[id]
2128
return callback
2229

23-
def store_subscription_callback(self, key: str, callback: callable):
30+
def save_subscription_callback(self, key: str, callback: callable):
2431
self.callbacks[key] = callback
2532

2633
def get_subscription_callback(self, key: str) -> callable:
27-
if key not in self.callbacks: return None
34+
if key not in self.callbacks:
35+
return None
2836
return self.callbacks[key]
2937

3038
def delete_subscription_callback(self, key: str):
31-
if key in self.callbacks: del self.callbacks[key]
32-
33-
39+
if key in self.callbacks:
40+
del self.callbacks[key]

cryptomarket/websockets/client_base.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,18 @@ def _on_open(self):
7171

7272
def _send_subscription(self, method, callback, params=None, result_callback=None):
7373
key = self._build_key(method)
74-
self._callback_cache.store_subscription_callback(key, callback)
74+
self._callback_cache.save_subscription_callback(key, callback)
7575
self._send_by_id(method, result_callback, params)
7676

7777
def _send_unsubscription(self, method, callback=None, params=None):
7878
key = self._build_key(method)
7979
self._callback_cache.delete_subscription_callback(key)
8080
self._send_by_id(method, callback, params)
8181

82-
def _send_by_id(self, method: str, callback: callable = None, params=None):
82+
def _send_by_id(self, method: str, callback: callable = None, params=None, call_count: int = 1):
8383
payload = {'method': method, 'params': params}
8484
if callback:
85-
id = self._callback_cache.store_callback(callback)
85+
id = self._callback_cache.save_callback(callback, call_count)
8686
payload['id'] = id
8787
self._ws_manager.send(payload)
8888

@@ -108,10 +108,9 @@ def _handle_notification(self, message):
108108

109109
def _handle_response(self, response):
110110
id = response['id']
111-
callback = self._callback_cache.pop_callback(id)
111+
callback = self._callback_cache.get_callback(id)
112112
if callback is None:
113113
return
114-
115114
if 'error' in response:
116115
callback(CryptomarketAPIException.from_dict(response), None)
117116
return
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from enum import Enum
2+
from functools import partial
3+
from dacite import Config, from_dict
4+
from cryptomarket.dataclasses.report import Report
5+
6+
7+
def intercept_report(callback):
8+
return partial(intercept_report_, callback=callback)
9+
10+
11+
def intercept_report_(err, response, callback):
12+
print('interceptor')
13+
print('err:', err)
14+
print('response:', response)
15+
if err:
16+
callback(err, None)
17+
return
18+
reports = from_dict(data_class=Report, data=response,
19+
config=Config(cast=[Enum]))
20+
callback(None, reports)

cryptomarket/websockets/market_data_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ def _send_channeled_subscription(
6363
result_callback=None
6464
):
6565
key = channel
66-
self._callback_cache.store_subscription_callback(key, callback)
66+
self._callback_cache.save_subscription_callback(key, callback)
6767

6868
def intercept_result(err, result):
6969
result_callback(err, result['subscriptions'])
70-
ID = self._callback_cache.store_callback(intercept_result)
70+
ID = self._callback_cache.save_callback(intercept_result)
7171
payload = {
7272
'method': 'subscribe',
7373
'ch': channel,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from dataclasses import dataclass
2+
from typing import Callable, Optional, Tuple
3+
4+
5+
@dataclass
6+
class ReusableCallback:
7+
callback: Callable
8+
call_count: int
9+
10+
def is_done(self) -> bool:
11+
return self.call_count < 1
12+
13+
def get_callback(self) -> Optional[Tuple[Callable, bool]]:
14+
if self.is_done():
15+
return None, True
16+
self.call_count -= 1
17+
return self.callback, self.is_done()

cryptomarket/websockets/trading_client.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,12 @@ def create_spot_order_list(
237237
orders: List[args.OrderRequest],
238238
order_list_id: Optional[str] = None,
239239
callback: Optional[Callable[[
240-
Union[CryptomarketAPIException, None], Union[List[Report], None]], None]] = None
240+
Union[CryptomarketAPIException, None], Union[Report, None]], None]] = None
241241
):
242242
"""creates a list of spot orders
243243
244+
calls the callback once per each order in the orders list
245+
244246
Types or Contingency:
245247
- Contingency.ALL_OR_NONE (Contingency.AON)
246248
- Contingency.ONE_CANCEL_OTHER (Contingency.OCO)
@@ -277,23 +279,22 @@ def create_spot_order_list(
277279
:param contingency_type: order list type.
278280
:param orders: the list of orders
279281
:param order_list_id: order list identifier. If not provided, it will be generated by the system. Must be equal to the client order id of the first order in the request
280-
:param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of reports of the created orders
282+
:param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a report of one of the created order. Once per created order
281283
"""
282284
params = args.DictBuilder().order_list_id(
283285
order_list_id).contingency_type(contingency_type).orders(orders).build()
284-
285286
if callback:
286287
def intercept_response(err, response):
287288
if err:
288289
callback(err, None)
289290
return
290-
reports = [from_dict(data_class=Report, data=report, config=Config(cast=[Enum]))
291-
for report in response]
292-
callback(None, reports)
291+
report = from_dict(data_class=Report,
292+
data=response, config=Config(cast=[Enum]))
293+
callback(None, report)
293294
else:
294295
intercept_response = None
295296
self._send_by_id('spot_new_order_list',
296-
callback=intercept_response, params=params)
297+
callback=intercept_response, params=params, call_count=len(orders))
297298

298299
def cancel_spot_order(
299300
self,

tests/websockets/test_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def good_report(report: Report):
198198
'time_in_force',
199199
'quantity',
200200
'price',
201-
'cum_quantity',
201+
'quantity_cumulative',
202202
'post_only',
203203
'created_at',
204204
'updated_at',

tests/websockets/test_trading_client.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import time
33
import unittest
44
from typing import Union
5+
from cryptomarket import args
56

67
from test_helpers import *
78

@@ -170,6 +171,53 @@ def check_good_trading_commission(err, commission):
170171
if Veredict.failed:
171172
self.fail(Veredict.message)
172173

174+
def test_create_spot_order_list(self):
175+
first_order_id = str(int(time.time()))
176+
second_order_id = first_order_id + "2"
177+
side = args.Side.SELL
178+
quantity = "0.01"
179+
price = "10000"
180+
time_in_force = args.TimeInForce.FOK
181+
182+
def check_good_report(err, report):
183+
if err:
184+
Veredict.fail(f'{err}')
185+
return
186+
if not good_report(report):
187+
Veredict.fail('not a good report')
188+
return
189+
Veredict.done = True
190+
try:
191+
self.ws.create_spot_order_list(
192+
contingency_type=args.ContingencyType.ALL_OR_NONE,
193+
order_list_id=first_order_id,
194+
orders=[
195+
args.OrderRequest(
196+
'EOSETH',
197+
side=side,
198+
quantity=quantity,
199+
client_order_id=first_order_id,
200+
time_in_force=time_in_force,
201+
price=price
202+
),
203+
args.OrderRequest(
204+
'EOSBTC',
205+
side=side,
206+
quantity=quantity,
207+
client_order_id=second_order_id,
208+
time_in_force=time_in_force,
209+
price=price
210+
),
211+
],
212+
callback=check_good_report)
213+
except Exception as e:
214+
print(e)
215+
self.fail(str(e))
216+
time.sleep(10)
217+
Veredict.wait_done()
218+
if Veredict.failed:
219+
self.fail(Veredict.message)
220+
173221

174222
if __name__ == '__main__':
175223
unittest.main()

0 commit comments

Comments
 (0)