Completed
Push — main ( 40d33d...2ccf5e )
by Switcheolytics
22s queued 12s
created

tradehub.websocket_client.DemexWebsocket.open()   A

Complexity

Conditions 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 1
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
from typing import Optional, List, Callable
2
import websockets
3
import json
4
5
6
class DemexWebsocket:
7
    """
8
    DemexWebsocket is a high-level async implementation off the official Tradehub Demex websocket and provides all
9
    functionalities described in the documentation.
10
    """
11
12
    def __init__(self, uri: str, ping_interval: Optional[int] = 10, ping_timeout: Optional[int] = 30):
13
        """
14
        Create a websocket which is complaint with the specification provided by the offical documentation.
15
16
        .. see::
17
            https://docs.switcheo.org/#/?id=websocket
18
19
        :param uri: Websocket URI, starting with 'ws://' or 'wss://' e.g. 'ws://85.214.81.155:5000/ws'
20
        :param ping_interval: Interval for pinging the server in seconds.
21
        :param ping_timeout: Time after no response for pings are considered as timeout in seconds.
22
        """
23
        self._uri: str = uri
24
        self._ping_interval: int = ping_interval
25
        self._ping_timeout: int = ping_timeout
26
        self._websocket: Optional[websockets.WebSocketClientProtocol] = None
27
28
    async def subscribe(self, message_id: str, channels: List[str]):
29
        """
30
        Subscribe to one or many channels.
31
32
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
33
                           identify which channel the notification is originated from.
34
        :param channels: List with channels to join.
35
        :return: None
36
        """
37
        await self.send({
38
            "id": message_id,
39
            "method": "subscribe",
40
            "params": {"channels": channels}
41
        })
42
43
    async def unsubscribe(self, message_id: str, channels: List[str]):
44
        """
45
        Unsubscribe to one or many channels.
46
47
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
48
                           identify which channel the notification is originated from.
49
        :param channels: List with channels to leave.
50
        :return: None
51
        """
52
        await self.send({
53
            "id": message_id,
54
            "method": "unsubscribe",
55
            "params": {"channels": channels}
56
        })
57
58
    async def subscribe_leverages(self, message_id: str, swth_address: str):
59
        """
60
        Subscribe to wallet specific leverages channel.
61
62
        .. warning::
63
            This channel has not been tested yet.
64
65
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
66
                           identify which channel the notification is originated from.
67
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
68
        :return: None
69
        """
70
        # TODO not tested yet
71
        channel_name: str = f"leverages.{swth_address}"
72
        await self.subscribe(message_id, [channel_name])
73
74
    async def subscribe_market_stats(self, message_id: str):
75
        """
76
        Subscribe to market stats.
77
78
        Example::
79
80
            ws_client.subscribe_market_stats('market_stats')
81
82
        The initial channel message is expected as::
83
84
            {
85
                'id':'market_stats',
86
                'result': ['market_stats']
87
            }
88
89
        The subscription and channel messages are expected as follow::
90
91
            {
92
                'channel': 'market_stats',
93
                'sequence_number': 484,
94
                'result': {
95
                    'cel1_usdc1': {
96
                        'day_high': '5.97',
97
                        'day_low': '5.72',
98
                        'day_open': '5.86',
99
                        'day_close': '5.74',
100
                        'day_volume': '414.4',
101
                        'day_quote_volume': '2429.009',
102
                        'index_price': '0',
103
                        'mark_price': '0',
104
                        'last_price': '5.74',
105
                        'market': 'cel1_usdc1',
106
                        'market_type': 'spot',
107
                        'open_interest': '0'
108
                    }
109
                    ...
110
                }
111
            }
112
113
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
114
                           identify which channel the notification is originated from.
115
        :return: None
116
        """
117
        channel_name: str = "market_stats"
118
        await self.subscribe(message_id, [channel_name])
119
120
    async def subscribe_books(self, message_id: str, market: str):
