wiz-trader 0.1.0__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.
apis/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # apis/__init__.py
2
+ # Provision for REST API functionalities in future.
3
+ # Currently, no APIs are implemented.
apis/client.py ADDED
@@ -0,0 +1,2 @@
1
+ # apis/placeholder.py
2
+ # This is a placeholder file for future REST API implementations.
ticker/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ from .client import QuotesClient
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ __all__ = ["QuotesClient", "__version__"]
ticker/client.py ADDED
@@ -0,0 +1,170 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import logging
5
+ import random
6
+ from typing import Callable, List, Optional
7
+
8
+ import websockets
9
+ from websockets.exceptions import ConnectionClosed
10
+ from websockets.protocol import State
11
+ from dotenv import load_dotenv
12
+
13
+ # Load environment variables from .env (if available)
14
+ load_dotenv()
15
+
16
+ # Setup module-level logger with a default handler if none exists.
17
+ logger = logging.getLogger(__name__)
18
+ if not logger.handlers:
19
+ handler = logging.StreamHandler()
20
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
21
+ handler.setFormatter(formatter)
22
+ logger.addHandler(handler)
23
+
24
+
25
+ class QuotesClient:
26
+ """
27
+ A Python SDK for connecting to the Quotes Server via WebSocket.
28
+
29
+ Attributes:
30
+ base_url (str): WebSocket URL of the quotes server.
31
+ token (str): JWT token for authentication.
32
+ on_tick (Callable[[dict], None]): Callback to process received tick data.
33
+ log_level (str): Logging level. Options: "error", "info", "debug".
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ base_url: Optional[str] = None,
39
+ token: Optional[str] = None,
40
+ log_level: str = "error" # default only errors
41
+ ):
42
+ # Configure logger based on log_level.
43
+ valid_levels = {"error": logging.ERROR, "info": logging.INFO, "debug": logging.DEBUG}
44
+ if log_level not in valid_levels:
45
+ raise ValueError(f"log_level must be one of {list(valid_levels.keys())}")
46
+ logger.setLevel(valid_levels[log_level])
47
+
48
+ self.log_level = log_level
49
+ self.base_url = base_url or os.getenv("WZ__QUOTES_BASE_URL")
50
+ self.token = token or os.getenv("WZ__TOKEN")
51
+ if not self.token:
52
+ raise ValueError("JWT token must be provided as an argument or in .env (WZ__TOKEN)")
53
+ if not self.base_url:
54
+ raise ValueError("Base URL must be provided as an argument or in .env (WZ__QUOTES_BASE_URL)")
55
+
56
+ # Construct the WebSocket URL.
57
+ self.url = f"{self.base_url}?token={self.token}"
58
+ self.ws: Optional[websockets.WebSocketClientProtocol] = None
59
+ self.on_tick: Optional[Callable[[dict], None]] = None
60
+ self.subscribed_instruments: set = set()
61
+
62
+ # Backoff configuration for reconnection (in seconds)
63
+ self._backoff_base = 1
64
+ self._backoff_factor = 2
65
+ self._backoff_max = 60
66
+
67
+ logger.debug("Initialized QuotesClient with URL: %s", self.url)
68
+
69
+ async def connect(self) -> None:
70
+ """
71
+ Continuously connect to the quotes server and process incoming messages.
72
+ Implements an exponential backoff reconnection strategy.
73
+ """
74
+ backoff = self._backoff_base
75
+
76
+ while True:
77
+ try:
78
+ logger.info("Connecting to %s ...", self.url)
79
+ async with websockets.connect(self.url) as websocket:
80
+ self.ws = websocket
81
+ logger.info("Connected to the quotes server.")
82
+
83
+ # On reconnection, re-subscribe if needed.
84
+ if self.subscribed_instruments:
85
+ subscribe_msg = {
86
+ "action": "subscribe",
87
+ "instruments": list(self.subscribed_instruments)
88
+ }
89
+ await self.ws.send(json.dumps(subscribe_msg))
90
+ logger.info("Re-subscribed to instruments: %s", list(self.subscribed_instruments))
91
+
92
+ # Reset backoff after a successful connection.
93
+ backoff = self._backoff_base
94
+
95
+ await self._handle_messages()
96
+ except ConnectionClosed as e:
97
+ logger.info("Disconnected from the quotes server: %s", e)
98
+ except Exception as e:
99
+ logger.error("Connection error: %s", e, exc_info=True)
100
+
101
+ # Exponential backoff before reconnecting.
102
+ sleep_time = min(backoff, self._backoff_max)
103
+ logger.info("Reconnecting in %s seconds...", sleep_time)
104
+ await asyncio.sleep(sleep_time)
105
+ backoff *= self._backoff_factor
106
+ # Add a bit of randomness to avoid thundering herd issues.
107
+ backoff += random.uniform(0, 1)
108
+
109
+ async def _handle_messages(self) -> None:
110
+ """
111
+ Handle incoming messages and dispatch them via the on_tick callback.
112
+ """
113
+ try:
114
+ async for message in self.ws: # type: ignore
115
+ try:
116
+ tick = json.loads(message)
117
+ if self.on_tick:
118
+ self.on_tick(tick)
119
+ else:
120
+ logger.debug("Received tick (no on_tick callback set): %s", tick)
121
+ except json.JSONDecodeError:
122
+ logger.debug("Received non-JSON message: %s", message)
123
+ except ConnectionClosed as e:
124
+ logger.info("Connection closed during message handling: %s", e)
125
+
126
+ async def subscribe(self, instruments: List[str]) -> None:
127
+ """
128
+ Subscribe to a list of instruments and update the subscription list.
129
+
130
+ Args:
131
+ instruments (List[str]): List of instrument identifiers.
132
+ """
133
+ if self.ws and self.ws.state == State.OPEN:
134
+ new_instruments = set(instruments) - self.subscribed_instruments
135
+ if new_instruments:
136
+ self.subscribed_instruments.update(new_instruments)
137
+ message = {"action": "subscribe", "instruments": list(new_instruments)}
138
+ await self.ws.send(json.dumps(message))
139
+ logger.info("Sent subscription message for instruments: %s", list(new_instruments))
140
+ else:
141
+ logger.info("Instruments already subscribed: %s", instruments)
142
+ else:
143
+ logger.info("Cannot subscribe: WebSocket is not connected.")
144
+
145
+ async def unsubscribe(self, instruments: List[str]) -> None:
146
+ """
147
+ Unsubscribe from a list of instruments and update the subscription list.
148
+
149
+ Args:
150
+ instruments (List[str]): List of instrument identifiers.
151
+ """
152
+ if self.ws and self.ws.state == State.OPEN:
153
+ unsub_set = set(instruments)
154
+ if unsub_set & self.subscribed_instruments:
155
+ self.subscribed_instruments.difference_update(unsub_set)
156
+ message = {"action": "unsubscribe", "instruments": list(unsub_set)}
157
+ await self.ws.send(json.dumps(message))
158
+ logger.info("Sent unsubscription message for instruments: %s", list(unsub_set))
159
+ else:
160
+ logger.info("No matching instruments found in current subscription.")
161
+ else:
162
+ logger.info("Cannot unsubscribe: WebSocket is not connected.")
163
+
164
+ async def close(self) -> None:
165
+ """
166
+ Close the WebSocket connection.
167
+ """
168
+ if self.ws:
169
+ await self.ws.close()
170
+ logger.info("WebSocket connection closed.")
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.2
2
+ Name: wiz_trader
3
+ Version: 0.1.0
4
+ Summary: A Python SDK for connecting to the Wizzer.
5
+ Home-page: https://bitbucket.org/wizzer-tech/quotes_sdk.git
6
+ Author: Pawan Wagh
7
+ Author-email: pawan@wizzer.in
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Financial and Insurance Industry
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Topic :: Office/Business :: Financial
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.6
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: websockets
19
+ Requires-Dist: python-dotenv
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
29
+
30
+ # WizTrader SDK
31
+
32
+ A Python SDK for connecting to the Wizzer.
33
+
34
+ ## Installation
35
+
36
+ Install the dependencies:
37
+ ```
38
+ pip install -r requirements.txt
39
+ ```
40
+
41
+ ## Usage Example
42
+
43
+ See `example.py` for a complete example.
@@ -0,0 +1,8 @@
1
+ apis/__init__.py,sha256=30bmGcN4MBabbN1-7wuowVRfwjuhdszhnF6uOoyIJoU,109
2
+ apis/client.py,sha256=NJ9cPIK0LIe1rhC8CIRIXRvQ3zksIuNJCFlnTJlYm9E,88
3
+ ticker/__init__.py,sha256=f4xaHOWLxEyckZpHbZFWHUbiaqqOTJ318Xi8vDJWGdg,99
4
+ ticker/client.py,sha256=DR0RIqmLjuo3ZDs2rVzMib-NRyjh96Xq09ZhI7vR56k,6245
5
+ wiz_trader-0.1.0.dist-info/METADATA,sha256=d36YvgtqisRnNhdZ9HyECOYGHwJ-TNqlKznMD_5AT1k,1158
6
+ wiz_trader-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
7
+ wiz_trader-0.1.0.dist-info/top_level.txt,sha256=VDgCJWC-MmXn4pnnJXeasXwqKHN0Qhu0TmbEPbGU2Ro,12
8
+ wiz_trader-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ apis
2
+ ticker