vortex-api 1.0.5__tar.gz → 1.0.6.dev2__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vortex_api
3
- Version: 1.0.5
3
+ Version: 1.0.6.dev2
4
4
  Summary: Vortex APIs to place orders in AsthaTrade Flow application
5
5
  Home-page: https://vortex.asthatrade.com
6
6
  Download-URL: https://github.com/AsthaTech/pyvortex
@@ -57,6 +57,32 @@ client.place_order(
57
57
  client.orders(limit=20,offset=1)
58
58
 
59
59
 
60
+ ```
61
+
62
+ # Connecting to websocket
63
+
64
+ ```
65
+ from vortex_api import VortexFeed
66
+ from vortex_api import Constants as Vc
67
+
68
+ def main():
69
+ # Get access token from any of the login methods
70
+ wire = VortexFeed(access_token)
71
+
72
+ wire.on_price_update = on_price_update
73
+ wire.on_order_update = on_order_update
74
+ wire.on_connect = on_connect
75
+
76
+ def on_price_update(ws,data):
77
+ print(data)
78
+
79
+ def on_order_update(ws,data):
80
+ print(data)
81
+
82
+ def on_connect(ws, response):
83
+ ws.subscribe(Vc.ExchangeTypes.NSE_EQUITY, 26000) # Subscribe to NIFTY 50
84
+ ws.subscribe(Vc.ExchangeTypes.NSE_EQUITY, 26000) # Subscribe to BANKNIFTY
85
+
60
86
  ```
61
87
  Refer to the [python document](https://vortex.asthatrade.com/docs/pyvortex/vortex_api.html) for all methods and features
62
88
 
@@ -36,6 +36,32 @@ client.place_order(
36
36
  client.orders(limit=20,offset=1)
37
37
 
38
38
 
39
+ ```
40
+
41
+ # Connecting to websocket
42
+
43
+ ```
44
+ from vortex_api import VortexFeed
45
+ from vortex_api import Constants as Vc
46
+
47
+ def main():
48
+ # Get access token from any of the login methods
49
+ wire = VortexFeed(access_token)
50
+
51
+ wire.on_price_update = on_price_update
52
+ wire.on_order_update = on_order_update
53
+ wire.on_connect = on_connect
54
+
55
+ def on_price_update(ws,data):
56
+ print(data)
57
+
58
+ def on_order_update(ws,data):
59
+ print(data)
60
+
61
+ def on_connect(ws, response):
62
+ ws.subscribe(Vc.ExchangeTypes.NSE_EQUITY, 26000) # Subscribe to NIFTY 50
63
+ ws.subscribe(Vc.ExchangeTypes.NSE_EQUITY, 26000) # Subscribe to BANKNIFTY
64
+
39
65
  ```
40
66
  Refer to the [python document](https://vortex.asthatrade.com/docs/pyvortex/vortex_api.html) for all methods and features
41
67
 
@@ -32,7 +32,11 @@ setup(
32
32
  packages=["vortex_api"],
33
33
  install_requires=[
34
34
  "requests>=2.25.1",
35
- "wrapt>=1.15.0"
35
+ "wrapt>=1.15.0",
36
+ "six>=1.11.0",
37
+ "pyOpenSSL>=17.5.0",
38
+ "python-dateutil>=2.6.1",
39
+ "autobahn[twisted]==19.11.2"
36
40
  ],
37
41
  classifiers=[
38
42
  "Intended Audience :: Developers",
@@ -34,4 +34,5 @@ Getting started
34
34
  """
35
35
  from __future__ import unicode_literals, absolute_import
36
36
  from vortex_api.api import AsthaTradeVortexAPI,Constants
37
- __all__ = [AsthaTradeVortexAPI,Constants]
37
+ from vortex_api.vortex_feed import VortexFeed
38
+ __all__ = [AsthaTradeVortexAPI,Constants,VortexFeed]
@@ -2,7 +2,7 @@ __name__ = "vortex_api"
2
2
  __description__ = "Vortex APIs to place orders in AsthaTrade Flow application"
3
3
  __url__ = "https://vortex.asthatrade.com"
4
4
  __download_url__ = "https://github.com/AsthaTech/pyvortex"
5
- __version__ = "1.0.5"
5
+ __version__ = "1.0.6.dev2"
6
6
  __author__ = "Astha Credit & Securities Pvt Ltd."
7
7
  __author_email__ = "tech@asthatrade.com"
8
8
  __license__ = "MIT"
@@ -3,8 +3,6 @@ import csv
3
3
  import datetime
4
4
  import logging
5
5
  from enum import Enum
6
- from functools import wraps
7
- from typing import Type
8
6
  import inspect
9
7
  import wrapt
10
8
 
@@ -571,6 +569,3 @@ class AsthaTradeVortexAPI:
571
569
 
572
570
  return False
573
571
 
574
-
575
-
576
-
@@ -0,0 +1,604 @@
1
+ import six
2
+ import sys
3
+ import time
4
+ import json
5
+ import struct
6
+ import logging
7
+ import threading
8
+ from twisted.internet import reactor, ssl
9
+ from twisted.python import log as twisted_log
10
+ from twisted.internet.protocol import ReconnectingClientFactory
11
+ from autobahn.twisted.websocket import WebSocketClientProtocol, \
12
+ WebSocketClientFactory, connectWS
13
+
14
+ from .__version__ import __version__, __name__
15
+
16
+ log = logging.getLogger(__name__)
17
+
18
+ class ClientProtocol(WebSocketClientProtocol):
19
+ """
20
+ A WebSocket client protocol that implements ping-pong keepalive.
21
+
22
+ Args:
23
+ PING_INTERVAL: The interval in seconds between sending pings.
24
+ KEEPALIVE_INTERVAL: The interval in seconds after which a connection is considered dead if no pongs have been received.
25
+ """
26
+ PING_INTERVAL = 2.5
27
+ KEEPALIVE_INTERVAL = 5
28
+
29
+ _ping_message = ""
30
+ _next_ping = None
31
+ _next_pong_check = None
32
+ _last_pong_time = None
33
+ _last_ping_time = None
34
+
35
+ def __init__(self, *args, **kwargs):
36
+ super(ClientProtocol, self).__init__(*args, **kwargs)
37
+
38
+ def onConnect(self, response):
39
+ """
40
+ Called when the connection is established.
41
+
42
+ Args:
43
+ response: The response from the server.
44
+ """
45
+ self.factory.ws = self
46
+
47
+ if self.factory.on_connect:
48
+ self.factory.on_connect(self, response)
49
+
50
+ # Reset reconnect on successful reconnect
51
+ self.factory.resetDelay()
52
+
53
+ def onOpen(self):
54
+ """
55
+ Called when the connection is opened.
56
+
57
+ Sends a ping and starts a timer to check for pongs.
58
+ """
59
+ # send ping
60
+ self._loop_ping()
61
+ # Start a timer to check for pongs
62
+ self._loop_pong_check()
63
+
64
+ if self.factory.on_open:
65
+ self.factory.on_open(self)
66
+
67
+ def onMessage(self, payload, is_binary):
68
+ """
69
+ Called when a message is received.
70
+
71
+ Args:
72
+ payload: The message payload.
73
+ is_binary: Whether the message is binary.
74
+ """
75
+ if self.factory.on_message:
76
+ self.factory.on_message(self, payload, is_binary)
77
+
78
+ def onClose(self, was_clean, code, reason):
79
+ """
80
+ Called when the connection is closed.
81
+
82
+ Args:
83
+ was_clean: Whether the connection was closed cleanly.
84
+ code: The close code.
85
+ reason: The close reason.
86
+ """
87
+ print("was_clean", was_clean)
88
+ if not was_clean:
89
+ if self.factory.on_error:
90
+ self.factory.on_error(self, code, reason)
91
+
92
+ if self.factory.on_close:
93
+ self.factory.on_close(self, code, reason)
94
+
95
+ # Cancel next ping and timer
96
+ self._last_ping_time = None
97
+ self._last_pong_time = None
98
+
99
+ if self._next_ping:
100
+ self._next_ping.cancel()
101
+
102
+ if self._next_pong_check:
103
+ self._next_pong_check.cancel()
104
+
105
+ def onPong(self, response):
106
+ """
107
+ Called when a pong message is received.
108
+
109
+ Args:
110
+ response: The pong message.
111
+ """
112
+ if self._last_pong_time and self.factory.debug:
113
+ log.debug("last pong was {} seconds back.".format(time.time() - self._last_pong_time))
114
+
115
+ self._last_pong_time = time.time()
116
+
117
+ if self.factory.debug:
118
+ log.debug("pong => {}".format(response))
119
+
120
+ def _loop_ping(self):
121
+ """
122
+ Sends a ping message every X seconds.
123
+ """
124
+ if self.factory.debug:
125
+ log.debug("ping => {}".format(self._ping_message))
126
+ if self._last_ping_time:
127
+ log.debug("last ping was {} seconds back.".format(time.time() - self._last_ping_time))
128
+
129
+ # Set current time as last ping time
130
+ self._last_ping_time = time.time()
131
+ # Send a ping message to server
132
+ self.sendPing(self._ping_message)
133
+
134
+ # Call self after X seconds
135
+ self._next_ping = self.factory.reactor.callLater(self.PING_INTERVAL, self._loop_ping)
136
+
137
+ def _loop_pong_check(self):
138
+ """
139
+ Checks if the connection is still alive by checking the last pong time.
140
+ If no pong has been received in X seconds, the connection is considered dead and is dropped.
141
+ """
142
+ if self._last_pong_time:
143
+ # No pong message since long time, so init reconnect
144
+ last_pong_diff = time.time() - self._last_pong_time
145
+ if last_pong_diff > (2 * self.PING_INTERVAL):
146
+ if self.factory.debug:
147
+ log.debug("Last pong was {} seconds ago. So dropping connection to reconnect.".format(
148
+ last_pong_diff))
149
+ # drop existing connection to avoid ghost connection
150
+ self.dropConnection(abort=True)
151
+
152
+ # Call self after X seconds
153
+ self._next_pong_check = self.factory.reactor.callLater(self.PING_INTERVAL, self._loop_pong_check)
154
+
155
+ class ClientFactory(WebSocketClientFactory,ReconnectingClientFactory):
156
+ """
157
+ A WebSocket client factory that implements reconnect logic.
158
+
159
+ Args:
160
+ protocol: The WebSocket protocol to use.
161
+ maxDelay: The maximum delay in seconds between retries.
162
+ maxRetries: The maximum number of retries.
163
+ """
164
+ protocol = ClientProtocol
165
+ maxDelay = 5
166
+ maxRetries = 10
167
+
168
+ _last_connection_time = None
169
+
170
+ def __init__(self, *args, **kwargs):
171
+ """Initialize with default callback method values."""
172
+ self.debug = True
173
+ self.ws = None
174
+ self.on_open = None
175
+ self.on_error = None
176
+ self.on_close = None
177
+ self.on_message = None
178
+ self.on_connect = None
179
+ self.on_reconnect = None
180
+ self.on_noreconnect = None
181
+
182
+ super(ClientFactory, self).__init__(*args, **kwargs)
183
+
184
+ def startedConnecting(self, connector):
185
+ """Called when the connection is started or reconnected."""
186
+ if not self._last_connection_time and self.debug:
187
+ log.debug("Start WebSocket connection.")
188
+
189
+ self._last_connection_time = time.time()
190
+
191
+ def clientConnectionFailed(self, connector, reason):
192
+ """Called when the connection fails."""
193
+ if self.retries > 0:
194
+ log.error("Retrying connection. Retry attempt count: {}. Next retry in around: {} seconds".format(self.retries, int(round(self.delay))))
195
+
196
+ # on reconnect callback
197
+ if self.on_reconnect:
198
+ self.on_reconnect(self.retries)
199
+
200
+ # Retry the connection
201
+ self.retry(connector)
202
+ self.send_noreconnect()
203
+
204
+ def clientConnectionLost(self, connector, reason):
205
+ """Called when the connection is lost."""
206
+ if self.retries > 0:
207
+ # on reconnect callback
208
+ if self.on_reconnect:
209
+ self.on_reconnect(self.retries)
210
+
211
+ # Retry the connection
212
+ self.retry(connector)
213
+ self.send_noreconnect()
214
+
215
+ def send_noreconnect(self):
216
+ """Called when the maximum number of retries has been exhausted."""
217
+ if self.maxRetries is not None and (self.retries > self.maxRetries):
218
+ if self.debug:
219
+ log.debug("Maximum retries ({}) exhausted.".format(self.maxRetries))
220
+ # Stop the loop for exceeding max retry attempts
221
+ self.stop()
222
+
223
+ if self.on_noreconnect:
224
+ self.on_noreconnect()
225
+
226
+ class VortexFeed:
227
+ """
228
+ The WebSocket client for connecting to vortex's live price and order streaming service
229
+ """
230
+ CONNECT_TIMEOUT = 30
231
+ # Default Reconnect max delay.
232
+ RECONNECT_MAX_DELAY = 60
233
+ # Default reconnect attempts
234
+ RECONNECT_MAX_TRIES = 50
235
+ _is_first_connect = True
236
+ _message_subscribe = "subscribe"
237
+ _message_unsubscribe = "unsubscribe"
238
+
239
+ def __init__(self, access_token: str, websocket_endpoint="wss://wire.asthatrade.com/ws",reconnect=True, reconnect_max_tries=RECONNECT_MAX_TRIES, reconnect_max_delay=RECONNECT_MAX_DELAY,
240
+ connect_timeout=CONNECT_TIMEOUT) -> None:
241
+ self._maximum_reconnect_max_tries = self.RECONNECT_MAX_TRIES
242
+ self._minimum_reconnect_max_delay = 0
243
+ if reconnect == False:
244
+ self.reconnect_max_tries = 0
245
+ elif reconnect_max_tries > self._maximum_reconnect_max_tries:
246
+ log.warning("`reconnect_max_tries` can not be more than {val}. Setting to highest possible value - {val}.".format(
247
+ val=self._maximum_reconnect_max_tries))
248
+ self.reconnect_max_tries = self._maximum_reconnect_max_tries
249
+ else:
250
+ self.reconnect_max_tries = reconnect_max_tries
251
+
252
+ if reconnect_max_delay < self._minimum_reconnect_max_delay:
253
+ log.warning("`reconnect_max_delay` can not be less than {val}. Setting to lowest possible value - {val}.".format(
254
+ val=self._minimum_reconnect_max_delay))
255
+ self.reconnect_max_delay = self._minimum_reconnect_max_delay
256
+ else:
257
+ self.reconnect_max_delay = reconnect_max_delay
258
+
259
+ self.connect_timeout = connect_timeout
260
+ self.socket_url = websocket_endpoint+"?auth_token="+access_token
261
+ self.access_token = access_token
262
+ self.socket_token = self.__getSocketToken__(self.access_token)
263
+
264
+ self.debug = True
265
+ # self.on_price_update = None
266
+ self.on_price_update = None
267
+ self.on_open = None
268
+ self.on_close = None
269
+ self.on_error = None
270
+ self.on_connect = None
271
+ self.on_message = None
272
+ self.on_reconnect = None
273
+ self.on_noreconnect = None
274
+ self.on_order_update = None
275
+ self.subscribed_tokens = {}
276
+ pass
277
+
278
+ def __getSocketToken__(self,access_token: str)->str:
279
+ return
280
+
281
+ def _create_connection(self, url, **kwargs):
282
+ self.factory = ClientFactory(url, **kwargs)
283
+ self.ws = self.factory.ws
284
+ self.factory.debug = self.debug
285
+
286
+ self.factory.on_open = self._on_open
287
+ self.factory.on_error = self._on_error
288
+ self.factory.on_close = self._on_close
289
+ self.factory.on_message = self._on_message
290
+ self.factory.on_connect = self._on_connect
291
+ self.factory.on_reconnect = self._on_reconnect
292
+ self.factory.on_noreconnect = self._on_noreconnect
293
+
294
+ self.factory.maxDelay = self.reconnect_max_delay
295
+ self.factory.maxRetries = self.reconnect_max_tries
296
+
297
+ def _user_agent(self):
298
+ return (__name__ + "-python/").capitalize() + __version__
299
+
300
+ def connect(self, threaded=False, disable_ssl_verification=False):
301
+ """
302
+ Establish a websocket connection.
303
+ - `disable_ssl_verification` disables building ssl context
304
+ """
305
+ # Init WebSocket client factory
306
+ self._create_connection(self.socket_url,
307
+ useragent=self._user_agent())
308
+
309
+ # Set SSL context
310
+ context_factory = None
311
+ if self.factory.isSecure and not disable_ssl_verification:
312
+ context_factory = ssl.ClientContextFactory()
313
+
314
+ # Establish WebSocket connection to a server
315
+ connectWS(self.factory, contextFactory=context_factory, timeout=self.connect_timeout)
316
+
317
+ if self.debug:
318
+ twisted_log.startLogging(sys.stdout)
319
+
320
+ # Run in seperate thread of blocking
321
+ opts = {}
322
+ # Run when reactor is not running
323
+ if not reactor.running:
324
+ if threaded:
325
+ # Signals are not allowed in non main thread by twisted so suppress it.
326
+ opts["installSignalHandlers"] = False
327
+ self.websocket_thread = threading.Thread(target=reactor.run, kwargs=opts)
328
+ self.websocket_thread.daemon = True
329
+ self.websocket_thread.start()
330
+ else:
331
+ reactor.run(**opts)
332
+ else:
333
+ print(reactor.running)
334
+
335
+ def is_connected(self):
336
+ """Check if WebSocket connection is established."""
337
+ if self.ws and self.ws.state == self.ws.STATE_OPEN:
338
+ return True
339
+ else:
340
+ return False
341
+
342
+ def _close(self, code=None, reason=None):
343
+ """Close the WebSocket connection."""
344
+ if self.ws:
345
+ self.ws.sendClose(code, reason)
346
+
347
+ def close(self, code=None, reason=None):
348
+ """Close the WebSocket connection."""
349
+ self.stop_retry()
350
+ self._close(code, reason)
351
+
352
+ def stop(self):
353
+ """Stop the event loop. Should be used if main thread has to be closed in `on_close` method.
354
+ Reconnection mechanism cannot happen past this method
355
+ """
356
+ reactor.stop()
357
+
358
+ def stop_retry(self):
359
+ """Stop auto retry when it is in progress."""
360
+ if self.factory:
361
+ self.factory.stopTrying()
362
+
363
+ def subscribe(self, exchange,token,mode):
364
+ """
365
+ Subscribe to a list of instrument_tokens.
366
+ - `instrument_tokens` is list of instrument instrument_tokens to subscribe
367
+ """
368
+ try:
369
+ self.ws.sendMessage(six.b(json.dumps({"message_type": self._message_subscribe, "segment_id": exchange,"token": token,"mode": mode})))
370
+
371
+ try:
372
+ self.subscribed_tokens[exchange][token] = mode
373
+ except KeyError:
374
+ self.subscribed_tokens[exchange] = {}
375
+ self.subscribed_tokens[exchange][token] = mode
376
+
377
+ return True
378
+ except Exception as e:
379
+ self._close(reason="Error while subscribe: {}".format(str(e)))
380
+ raise
381
+
382
+ def unsubscribe(self, exchange,token):
383
+ """
384
+ Unsubscribe the given list of instrument_tokens.
385
+ - `instrument_tokens` is list of instrument_tokens to unsubscribe.
386
+ """
387
+ try:
388
+ self.ws.sendMessage(six.b(json.dumps({"message_type": self._message_unsubscribe, "segment_id": exchange,"token": token})))
389
+
390
+ try:
391
+ del(self.subscribed_tokens[exchange][token])
392
+ except KeyError:
393
+ pass
394
+
395
+ return True
396
+ except Exception as e:
397
+ self._close(reason="Error while unsubscribe: {}".format(str(e)))
398
+ raise
399
+
400
+ def resubscribe(self):
401
+ """Resubscribe to all current subscribed tokens."""
402
+ modes = {}
403
+
404
+ for exchange in self.subscribed_tokens:
405
+ for token in self.subscribed_tokens[exchange]:
406
+ self.subscribe(exchange=exchange, token=token)
407
+
408
+ for token in self.subscribed_tokens:
409
+ m = self.subscribed_tokens[token]
410
+
411
+ if not modes.get(m):
412
+ modes[m] = []
413
+
414
+ modes[m].append(token)
415
+
416
+ for mode in modes:
417
+ if self.debug:
418
+ log.debug("Resubscribe and set mode: {} - {}".format(mode, modes[mode]))
419
+
420
+ self.subscribe(modes[mode])
421
+
422
+ def _on_connect(self, ws, response):
423
+ self.ws = ws
424
+ if self.on_connect:
425
+ self.on_connect(self, response)
426
+
427
+ def _on_close(self, ws, code, reason):
428
+ """Call `on_close` callback when connection is closed."""
429
+ log.error("Connection closed: {} - {}".format(code, str(reason)))
430
+
431
+ if self.on_close:
432
+ self.on_close(self, code, reason)
433
+
434
+ def _on_error(self, ws, code, reason):
435
+ """Call `on_error` callback when connection throws an error."""
436
+ log.error("Connection error: {} - {}".format(code, str(reason)))
437
+
438
+ if self.on_error:
439
+ self.on_error(self, code, reason)
440
+
441
+ def _on_message(self, ws, payload, is_binary):
442
+ """Call `on_message` callback when text message is received."""
443
+ if self.on_message:
444
+ self.on_message(self, payload, is_binary)
445
+
446
+ # If the message is binary, parse it and send it to the callback.
447
+ if self.on_price_update and is_binary and len(payload) > 4:
448
+ self.on_price_update(self, self._parse_binary(payload))
449
+
450
+ # Parse text messages
451
+ if not is_binary:
452
+ self._parse_text_message(payload)
453
+
454
+ def _on_open(self, ws):
455
+ # Resubscribe if its reconnect
456
+ if not self._is_first_connect:
457
+ self.resubscribe()
458
+
459
+ # Set first connect to false once its connected first time
460
+ self._is_first_connect = False
461
+
462
+ if self.on_open:
463
+ return self.on_open(self)
464
+
465
+ def _on_reconnect(self, attempts_count):
466
+ if self.on_reconnect:
467
+ return self.on_reconnect(self, attempts_count)
468
+
469
+ def _on_noreconnect(self):
470
+ if self.on_noreconnect:
471
+ return self.on_noreconnect(self)
472
+
473
+ def _parse_text_message(self, payload):
474
+ """Parse text message."""
475
+ # Decode unicode data
476
+ if not six.PY2 and type(payload) == bytes:
477
+ payload = payload.decode("utf-8")
478
+
479
+ try:
480
+ data = json.loads(payload)
481
+ except ValueError:
482
+ return
483
+
484
+ # Order update callback
485
+ if self.on_order_update and data.get("type") and data.get("data"):
486
+ self.on_order_update(self, data)
487
+
488
+ def _parse_binary(self, bin):
489
+ """Parse binary data to a (list of) ticks structure."""
490
+ packets = self._split_packets(bin) # split data to individual ticks packet
491
+ data = []
492
+
493
+ for packet in packets:
494
+ if len(packet) == 19:
495
+ format_string = "<7sid"
496
+ exchange, token, last_trade_price = struct.unpack(format_string, packet)
497
+ exchange = exchange.decode("utf-8").rstrip('\x00')
498
+ data.append({
499
+ "exchange" : exchange,
500
+ "token": token,
501
+ "last_trade_price": last_trade_price
502
+ })
503
+ elif len(packet) == 59:
504
+ format_string = "<7sididdddi"
505
+ exchange, token, last_trade_price, last_trade_time, open_price, high_price, low_price, close_price, volume = struct.unpack(format_string, packet)
506
+ exchange = exchange.decode("utf-8").rstrip('\x00')
507
+ data.append({
508
+ "exchange" : exchange,
509
+ "token": token,
510
+ "last_trade_price": last_trade_price,
511
+ "last_trade_time": last_trade_time,
512
+ "open_price": open_price,
513
+ "high_price": high_price,
514
+ "low_price": low_price,
515
+ "close_price": close_price,
516
+ "volume": volume
517
+ })
518
+ elif len(packet) == 263:
519
+ format_string = "<7siiidiidqqidddddiidiidiidiidiidiidiidiidiidiiii"
520
+ unpacked_data = struct.unpack(format_string, packet)
521
+ exchange = unpacked_data[0].decode("utf-8").rstrip('\x00')
522
+ data.append({
523
+ "exchange" : exchange,
524
+ "token": unpacked_data[1],
525
+ "last_trade_time": unpacked_data[2],
526
+ "last_update_time": unpacked_data[3],
527
+ "last_trade_price": unpacked_data[4],
528
+ "last_trade_quantity": unpacked_data[5],
529
+ "volume": unpacked_data[6],
530
+ "average_trade_price": unpacked_data[7],
531
+ "total_buy_quantity": unpacked_data[8],
532
+ "total_sell_quantity": unpacked_data[9],
533
+ "open_interest": unpacked_data[10],
534
+ "open_price": unpacked_data[11],
535
+ "high_price": unpacked_data[12],
536
+ "low_price": unpacked_data[13],
537
+ "close_price": unpacked_data[14],
538
+ "depth": {
539
+ "buy": [{
540
+ "price": unpacked_data[15],
541
+ "quantity": unpacked_data[16],
542
+ "orders": unpacked_data[17],
543
+ },{
544
+ "price": unpacked_data[18],
545
+ "quantity": unpacked_data[19],
546
+ "orders": unpacked_data[20],
547
+ },{
548
+ "price": unpacked_data[21],
549
+ "quantity": unpacked_data[22],
550
+ "orders": unpacked_data[23],
551
+ },{
552
+ "price": unpacked_data[24],
553
+ "quantity": unpacked_data[25],
554
+ "orders": unpacked_data[26],
555
+ },{
556
+ "price": unpacked_data[27],
557
+ "quantity": unpacked_data[28],
558
+ "orders": unpacked_data[29],
559
+ }],
560
+ "sell": [{
561
+ "price": unpacked_data[30],
562
+ "quantity": unpacked_data[31],
563
+ "orders": unpacked_data[32],
564
+ },{
565
+ "price": unpacked_data[33],
566
+ "quantity": unpacked_data[34],
567
+ "orders": unpacked_data[35],
568
+ },{
569
+ "price": unpacked_data[36],
570
+ "quantity": unpacked_data[37],
571
+ "orders": unpacked_data[38],
572
+ },{
573
+ "price": unpacked_data[39],
574
+ "quantity": unpacked_data[40],
575
+ "orders": unpacked_data[41],
576
+ },{
577
+ "price": unpacked_data[42],
578
+ "quantity": unpacked_data[43],
579
+ "orders": unpacked_data[44],
580
+ }]
581
+ }
582
+ })
583
+ return data
584
+
585
+ def _unpack_int(self, bin, start, end, byte_format="H"):
586
+ """Unpack binary data as unsgined interger."""
587
+ return struct.unpack("<" + byte_format, bin[start:end])[0]
588
+
589
+ def _split_packets(self, bin):
590
+ """Split the data to individual packets """
591
+ # Ignore heartbeat data.
592
+ if len(bin) < 2:
593
+ return []
594
+
595
+ number_of_packets = self._unpack_int(bin, 0, 2, byte_format="H")
596
+ packets = []
597
+
598
+ j = 2
599
+ for i in range(number_of_packets):
600
+ packet_length = self._unpack_int(bin, j, j + 2, byte_format="H")
601
+ packets.append(bin[j + 2: j + 2 + packet_length])
602
+ j = j + 2 + packet_length
603
+
604
+ return packets
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vortex-api
3
- Version: 1.0.5
3
+ Version: 1.0.6.dev2
4
4
  Summary: Vortex APIs to place orders in AsthaTrade Flow application
5
5
  Home-page: https://vortex.asthatrade.com
6
6
  Download-URL: https://github.com/AsthaTech/pyvortex
@@ -57,6 +57,32 @@ client.place_order(
57
57
  client.orders(limit=20,offset=1)
58
58
 
59
59
 
60
+ ```
61
+
62
+ # Connecting to websocket
63
+
64
+ ```
65
+ from vortex_api import VortexFeed
66
+ from vortex_api import Constants as Vc
67
+
68
+ def main():
69
+ # Get access token from any of the login methods
70
+ wire = VortexFeed(access_token)
71
+
72
+ wire.on_price_update = on_price_update
73
+ wire.on_order_update = on_order_update
74
+ wire.on_connect = on_connect
75
+
76
+ def on_price_update(ws,data):
77
+ print(data)
78
+
79
+ def on_order_update(ws,data):
80
+ print(data)
81
+
82
+ def on_connect(ws, response):
83
+ ws.subscribe(Vc.ExchangeTypes.NSE_EQUITY, 26000) # Subscribe to NIFTY 50
84
+ ws.subscribe(Vc.ExchangeTypes.NSE_EQUITY, 26000) # Subscribe to BANKNIFTY
85
+
60
86
  ```
61
87
  Refer to the [python document](https://vortex.asthatrade.com/docs/pyvortex/vortex_api.html) for all methods and features
62
88
 
@@ -5,6 +5,7 @@ setup.py
5
5
  vortex_api/__init__.py
6
6
  vortex_api/__version__.py
7
7
  vortex_api/api.py
8
+ vortex_api/vortex_feed.py
8
9
  vortex_api.egg-info/PKG-INFO
9
10
  vortex_api.egg-info/SOURCES.txt
10
11
  vortex_api.egg-info/dependency_links.txt
@@ -0,0 +1,6 @@
1
+ requests>=2.25.1
2
+ wrapt>=1.15.0
3
+ six>=1.11.0
4
+ pyOpenSSL>=17.5.0
5
+ python-dateutil>=2.6.1
6
+ autobahn[twisted]==19.11.2
@@ -1,2 +0,0 @@
1
- requests>=2.25.1
2
- wrapt>=1.15.0
File without changes
File without changes