121
        """
122
        Subscribe to book channel.
123
124
        Example::
125
126
            ws_client.subscribe_books('orderbook', "swth_eth1')
127
128
        The initial channel message is expected as::
129
130
            {
131
                'id':'orderbook',
132
                'result': ['books.eth1_usdc1', ...]
133
            }
134
135
        The initial subscription message is expected as::
136
137
            {
138
                'channel': 'books.eth1_usdc1',
139
                'sequence_number': 924,
140
                'result': [
141
                    {
142
                        'market': 'eth1_usdc1',
143
                        'price': '1797.1',
144
                        'quantity': '0.02',
145
                        'side': 'sell',
146
                        'type': 'new'
147
                    },
148
                    ...
149
                    {
150
                        'market': 'eth1_usdc1',
151
                        'price': '1790.1',
152
                        'quantity': '0.02',
153
                        'side': 'buy',
154
                        'type': 'new'
155
                    }
156
                    ...
157
                ]
158
            }
159
160
        The channel update messages are expected as::
161
162
            {
163
                'channel': 'books.eth1_usdc1',
164
                'sequence_number': 924,
165
                'result': [
166
                    {
167
                        'market': 'eth1_usdc1',
168
                        'price': '1797.1',
169
                        'quantity': '0',
170
                        'side': 'sell',
171
                        'type': 'delete'
172
                    },
173
                    ...
174
                    {
175
                        'market':'eth1_usdc1',
176
                        'price': '1800.18',
177
                        'quantity': '-0.43',
178
                        'side': 'sell',
179
                        'type': 'update'
180
                    },
181
                    ...
182
                    {
183
                        'market': 'eth1_usdc1',
184
                        'price': '1114.48',
185
                        'quantity': '182.716',
186
                        'side': 'buy',
187
                        'type': 'new'
188
                    }
189
                ]
190
            }
191
192
        .. note::
193
            The initial message is a snapshot of the current orderbook. The following messages are delta messages to the
194
            snapshot. Each message has a 'sequence_number'.
195
196
            Updates can contain update types: 'new', 'update' or 'delete'. The quantity in a 'update' message can be
197
            negative indicating a reduction, while positive value means an increment.
198
199
            All updates need to be processed in the provided order to maintain an consistent orderbook.
200
201
        .. warning::
202
            The initial snapshot is a partial orderbook with a total of 100 entries!
203
            Expect receiving updates for orders outside the local managed orderbook.
204
            Ignore or reconnect to maintain the local orderbook.
205
206
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
207
                           identify which channel the notification is originated from.
208
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
209
        :return: None
210
        """
211
        channel_name: str = f"books.{market}"
212
        await self.subscribe(message_id, [channel_name])
213
214
    async def subscribe_orders(self, message_id: str, swth_address: str, market: Optional[str] = None):
215
        """
216
        Subscribe to orders channel.
217
218
        .. note::
219
            The market identifier is optional and acts as a filter.
220
221
        Example::
222
223
            ws_client.subscribe_orders('orders', "swth1...abcd')
224
225
226
        The initial channel message is expected as::
227
228
            {
229
                'id':'orders',
230
                'result': ['orders.swth1...abcd']
231
            }
232
233
        The channel update messages are expected as::
234
235
            {
236
                'channel': 'orders.swth1...abcd',
237
                'result': [
238
                    {
239
                        'order_id': '7CBBF51B75CF2E046726BB...56757D6D502B01F4BB62178DCF',
240
                        'block_height': 7375724,
241
                        'triggered_block_height': 0,
242
                        'address': 'swth1...abcd',
243
                        'market': 'eth1_wbtc1',
244
                        'side': 'sell',
245
                        'price': '0',
246
                        'quantity': '0.08',
247
                        'available': '0.08',
248
                        'filled': '0',
249
                        'order_status': 'pending',
250
                        'order_type': 'market',
251
                        'initiator': 'user',
252
                        'time_in_force': 'fok',
253
                        'stop_price': '0',
254
                        'trigger_type': '',
255
                        'allocated_margin_denom': 'eth1',
256
                        'allocated_margin_amount': '0',
257
                        'is_liquidation': False,
258
                        'is_post_only': False,
259
                        'is_reduce_only': False,
260
                        'type': 'new',
261
                        'block_created_at': '2021-02-11T20:36:02.244175356Z',
262
                        'username': '',
263
                        'id': ''
264
                    }
265
                    ...
266
                ]
267
            }
268
269
270
271
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
272
                           identify which channel the notification is originated from.
273
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
274
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
275
        :return: None
276
        """
277
        if market:
278
            channel_name: str = f"orders_by_market.{market}.{swth_address}"
279
        else:
280
            channel_name: str = f"orders.{swth_address}"
281
        await self.subscribe(message_id, [channel_name])
282
283
    async def subscribe_positions(self, message_id: str, swth_address: str, market: Optional[str] = None):
284
        """
285
        Subscribe to positions channel.
286
287
        .. note::
288
            The market identifier is optional and acts as a filter.
289
290
        .. warning::
291
            This channel is not tested yet.
292
293
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
294
                           identify which channel the notification is originated from.
295
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
296
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
297
        :return: None
298
        """
299
        # TODO not tested yet
300
        if market:
301
            channel_name: str = f"positions_by_market.{market}.{swth_address}"
302
        else:
303
            channel_name: str = f"positions.{swth_address}"
304
        await self.subscribe(message_id, [channel_name])
305
306
    async def subscribe_recent_trades(self, message_id: str, market: str):
307
        """
308
        Subscribe to recent trades.
309
310
        Example::
311
312
            ws_client.subscribe_recent_trades('trades', "swth_eth1')
313
314
315
        The initial channel message is expected as::
316
317
            {
318
                'id': 'trades',
319
                'result': ['recent_trades.swth_eth1']
320
            }
321
322
        The channel update messages are expected as::
323
324
            {
325
                'channel': 'recent_trades.eth1_usdc1',
326
                'sequence_number': 812,
327
                'result': [
328
                    {
329
                        'id': '0',
330
                        'block_created_at': '2021-02-11T20:49:07.095418551Z',
331
                        'taker_id': '5FF349410F9CF59BED36D412D1223424835342274BC0E504ED0A17EE4B5B0856',
332
                        'taker_address': 'swth1vaavrkrm7usqg9hcwhqh2hev9m3nryw7aera8p',
333
                        'taker_fee_amount': '0.00002',
334
                        'taker_fee_denom': 'eth1',
335
                        'taker_side': 'buy',
336
                        'maker_id': '8334A9C97CAEFAF84774AAADB0D5666E7764BA023DF145C8AF90BB6A6862EA2E',
337
                        'maker_address': 'swth1wmcj8gmz4tszy5v8c0d9lxnmguqcdkw22275w5',
338
                        'maker_fee_amount': '-0.00001',
339
                        'maker_fee_denom': 'eth1',
340
                        'maker_side': 'sell',
341
                        'market': 'eth1_usdc1',
342
                        'price': '1797.1',
343
                        'quantity': '0.02',
344
                        'liquidation': '',
345
                        'taker_username': '',
346
                        'maker_username': '',
347
                        'block_height': '7376096'
348
                    },
349
                    ...
350
                ]
351
            }
352
353
        .. warning::
354
            The field 'id' is sometimes '0'. This endpoint/channel does not seem to work correct.
355
356
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
357
                           identify which channel the notification is originated from.
358
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
359
        :return: None
360
        """
361
        channel_name: str = f"recent_trades.{market}"
362
        await self.subscribe(message_id, [channel_name])
363
364
    async def subscribe_account_trades(self, message_id: str, swth_address: str, market: Optional[str] = None):
365
        """
366
        Subscribe to account trades.
367
368
        Example::
369
370
            ws_client.subscribe_account_trades('account', 'swth...abcd', 'eth1_usdc1')
371
            # or for all markets
372
            ws_client.subscribe_account_trades('account', "swth...abcd')
373
374
375
        The initial channel message is expected as::
376
377
            {
378
                'id': 'account',
379
                'result': ['account_trades_by_market.eth1_usdc1.swth1...abcd']
380
            }
381
            # or for all markets
382
            {
383
                'id': 'account',
384
                'result': ['account_trades.swth1...abcd']
385
            }
386
387
        The channel update messages are expected as::
388
389
            {
390
                'channel': 'recent_trades.eth1_usdc1',
391
                'sequence_number': 812,
392
                'result': [
393
                    {
394
                        'id': '0',
395
                        'block_created_at': '2021-02-11T20:49:07.095418551Z',
396
                        'taker_id': '5FF349410F9CF59BED36D412D1223424835342274BC0E504ED0A17EE4B5B0856',
397
                        'taker_address': 'swth1...taker',
398
                        'taker_fee_amount': '0.00002',
399
                        'taker_fee_denom': 'eth1',
400
                        'taker_side': 'buy',
401
                        'maker_id': '8334A9C97CAEFAF84774AAADB0D5666E7764BA023DF145C8AF90BB6A6862EA2E',
402
                        'maker_address': 'swth1...maker',
403
                        'maker_fee_amount': '-0.00001',
404
                        'maker_fee_denom': 'eth1',
405
                        'maker_side': 'sell',
406
                        'market': 'eth1_usdc1',
407
                        'price': '1797.1',
408
                        'quantity': '0.02',
409
                        'liquidation': '',
410
                        'taker_username': '',
411
                        'maker_username': '',
412
                        'block_height': '7376096'
413
                    },
414
                    ...
415
                ]
416
            }
417
418
        .. note::
419
            The market identifier is optional and acts as a filter.
420
421
        .. warning::
422
            The field 'id' is '0' all the time. This endpoint/channel does not seem to work correct.
423
424
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
425
                           identify which channel the notification is originated from.
426
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
427
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
428
        :return: None
429
        """
430
        if market:
431
            channel_name: str = f"account_trades_by_market.{market}.{swth_address}"
432
        else:
433
            channel_name: str = f"account_trades.{swth_address}"
434
        await self.subscribe(message_id, [channel_name])
435
436
    async def subscribe_balances(self, message_id: str, swth_address: str):
437
        """
438
        Subscribe to wallet specific balance channel.
439
440
        Example::
441
442
            ws_client.subscribe_balances('balance', "swth1...abcd')
443
444
445
        The initial channel message is expected as::
446
447
            {
448
                'id': 'balance',
449
                'result': ['balances.swth1...abcd']
450
            }
451
452
        The subscription and channel messages are expected as follow::
453
454
            {
455
                'channel': 'balances.swth1vaavrkrm7usqg9hcwhqh2hev9m3nryw7aera8p',
456
                'result': {
457
                    'eth1': {
458
                        'available': '0.83941506825',
459
                        'order': '0',
460
                        'position': '0',
461
                        'denom': 'eth1'
462
                    },
463
                    ...
464
                }
465
            }
466
467
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
468
                           identify which channel the notification is originated from.
469
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
470
        :return: None
471
        """
472
        channel_name: str = f"balances.{swth_address}"
473
        await self.subscribe(message_id, [channel_name])
474
475
    async def subscribe_candlesticks(self, message_id: str, market: str, granularity: int):
476
        """
477
        Subscribe to candlesticks channel.
478
479
        Example::
480
481
            ws_client.subscribe_candlesticks('candle', "swth_eth1', 1)
482
483
484
        The initial channel message is expected as::
485
486
            {
487
                'id': 'candle',
488
                'result': ['candlesticks.swth_eth1.1']
489
            }
490
491
        The subscription and channel messages are expected as follow::
492
493
            {
494
                'channel': 'candlesticks.swth_eth1.1',
495
                'sequence_number': 57,
496
                'result': {
497
                    'id': 0,
498
                    'market':'swth_eth1',
499
                    'time': '2021-02-17T10:59:00Z',
500
                    'resolution': 1,
501
                    'open': '0.000018',
502
                    'close': '0.000018',
503
                    'high': '0.000018',
504
                    'low': '0.000018',
505
                    'volume': '5555',
506
                    'quote_volume': '0.09999'
507
                }
508
            }
509
510
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
511
                           identify which channel the notification is originated from.
512
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
513
        :param granularity: Define the candlesticks granularity. Allowed values: 1, 5, 15, 30, 60, 360, 1440.
514
        :return: None
515
        """
516
        if granularity not in [1, 5, 15, 30, 60, 360, 1440]:
517
            raise ValueError(f"Granularity '{granularity}' not supported. Allowed values: 1, 5, 15, 30, 60, 360, 1440")
518
        channel_name: str = f"candlesticks.{market}.{granularity}"
519
        await self.subscribe(message_id, [channel_name])
520
521
    async def get_order_history(self, message_id: str, swth_address: str, market: Optional[str] = None):
522
        """
523
        Request up to 200 order histories.
524
525
        Example::
526
527
            ws_client.get_order_history('order_history', "swth1vaavrkrm7usqg9hcwhqh2hev9m3nryw7aera8p")
528
529
        The expected return result for this function is as follows::
530
531
            {
532
                "id": "order_history",
533
                "result": [
534
                    {
535
                        "order_id": "C7D7DDDCFDC68DF2D078CBD8630B657148893AC24CF8DB8F2E23293C6EDC90AD",
536
                        "block_height": 7561537,
537
                        "triggered_block_height": 0,
538
                        "address": "swth1vaavrkrm7usqg9hcwhqh2hev9m3nryw7aera8p",
539
                        "market": "wbtc1_usdc1",
540
                        "side": "sell",
541
                        "price": "0",
542
                        "quantity": "0.0011",
543
                        "available": "0",
544
                        "filled": "0.0011",
545
                        "order_status": "filled",
546
                        "order_type": "market",
547
                        "initiator": "user",
548
                        "time_in_force": "fok",
549
                        "stop_price": "0",
550
                        "trigger_type": "",
551
                        "allocated_margin_denom": "wbtc1",
552
                        "allocated_margin_amount": "0",
553
                        "is_liquidation": false,
554
                        "is_post_only": false,
555
                        "is_reduce_only": false,
556
                        "type": "",
557
                        "block_created_at": "2021-02-16T08:31:13.225303Z",
558
                        "username": "",
559
                        "id": "2315998"
560
                    },
561
                    ...
562
                ]
563
            }
564
565
566
        .. note::
567
            The market identifier is optional and acts as a filter.
568
569
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
570
                           identify which channel the notification is originated from.
571
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
572
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
573
        :return: None
574
        """
575
        await self.send({
576
            "id": message_id,
577
            "method": "get_order_history",
578
            "params": {
579
                "address": swth_address,
580
                "market": market
581
            }
582
        })
583
584
    async def get_recent_trades(self, message_id: str, market: str):
585
        """
586
        Request up  to 100 recent trades for a market.
587
588
        Example::
589
590
            ws_client.get_recent_trades('recent_trades', "swth_eth1")
591
592
        The expected return result for this function is as follows::
593
594
            {
595
                "id": "recent_trades",
596
                "sequence_number": 3,
597
                "result": [
598
                    {
599
                        "id": "0",
600
                        "block_created_at": "2021-02-16T10:21:31.346041707Z",
601
                        "taker_id": "3F71918F83D84639F505464335FD355105EE63E622CBB819AAFBBAC97368CC7A",
602
                        "taker_address": "swth1ysezxr46dhd4dzjsswqte35wfm0ml5dxx97aqt",
603
                        "taker_fee_amount": "3.2475",
604
                        "taker_fee_denom": "swth",
605
                        "taker_side": "buy",
606
                        "maker_id": "343590CF4F54FEB1E2429F60B77CD3BED701A040418AEB914BB41D561E24E7DE",
607
                        "maker_address": "swth1a5v8pyhkzjjmyw03mh9zqfakwyu0t5wkv0tf66",
608
                        "maker_fee_amount": "-0.6495",
609
                        "maker_fee_denom": "swth",
610
                        "maker_side": "sell",
611
                        "market": "swth_eth1",
612
                        "price": "0.0000182",
613
                        "quantity": "1299",
614
                        "liquidation": "",
615
                        "taker_username": "",
616
                        "maker_username": "",
617
                        "block_height": "7564715"
618
                    },
619
                    ...
620
                ]
621
            }
622
623
        .. warning::
624
            The field 'id' is sometimes '0'. This endpoint/channel does not seem to work correct.
625
626
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
627
                           identify which channel the notification is originated from.
628
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
629
        :return: None
630
        """
631
        await self.send({
632
            "id": message_id,
633
            "method": "get_recent_trades",
634
            "params": {
635
                "market": market
636
            }
637
        })
638
639
    async def get_candlesticks(self, message_id: str, market: str, granularity: int,
640
                               from_epoch: int, to_epoch: int):
641
        """
642
        Requests candlesticks for market with granularity.
643
644
        Example::
645
646
            ws_client.get_candlesticks('recent_trades', "swth_eth1")
647
648
        The subscription and channel messages are expected as follow::
649
650
            {
651
                'id': 'candlesticks.swth_eth1.1',
652
                'sequence_number': 57,
653
                'result': [
654
                    {
655
                        'id': 0,
656
                        'market':'swth_eth1',
657
                        'time': '2021-02-17T10:59:00Z',
658
                        'resolution': 1,
659
                        'open': '0.000018',
660
                        'close': '0.000018',
661
                        'high': '0.000018',
662
                        'low': '0.000018',
663
                        'volume': '5555',
664
                        'quote_volume': '0.09999'
665
                    }
666
                ]
667
            }
668
669
        .. note::
670
            Only candles with non empty volume will be returned. Expect almost none or just a few candles with a low
671
            granularity.
672
673
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
674
                           identify which channel the notification is originated from.
675
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
676
        :param granularity: Define the candlesticks granularity. Allowed values: 1, 5, 15, 30, 60, 360, 1440.
677
        :param from_epoch: Starting from epoch seconds.
678
        :param to_epoch: Ending to epoch seconds.
679
        :return: None
680
        """
681
        if granularity not in [1, 5, 15, 30, 60, 360, 1440]:
682
            raise ValueError(f"Granularity '{granularity}' not supported. Allowed values: 1, 5, 15, 30, 60, 360, 1440")
683
        await self.send({
684
            "id": message_id,
685
            "method": "get_candlesticks",
686
            "params": {
687
                "market": market,
688
                "resolution": str(granularity),
689
                "from": str(from_epoch),
690
                "to": str(to_epoch)
691
            }
692
        })
693
694
    async def get_open_orders(self, message_id: str, swth_address: str, market: Optional[str] = None):
695
        """
696
        Request open orders.
697
698
        Example::
699
700
            ws_client.get_open_orders('open_orders', "swth1p5hjhag5glkpqaj0y0vn3au7x0vz33k0gxuejk")
701
702
        The expected return result for this function is as follows::
703
704
            {
705
                "id": "open_orders",
706
                "result": [
707
                    {
708
                        "order_id": "A7C488A6AE25249E90523FCD603236342025340E3DCAE6A6312133905C41794C",
709
                        "block_height": 7564973,
710
                        "triggered_block_height": 0,
711
                        "address": "swth1p5hjhag5glkpqaj0y0vn3au7x0vz33k0gxuejk",
712
                        "market": "swth_eth1",
713
                        "side": "sell",
714
                        "price": "0.0000181",
715
                        "quantity": "58806",
716
                        "available": "58806",
717
                        "filled": "0",
718
                        "order_status": "open",
719
                        "order_type": "limit",
720
                        "initiator": "amm",
721
                        "time_in_force": "gtc",
722
                        "stop_price": "0",
723
                        "trigger_type": "",
724
                        "allocated_margin_denom": "swth",
725
                        "allocated_margin_amount": "0",
726
                        "is_liquidation": false,
727
                        "is_post_only": false,
728
                        "is_reduce_only": false,
729
                        "type": "",
730
                        "block_created_at": "2021-02-16T10:30:27.079962Z",
731
                        "username": "",
732
                        "id": "2316597"
733
                    },
734
                    ...
735
                ]
736
            }
737
738
        .. note::
739
            The market identifier is optional and acts as a filter.
740
741
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
742
                           identify which channel the notification is originated from.
743
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
744
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
745
        :return: None
746
        """
747
        await self.send({
748
            "id": message_id,
749
            "method": "get_open_orders",
750
            "params": {
751
                "address": swth_address,
752
                "market": market
753
            }
754
        })
755
756
    async def get_account_trades(self, message_id: str, swth_address: str,
757
                                 market: Optional[str] = None, page: Optional[int] = None):
758
        """
759
        Request up to 100 account trades.
760
761
        Example::
762
763
            ws_client.get_account_trades('account_trades', 'swth1vaavrkrm7usqg9hcwhqh2hev9m3nryw7aera8p')
764
765
        The expected return result for this function is as follows::
766
767
            {
768
                "id": "account_trades",
769
                "result": [
770
                    {
771
                        "base_precision": 8,
772
                        "quote_precision": 6,
773
                        "fee_precision": 6,
774
                        "order_id": "C7D7DDDCFDC68DF2D078CBD8630B657148893AC24CF8DB8F2E23293C6EDC90AD",
775
                        "market": "wbtc1_usdc1",
776
                        "side": "sell",
777
                        "quantity": "0.0001",
778
                        "price": "48745.12",
779
                        "fee_amount": "0.004875",
780
                        "fee_denom": "usdc1",
781
                        "address": "swth1vaavrkrm7usqg9hcwhqh2hev9m3nryw7aera8p",
782
                        "block_height": "7561537",
783
                        "block_created_at": "2021-02-16T08:31:13.225303Z",
784
                        "id": 289733
785
                    },
786
                    ...
787
                ]
788
            }
789
790
        .. note::
791
            The market identifier is optional and acts as a filter.
792
793
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
794
                           identify which channel the notification is originated from.
795
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
796
        :param market: Tradehub market identifier, e.g. 'swth_eth1'.
797
        :param page: Used for pagination.
798
        :return: None
799
        """
800
        await self.send({
801
            "id": message_id,
802
            "method": "get_account_trades",
803
            "params": {
804
                "address": swth_address,
805
                "market": market,
806
                "page": str(page) if page else None
807
            }
808
        })
809
810
    async def get_market_stats(self, message_id: str, market: Optional[str] = None):
811
        """
812
        Request market stats.
813
814
        Example::
815
816
            ws_client.get_market_stats('market_stats')
817
818
        The expected return result for this function is as follows::
819
820
            {
821
                "id": "market_stats",
822
                "result": {
823
                    "eth1_usdc1": {
824
                        "day_high": "1818.51",
825
                        "day_low": "1751.81",
826
                        "day_open": "1760.07",
827
                        "day_close": "1788.19",
828
                        "day_volume": "36.503",
829
                        "day_quote_volume": "65153.50224",
830
                        "index_price": "0",
831
                        "mark_price": "0",
832
                        "last_price": "1788.19",
833
                        "market": "eth1_usdc1",
834
                        "market_type": "spot",
835
                        "open_interest": "0"
836
                    },
837
                    ...
838
                }
839
            }
840
841
        .. warning::
842
            Parameter 'market' has no effect. Maybe not intended as parameter. Request will result in all market stats.
843
844
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
845
                           identify which channel the notification is originated from.
846
        :param market: Tradehub market identifier, e.g. 'swth_eth1'
847
        :return: None
848
        """
849
        # TODO market has no effect
850
        await self.send({
851
            "id": message_id,
852
            "method": "get_market_stats",
853
            "params": {
854
                "market": market
855
            }
856
        })
857
858
    async def get_leverages(self, message_id: str, swth_address: str, market: Optional[str] = None):
859
        """
860
        Request leverages.
861
862
        .. note::
863
            The market identifier is optional and acts as a filter.
864
865
        .. warning::
866
            The request method has not been tested yet.
867
868
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
869
                           identify which channel the notification is originated from.
870
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
871
        :param market: Tradehub market identifier, e.g. 'swth_eth1'.
872
        :return: None
873
        """
874
        # TODO not tested yet
875
        await self.send({
876
            "id": message_id,
877
            "method": "get_leverages",
878
            "params": {
879
                "address": swth_address,
880
                "market": market
881
            }
882
        })
883
884
    async def get_open_positions(self, message_id: str, swth_address: str, market: Optional[str] = None):
885
        """
886
        Request open positions.
887
888
        .. note::
889
            The market identifier is optional and acts as a filter.
890
891
        .. warning::
892
            The request method has not been tested yet.
893
894
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
895
                           identify which channel the notification is originated from.
896
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
897
        :param market: Tradehub market identifier, e.g. 'swth_eth1'.
898
        :return: None
899
        """
900
        # TODO not tested yet
901
        await self.send({
902
            "id": message_id,
903
            "method": "get_open_positions",
904
            "params": {
905
                "address": swth_address,
906
                "market": market
907
            }
908
        })
909
910
    async def get_closed_positions(self, message_id: str, swth_address: str, market: Optional[str] = None):
911
        """
912
        Request closed positions.
913
914
        .. note::
915
            The market identifier is optional and acts as a filter.
916
917
        .. warning::
918
            The request method has not been tested yet.
919
920
        :param message_id: Identifier that will be included in the websocket message response to allow the subscriber to
921
                           identify which channel the notification is originated from.
922
        :param swth_address: Tradehub wallet address starting with 'swth1' for mainnet and 'tswth1' for testnet.
923
        :param market: Tradehub market identifier, e.g. 'swth_eth1'.
924
        :return: None
925
        """
926
        # TODO not tested yet
927
        await self.send({
928
            "id": message_id,
929
            "method": "get_closed_positions",
930
            "params": {
931
                "address": swth_address,
932
                "market": market
933
            }
934
        })
935
936
    async def send(self, data: dict):
937
        """
938
        Send data to websocket server. Provided data will be translated to json.
939
940
        :param data: data as dictionary.
941
        :return:
942
        """
943
        await self._websocket.send(json.dumps(data))
944
945
    def open(self) -> bool:
946
        """
947
        Check if the connection is open.
948
949
        :return: Bool
950
        """
951
        if not self._websocket:
952
            return False
953
954
        return self._websocket.open
955
956
    async def disconnect(self):
957
        """
958
        Safely close the websocket connection.
959
960
        :return:
961
        """
962
        if self._websocket:
963
            await self._websocket.close()
964
965
    async def connect(self,
966
                      on_receive_message_callback: Callable,
967
                      on_connect_callback: Optional[Callable] = None,
968
                      on_error_callback: Optional[Callable] = None):
969
        """
970
        Connect to websocket server.
971
972
        .. warning::
973
            Callbacks need to be NON-BLOCKING! Otherwise the PING-PONG coroutine is blocked and the server will close
974
            the connection. You will not receive any notification about this.
975
976
        :param on_receive_message_callback: async callback which is called with the received message as dict.
977
        :param on_connect_callback: async callback which is called if websocket is connected.
978
        :param on_error_callback: async callback which is called if websocket has an error.
979
        :return: None
980
        """
981
        try:
982
            async with websockets.connect(self._uri,
983
                                          ping_interval=self._ping_interval,
984
                                          ping_timeout=self._ping_timeout) as websocket:
985
                self._websocket = websocket
986
987
                if on_connect_callback:
988
                    await on_connect_callback()
989
990
                async for message in websocket:
991
                    data = json.loads(message)
992
                    await on_receive_message_callback(data)
993
        except Exception as e:
994
            if on_error_callback:
995
                await on_error_callback(e)
996
            else:
997
                raise e
998