wiz-trader 0.34.0__py3-none-any.whl → 0.36.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.
wiz_trader/__init__.py CHANGED
@@ -3,6 +3,6 @@
3
3
  from .quotes import QuotesClient
4
4
  from .apis import WizzerClient
5
5
 
6
- __version__ = "0.34.0"
6
+ __version__ = "0.36.0"
7
7
 
8
8
  __all__ = ["QuotesClient", "WizzerClient"]
wiz_trader/apis/client.py CHANGED
@@ -160,6 +160,13 @@ class WizzerClient:
160
160
  WEIGHTAGE_SCHEME_FLOAT_ADJUSTED_MARKET_CAP_WEIGHTED = "float_adjusted_market_cap_weighted"
161
161
  WEIGHTAGE_SCHEME_FUNDAMENTAL_WEIGHTED = "fundamental_weighted"
162
162
  WEIGHTAGE_SCHEME_CUSTOM_WEIGHTED = "custom_weighted"
163
+
164
+ # KV data type constants
165
+ KV_TYPE_STRING = "string"
166
+ KV_TYPE_BOOLEAN = "boolean"
167
+ KV_TYPE_NUMBER = "number"
168
+ KV_TYPE_ARRAY = "array"
169
+ KV_TYPE_OBJECT = "object"
163
170
 
164
171
  # URIs to various API endpoints
165
172
  _routes = {
@@ -209,7 +216,17 @@ class WizzerClient:
209
216
 
210
217
  # Screener API endpoints
211
218
  "screener.run": "/datahub/screener",
212
- "screener.fields": "/datahub/screener/metadata"
219
+ "screener.fields": "/datahub/screener/metadata",
220
+
221
+ # KV store endpoints
222
+ "kv.create": "/kv/{strategy_id}/{key}",
223
+ "kv.get": "/kv/{strategy_id}/{key}",
224
+ "kv.update": "/kv/{strategy_id}/{key}",
225
+ "kv.patch": "/kv/{strategy_id}/{key}",
226
+ "kv.delete": "/kv/{strategy_id}/{key}",
227
+ "kv.list": "/kv/{strategy_id}",
228
+ "kv.keys": "/kv/{strategy_id}/keys",
229
+ "kv.delete_all": "/kv/{strategy_id}/all"
213
230
  }
214
231
 
215
232
  def __init__(
@@ -1794,3 +1811,426 @@ class WizzerClient:
1794
1811
  if hasattr(e.response, 'text'):
1795
1812
  logger.error("Response content: %s", e.response.text)
1796
1813
  raise
1814
+
1815
+ # ===== KV STORE METHODS =====
1816
+
1817
+ def create_kv(self, key: str, value: Any, ttl: Optional[int] = None) -> Dict[str, Any]:
1818
+ """
1819
+ Create a new key-value pair.
1820
+
1821
+ Args:
1822
+ key (str): The key for the KV pair.
1823
+ value (Any): The value to store (any JSON-serializable type).
1824
+ ttl (Optional[int]): Time to live in seconds. If provided, the key will expire after this duration.
1825
+
1826
+ Returns:
1827
+ Dict[str, Any]: Response containing key, value, type, ttl, createdAt, updatedAt.
1828
+
1829
+ Example Response:
1830
+ {
1831
+ "key": "state",
1832
+ "value": {"positions": 30},
1833
+ "type": "object",
1834
+ "ttl": 100,
1835
+ "createdAt": "2023-10-01T10:00:00Z",
1836
+ "updatedAt": "2023-10-01T10:00:00Z"
1837
+ }
1838
+
1839
+ Raises:
1840
+ ValueError: If key is empty or invalid.
1841
+ requests.RequestException: If key already exists (409 Conflict) or API request fails.
1842
+
1843
+ Example:
1844
+ client.create_kv(key="portfolio_state", value={"positions": 30}, ttl=3600)
1845
+ """
1846
+ if not key or not key.strip():
1847
+ raise ValueError("Key cannot be empty or whitespace")
1848
+
1849
+ # Get strategy information
1850
+ strategy_info = self._get_strategy()
1851
+ endpoint = self._routes["kv.create"].format(strategy_id=strategy_info["id"], key=key)
1852
+
1853
+ # Build request payload
1854
+ data = {"value": value}
1855
+ if ttl is not None:
1856
+ data["ttl"] = ttl
1857
+
1858
+ logger.debug("Creating KV pair: key=%s, type=%s, ttl=%s", key, type(value).__name__, ttl)
1859
+ return self._make_request("POST", endpoint, json=data)
1860
+
1861
+ def get_kv(self, key: str) -> Dict[str, Any]:
1862
+ """
1863
+ Retrieve a key-value pair.
1864
+
1865
+ Args:
1866
+ key (str): The key to retrieve.
1867
+
1868
+ Returns:
1869
+ Dict[str, Any]: Response containing value, type, and optional ttl.
1870
+
1871
+ Example Response:
1872
+ {
1873
+ "value": {"positions": 30},
1874
+ "type": "object",
1875
+ "ttl": 85
1876
+ }
1877
+
1878
+ Raises:
1879
+ ValueError: If key is empty or invalid.
1880
+ requests.RequestException: If key not found (404) or API request fails.
1881
+
1882
+ Example:
1883
+ data = client.get_kv("portfolio_state")
1884
+ positions = data["value"]["positions"]
1885
+ """
1886
+ if not key or not key.strip():
1887
+ raise ValueError("Key cannot be empty or whitespace")
1888
+
1889
+ # Get strategy information
1890
+ strategy_info = self._get_strategy()
1891
+ endpoint = self._routes["kv.get"].format(strategy_id=strategy_info["id"], key=key)
1892
+
1893
+ logger.debug("Retrieving KV pair: key=%s", key)
1894
+ return self._make_request("GET", endpoint)
1895
+
1896
+ def update_kv(self, key: str, value: Any, ttl: Optional[int] = None) -> Dict[str, Any]:
1897
+ """
1898
+ Update (replace) an existing key-value pair completely.
1899
+
1900
+ Args:
1901
+ key (str): The key to update.
1902
+ value (Any): The new value to store (any JSON-serializable type).
1903
+ ttl (Optional[int]): New time to live in seconds.
1904
+
1905
+ Returns:
1906
+ Dict[str, Any]: Response containing key, value, type, ttl, createdAt, updatedAt.
1907
+
1908
+ Example Response:
1909
+ {
1910
+ "key": "state",
1911
+ "value": {"positions": 50, "orders": 10},
1912
+ "type": "object",
1913
+ "ttl": 200,
1914
+ "createdAt": "2023-10-01T10:00:00Z",
1915
+ "updatedAt": "2023-10-01T10:30:00Z"
1916
+ }
1917
+
1918
+ Raises:
1919
+ ValueError: If key is empty or invalid.
1920
+ requests.RequestException: If key not found (404) or API request fails.
1921
+
1922
+ Example:
1923
+ client.update_kv(key="portfolio_state", value={"positions": 50}, ttl=1800)
1924
+ """
1925
+ if not key or not key.strip():
1926
+ raise ValueError("Key cannot be empty or whitespace")
1927
+
1928
+ # Get strategy information
1929
+ strategy_info = self._get_strategy()
1930
+ endpoint = self._routes["kv.update"].format(strategy_id=strategy_info["id"], key=key)
1931
+
1932
+ # Build request payload
1933
+ data = {"value": value}
1934
+ if ttl is not None:
1935
+ data["ttl"] = ttl
1936
+
1937
+ logger.debug("Updating KV pair: key=%s, type=%s, ttl=%s", key, type(value).__name__, ttl)
1938
+ return self._make_request("PUT", endpoint, json=data)
1939
+
1940
+ def patch_kv(self, key: str, value: Optional[Any] = None, ttl: Optional[int] = None) -> Dict[str, Any]:
1941
+ """
1942
+ Partially update a key-value pair.
1943
+
1944
+ For OBJECT types: merges the new value with existing value.
1945
+ For other types (STRING, NUMBER, BOOLEAN, ARRAY): replaces the value entirely.
1946
+
1947
+ Args:
1948
+ key (str): The key to patch.
1949
+ value (Optional[Any]): New value or partial value to merge.
1950
+ ttl (Optional[int]): New time to live in seconds.
1951
+
1952
+ Note: At least one of value or ttl must be provided.
1953
+
1954
+ Returns:
1955
+ Dict[str, Any]: Response containing key, value, type, ttl, createdAt, updatedAt.
1956
+
1957
+ Example Response (for object merge):
1958
+ {
1959
+ "key": "state",
1960
+ "value": {"positions": 30, "orders": 5, "last_update": "2023-10-01"}, # Merged
1961
+ "type": "object",
1962
+ "ttl": 150,
1963
+ "createdAt": "2023-10-01T10:00:00Z",
1964
+ "updatedAt": "2023-10-01T10:15:00Z"
1965
+ }
1966
+
1967
+ Raises:
1968
+ ValueError: If key is empty, invalid, or no parameters provided.
1969
+ requests.RequestException: If key not found (404) or API request fails.
1970
+
1971
+ Example:
1972
+ # For objects - merges with existing
1973
+ client.patch_kv(key="portfolio_state", value={"last_update": "2023-10-01"})
1974
+
1975
+ # For non-objects - replaces entirely
1976
+ client.patch_kv(key="counter", value=42)
1977
+
1978
+ # Update only TTL
1979
+ client.patch_kv(key="session", ttl=1800)
1980
+ """
1981
+ if not key or not key.strip():
1982
+ raise ValueError("Key cannot be empty or whitespace")
1983
+
1984
+ if value is None and ttl is None:
1985
+ raise ValueError("At least one of value or ttl must be provided")
1986
+
1987
+ # Get strategy information
1988
+ strategy_info = self._get_strategy()
1989
+ endpoint = self._routes["kv.patch"].format(strategy_id=strategy_info["id"], key=key)
1990
+
1991
+ # Build request payload
1992
+ data = {}
1993
+ if value is not None:
1994
+ data["value"] = value
1995
+ if ttl is not None:
1996
+ data["ttl"] = ttl
1997
+
1998
+ logger.debug("Patching KV pair: key=%s, has_value=%s, has_ttl=%s", key, value is not None, ttl is not None)
1999
+ return self._make_request("PATCH", endpoint, json=data)
2000
+
2001
+ def delete_kv(self, key: str) -> Dict[str, Any]:
2002
+ """
2003
+ Delete a key-value pair.
2004
+
2005
+ Args:
2006
+ key (str): The key to delete.
2007
+
2008
+ Returns:
2009
+ Dict[str, Any]: Success confirmation.
2010
+
2011
+ Example Response:
2012
+ {
2013
+ "success": True,
2014
+ "message": "Key deleted successfully"
2015
+ }
2016
+
2017
+ Raises:
2018
+ ValueError: If key is empty or invalid.
2019
+ requests.RequestException: If key not found (404) or API request fails.
2020
+
2021
+ Example:
2022
+ response = client.delete_kv("old_config")
2023
+ if response["success"]:
2024
+ print("Key deleted successfully")
2025
+ """
2026
+ if not key or not key.strip():
2027
+ raise ValueError("Key cannot be empty or whitespace")
2028
+
2029
+ # Get strategy information
2030
+ strategy_info = self._get_strategy()
2031
+ endpoint = self._routes["kv.delete"].format(strategy_id=strategy_info["id"], key=key)
2032
+
2033
+ logger.debug("Deleting KV pair: key=%s", key)
2034
+ return self._make_request("DELETE", endpoint)
2035
+
2036
+ def get_all_kvs(self, page_no: int = 1, paginate: bool = False) -> List[Dict[str, Any]]:
2037
+ """
2038
+ Get all key-value pairs with their complete data.
2039
+
2040
+ Args:
2041
+ page_no (int): Page number for pagination (default: 1).
2042
+ paginate (bool): If True, automatically fetch all pages (default: False).
2043
+
2044
+ Returns:
2045
+ List[Dict[str, Any]]: List of all KV pairs with complete data.
2046
+
2047
+ Example Response:
2048
+ [
2049
+ {
2050
+ "key": "portfolio_state",
2051
+ "value": {"positions": 30},
2052
+ "type": "object",
2053
+ "ttl": 100,
2054
+ "createdAt": "2023-10-01T10:00:00Z",
2055
+ "updatedAt": "2023-10-01T10:00:00Z"
2056
+ },
2057
+ {
2058
+ "key": "environment",
2059
+ "value": "production",
2060
+ "type": "string",
2061
+ "createdAt": "2023-10-01T09:00:00Z",
2062
+ "updatedAt": "2023-10-01T09:00:00Z"
2063
+ },
2064
+ {
2065
+ "key": "trade_count",
2066
+ "value": 42,
2067
+ "type": "number",
2068
+ "createdAt": "2023-10-01T08:00:00Z",
2069
+ "updatedAt": "2023-10-01T08:30:00Z"
2070
+ }
2071
+ ]
2072
+
2073
+ Example:
2074
+ # Get first page only
2075
+ kvs = client.get_all_kvs()
2076
+
2077
+ # Get all pages automatically
2078
+ all_kvs = client.get_all_kvs(paginate=True)
2079
+ """
2080
+ # Get strategy information
2081
+ strategy_info = self._get_strategy()
2082
+ endpoint = self._routes["kv.list"].format(strategy_id=strategy_info["id"])
2083
+
2084
+ if paginate:
2085
+ return self._paginate_kv_requests(endpoint, "get_all_kvs")
2086
+ else:
2087
+ params = {"pageNo": page_no}
2088
+ logger.debug("Fetching all KVs page %d", page_no)
2089
+ return self._make_request("GET", endpoint, params=params)
2090
+
2091
+ def get_kv_keys(self, page_no: int = 1, paginate: bool = False) -> List[Dict[str, Any]]:
2092
+ """
2093
+ Get all keys with metadata (without values for memory efficiency).
2094
+
2095
+ Args:
2096
+ page_no (int): Page number for pagination (default: 1).
2097
+ paginate (bool): If True, automatically fetch all pages (default: False).
2098
+
2099
+ Returns:
2100
+ List[Dict[str, Any]]: List of keys with metadata (no values).
2101
+
2102
+ Example Response:
2103
+ [
2104
+ {
2105
+ "key": "portfolio_state",
2106
+ "type": "object",
2107
+ "ttl": 100,
2108
+ "createdAt": "2023-10-01T10:00:00Z",
2109
+ "updatedAt": "2023-10-01T10:00:00Z"
2110
+ },
2111
+ {
2112
+ "key": "environment",
2113
+ "type": "string",
2114
+ "createdAt": "2023-10-01T09:00:00Z",
2115
+ "updatedAt": "2023-10-01T09:00:00Z"
2116
+ },
2117
+ {
2118
+ "key": "trade_count",
2119
+ "type": "number",
2120
+ "createdAt": "2023-10-01T08:00:00Z",
2121
+ "updatedAt": "2023-10-01T08:30:00Z"
2122
+ }
2123
+ ]
2124
+
2125
+ Example:
2126
+ # Get keys for first page
2127
+ keys = client.get_kv_keys()
2128
+
2129
+ # Get all keys across all pages
2130
+ all_keys = client.get_kv_keys(paginate=True)
2131
+
2132
+ # Check what keys exist
2133
+ for key_info in all_keys:
2134
+ print(f"Key: {key_info['key']}, Type: {key_info['type']}")
2135
+ """
2136
+ # Get strategy information
2137
+ strategy_info = self._get_strategy()
2138
+ endpoint = self._routes["kv.keys"].format(strategy_id=strategy_info["id"])
2139
+
2140
+ if paginate:
2141
+ return self._paginate_kv_requests(endpoint, "get_kv_keys")
2142
+ else:
2143
+ params = {"pageNo": page_no}
2144
+ logger.debug("Fetching KV keys page %d", page_no)
2145
+ return self._make_request("GET", endpoint, params=params)
2146
+
2147
+ def _paginate_kv_requests(self, endpoint: str, operation: str) -> List[Dict[str, Any]]:
2148
+ """
2149
+ Internal method to handle pagination for KV list operations.
2150
+
2151
+ Args:
2152
+ endpoint (str): API endpoint.
2153
+ operation (str): Operation name for logging.
2154
+
2155
+ Returns:
2156
+ List[Dict[str, Any]]: Combined results from all pages.
2157
+ """
2158
+ all_items = []
2159
+ page_no = 1
2160
+ total_count = None
2161
+ page_size = 20 # KV API uses 20 items per page
2162
+
2163
+ while True:
2164
+ params = {"pageNo": page_no}
2165
+ logger.debug("Fetching %s page %d", operation, page_no)
2166
+
2167
+ try:
2168
+ url = f"{self.base_url}{endpoint}"
2169
+ response = requests.request(
2170
+ method="GET",
2171
+ url=url,
2172
+ headers=self.headers,
2173
+ params=params
2174
+ )
2175
+ response.raise_for_status()
2176
+
2177
+ # Get items from the current page
2178
+ items = response.json()
2179
+ all_items.extend(items)
2180
+
2181
+ # Check if we need to fetch more pages
2182
+ if total_count is None and "X-Total-Count" in response.headers:
2183
+ try:
2184
+ total_count = int(response.headers["X-Total-Count"])
2185
+ logger.debug("Total %s count: %d", operation, total_count)
2186
+ except (ValueError, TypeError):
2187
+ logger.warning("Could not parse X-Total-Count header for %s", operation)
2188
+ break
2189
+
2190
+ # If we've fetched all items or there are no more pages, stop
2191
+ if not items or len(all_items) >= total_count or total_count is None:
2192
+ break
2193
+
2194
+ # Move to the next page
2195
+ page_no += 1
2196
+
2197
+ except requests.RequestException as e:
2198
+ logger.error("API request failed during %s pagination: %s", operation, e, exc_info=True)
2199
+ if hasattr(e.response, 'text'):
2200
+ logger.error("Response content: %s", e.response.text)
2201
+ raise
2202
+
2203
+ logger.info("Fetched %d items in total for %s", len(all_items), operation)
2204
+ return all_items
2205
+
2206
+ # Add this method to the WizzerClient class
2207
+ def delete_all_kv(self) -> Dict[str, Any]:
2208
+ """
2209
+ Delete all key-value pairs for the current strategy.
2210
+
2211
+ This method removes all KV pairs associated with the client's strategy.
2212
+ Use with caution as this operation cannot be undone.
2213
+
2214
+ Returns:
2215
+ Dict[str, Any]: Response containing success status, count of deleted items, and message.
2216
+
2217
+ Example Response:
2218
+ {
2219
+ "success": True,
2220
+ "deleted": 15,
2221
+ "message": "Successfully deleted 15 key-value pairs"
2222
+ }
2223
+
2224
+ Raises:
2225
+ requests.RequestException: If API request fails.
2226
+
2227
+ Example:
2228
+ response = client.delete_all_kv()
2229
+ print(f"Deleted {response['deleted']} key-value pairs")
2230
+ """
2231
+ # Get strategy information
2232
+ strategy_info = self._get_strategy()
2233
+ endpoint = self._routes["kv.delete_all"].format(strategy_id=strategy_info["id"])
2234
+
2235
+ logger.debug("Deleting all KV pairs for strategy: %s", strategy_info["id"])
2236
+ return self._make_request("DELETE", endpoint)
@@ -28,15 +28,38 @@ class QuotesClient:
28
28
  log_level (str): Logging level. Options: "error", "info", "debug".
29
29
  on_tick (Callable): Callback for tick messages (type='ticks' or no type field).
30
30
  on_stats (Callable): Callback for stats/greeks messages (type='greeks').
31
+ on_order (Callable): Callback for order events (type='order').
32
+ on_trade (Callable): Callback for trade events (type='trade').
33
+ on_position (Callable): Callback for position events (type='position').
34
+ on_holding (Callable): Callback for holding events (type='holding').
31
35
  on_connect (Callable): Callback when connection is established.
32
36
  on_close (Callable): Callback when connection is closed.
33
37
  on_error (Callable): Callback for errors.
34
38
 
35
39
  Message Routing:
36
- - Messages with type='ticks' are routed to on_tick callback
37
- - Messages with type='greeks' are routed to on_stats callback
40
+ - Legacy messages with type='ticks' are routed to on_tick callback
41
+ - Legacy messages with type='greeks' are routed to on_stats callback
42
+ - Event messages with type='order' are routed to on_order callback
43
+ - Event messages with type='trade' are routed to on_trade callback
44
+ - Event messages with type='position' are routed to on_position callback
45
+ - Event messages with type='holding' are routed to on_holding callback
38
46
  - Messages without type field are routed to on_tick for backward compatibility
39
47
  - Messages are silently dropped if the appropriate handler is not registered
48
+
49
+ Message Structure Support:
50
+ - Legacy Format: {type: "ticks", ...data} or {...data} (no type)
51
+ - New Event Format: {type: "position", data: {...actualData}} - automatically normalized
52
+ - All handlers receive flattened message structure with type preserved at root level
53
+
54
+ Event Handler Signature:
55
+ All event handlers follow the same signature: handler(ws: QuotesClient, event_data: dict)
56
+
57
+ Example:
58
+ def on_order_event(ws, order):
59
+ print(f"Order {order['id']}: {order['status']}")
60
+
61
+ client.on_order = on_order_event
62
+ client.connect()
40
63
  """
41
64
 
42
65
  ACTION_SUBSCRIBE = "subscribe"
@@ -89,6 +112,12 @@ class QuotesClient:
89
112
  self.on_connect: Optional[Callable[[Any], None]] = None
90
113
  self.on_close: Optional[Callable[[Any, Optional[int], Optional[str]], None]] = None
91
114
  self.on_error: Optional[Callable[[Any, Exception], None]] = None
115
+
116
+ # Event callbacks for account events
117
+ self.on_order: Optional[Callable[[Any, dict], None]] = None
118
+ self.on_trade: Optional[Callable[[Any, dict], None]] = None
119
+ self.on_position: Optional[Callable[[Any, dict], None]] = None
120
+ self.on_holding: Optional[Callable[[Any, dict], None]] = None
92
121
 
93
122
  logger.debug("Initialized QuotesClient with URL: %s", self.url)
94
123
 
@@ -96,6 +125,40 @@ class QuotesClient:
96
125
  for i in range(0, len(data), chunk_size):
97
126
  yield data[i:i + chunk_size]
98
127
 
128
+ def _normalize_message(self, raw_message: dict) -> dict:
129
+ """
130
+ Normalize message structure to handle both legacy and new formats.
131
+
132
+ Legacy Format (Ticks/Greeks): {type: "ticks", ...data} or {...data} (no type)
133
+ New Format (Events): {type: "position", data: {...actualData}}
134
+
135
+ Returns: Flattened message with type preserved at root level
136
+ """
137
+ try:
138
+ # Check if message has both 'type' and 'data' fields (new format)
139
+ if 'type' in raw_message and 'data' in raw_message:
140
+ # New format: Extract type, merge data into root level
141
+ message_type = raw_message['type']
142
+ data = raw_message['data']
143
+ if isinstance(data, dict):
144
+ # Create flattened message with type preserved
145
+ normalized = {'type': message_type}
146
+ normalized.update(data)
147
+ logger.debug("Normalized new format message: type=%s", message_type)
148
+ return normalized
149
+ else:
150
+ # Data is not a dict, keep original structure
151
+ logger.debug("Data field is not a dict, keeping original structure")
152
+ return raw_message
153
+ else:
154
+ # Legacy format: Use as-is (either has type only or no type)
155
+ logger.debug("Legacy format message detected")
156
+ return raw_message
157
+
158
+ except Exception as e:
159
+ logger.debug("Error normalizing message: %s, using original", e)
160
+ return raw_message
161
+
99
162
  async def _connect_with_backoff(self) -> None:
100
163
  backoff = self._backoff_base
101
164
 
@@ -174,24 +237,67 @@ class QuotesClient:
174
237
  if not chunk:
175
238
  continue
176
239
  try:
177
- data = json.loads(chunk)
240
+ raw_data = json.loads(chunk)
241
+ # Normalize message structure
242
+ data = self._normalize_message(raw_data)
178
243
  # Route based on message type
179
244
  message_type = data.get('type')
180
245
 
181
246
  if message_type == 'greeks':
182
247
  if self.on_stats:
183
- self.on_stats(self, data)
248
+ try:
249
+ self.on_stats(self, data)
250
+ except Exception as e:
251
+ logger.debug("Error in on_stats handler: %s", e)
184
252
  else:
185
253
  logger.debug("Received greeks message but no on_stats handler registered")
186
254
  elif message_type == 'ticks':
187
255
  if self.on_tick:
188
- self.on_tick(self, data)
256
+ try:
257
+ self.on_tick(self, data)
258
+ except Exception as e:
259
+ logger.debug("Error in on_tick handler: %s", e)
189
260
  else:
190
261
  logger.debug("Received ticks message but no on_tick handler registered")
262
+ elif message_type == 'order':
263
+ if self.on_order:
264
+ try:
265
+ self.on_order(self, data)
266
+ except Exception as e:
267
+ logger.debug("Error in on_order handler: %s", e)
268
+ else:
269
+ logger.debug("Received order event but no on_order handler registered")
270
+ elif message_type == 'trade':
271
+ if self.on_trade:
272
+ try:
273
+ self.on_trade(self, data)
274
+ except Exception as e:
275
+ logger.debug("Error in on_trade handler: %s", e)
276
+ else:
277
+ logger.debug("Received trade event but no on_trade handler registered")
278
+ elif message_type == 'position':
279
+ if self.on_position:
280
+ try:
281
+ self.on_position(self, data)
282
+ except Exception as e:
283
+ logger.debug("Error in on_position handler: %s", e)
284
+ else:
285
+ logger.debug("Received position event but no on_position handler registered")
286
+ elif message_type == 'holding':
287
+ if self.on_holding:
288
+ try:
289
+ self.on_holding(self, data)
290
+ except Exception as e:
291
+ logger.debug("Error in on_holding handler: %s", e)
292
+ else:
293
+ logger.debug("Received holding event but no on_holding handler registered")
191
294
  else:
192
295
  # No type field - send to on_tick for backward compatibility
193
296
  if self.on_tick:
194
- self.on_tick(self, data)
297
+ try:
298
+ self.on_tick(self, data)
299
+ except Exception as e:
300
+ logger.debug("Error in on_tick handler: %s", e)
195
301
  else:
196
302
  logger.debug("Received message without type field and no on_tick handler registered")
197
303
  except json.JSONDecodeError as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wiz_trader
3
- Version: 0.34.0
3
+ Version: 0.36.0
4
4
  Summary: A Python SDK for connecting to the Wizzer.
5
5
  Home-page: https://bitbucket.org/wizzer-tech/quotes_sdk.git
6
6
  Author: Pawan Wagh
@@ -38,6 +38,7 @@ Dynamic: requires-python
38
38
  - [Callbacks](#callbacks)
39
39
  - [Subscribing to Instruments](#subscribing-to-instruments)
40
40
  - [Unsubscribing from Instruments](#unsubscribing-from-instruments)
41
+ - [Account Events](#account-events)
41
42
  - [Handling WebSocket Connection](#handling-websocket-connection)
42
43
  - [Complete Examples](#quotes-client-examples)
43
44
  5. [Wizzer Client](#wizzer-client)
@@ -253,6 +254,347 @@ ws.unsubscribe(["NSE:RELIANCE:2885"])
253
254
  - Can be called multiple times for different instrument sets
254
255
  - No callback fired for unsubscribed instruments
255
256
 
257
+ ### Account Events
258
+
259
+ The QuotesClient now supports automatic reception of account-related events (orders, trades, positions, holdings) from the quotes server. These events are automatically pushed to SDK users via dedicated callback handlers based on the event type.
260
+
261
+ #### Event Handlers
262
+
263
+ Register event handlers to receive real-time updates for your account activity:
264
+
265
+ ```python
266
+ def on_order(ws, order):
267
+ print(f"Order event: {order}")
268
+
269
+ def on_trade(ws, trade):
270
+ print(f"Trade event: {trade}")
271
+
272
+ def on_position(ws, position):
273
+ print(f"Position event: {position}")
274
+
275
+ def on_holding(ws, holding):
276
+ print(f"Holding event: {holding}")
277
+
278
+ # Register event handlers
279
+ client.on_order = on_order
280
+ client.on_trade = on_trade
281
+ client.on_position = on_position
282
+ client.on_holding = on_holding
283
+ ```
284
+
285
+ #### Key Features
286
+
287
+ - **Automatic Event Reception**: No explicit subscription needed - events are automatically sent based on your JWT token's account ID and strategy ID
288
+ - **Optional Handlers**: Events are silently dropped if no handler is registered - you only receive what you need
289
+ - **Message Normalization**: The SDK automatically handles different message formats and delivers clean, flattened event data to your handlers
290
+ - **Error Resilience**: Handler exceptions are caught and logged without breaking the event flow
291
+ - **Backward Compatible**: Existing tick and Greeks functionality remains unchanged
292
+
293
+ #### Event Data Structure
294
+
295
+ All event handlers receive flattened event data with the event type preserved:
296
+
297
+ ```python
298
+ # Example order event data
299
+ {
300
+ "_id": "order_01k10np7weemkv812nx55bppw5",
301
+ "accountEntityType": "client",
302
+ "accountId": "user_01j9brznsffphvzqn9gzyj1frw",
303
+ "avgPrice": 72.65,
304
+ "broker": "wizzer",
305
+ "brokerOrderId": "dssorder_175344314711184",
306
+ "brokerTimestamp": "2025-07-25T11:32:28.147Z",
307
+ "cancelledQty": 0,
308
+ "completedAt": "2025-07-25T11:32:28.147Z",
309
+ "createdAt": "2025-07-25T11:32:27.722Z",
310
+ "disclosedQty": 0,
311
+ "exchange": "NSE",
312
+ "exchangeOrderId": "dssx_1753443147",
313
+ "exchangeToken": 11184,
314
+ "exchangeUpdateTimestamp": "2025-07-25T11:32:28.147Z",
315
+ "executionType": "regular",
316
+ "filledQty": 1,
317
+ "guid": "t0lVueivgBgcEh2k_BP",
318
+ "instrumentType": "EQLC",
319
+ "isDealer": false,
320
+ "isOrderModified": false,
321
+ "legs": [],
322
+ "marginUsed": 0,
323
+ "meta": {
324
+ "dss": {"orderId": "order_01k10np7weemkv812nx55bppw5"},
325
+ "source": "wizzer.dss",
326
+ "wizzer": {"mode": "paper_trading"}
327
+ },
328
+ "orderType": "MARKET",
329
+ "parentOrderId": "",
330
+ "pendingQty": 1,
331
+ "placedBy": "user_01j9brznsffphvzqn9gzyj1frw",
332
+ "price": 0,
333
+ "product": "CNC",
334
+ "qty": 1,
335
+ "remarks": "",
336
+ "segment": "NSECM",
337
+ "slPrice": 0,
338
+ "slTriggerPrice": 0,
339
+ "sqOffHoldingId": "",
340
+ "sqOffPositionId": "",
341
+ "status": "COMPLETED",
342
+ "stoploss": 0,
343
+ "stoplossOrderId": "",
344
+ "strategy": {
345
+ "id": "stgy_01jtn7t3erf8ss78spqy40k8en",
346
+ "identifier": "S-608377",
347
+ "name": "Test 1122"
348
+ },
349
+ "target": 0,
350
+ "targetOrderId": "",
351
+ "tradingSymbol": "IDFCFIRSTB",
352
+ "transactionType": "BUY",
353
+ "triggerId": "",
354
+ "triggerPrice": 0,
355
+ "updatedAt": "2025-07-25T11:32:28.254Z",
356
+ "userId": "user_01j9brznsffphvzqn9gzyj1frw",
357
+ "validity": "DAY",
358
+ "variety": "REGULAR"
359
+ }
360
+
361
+ # Example trade event data
362
+ {
363
+ "_id": "trade_01k10np8rqef793zr93ctqj0wt",
364
+ "accountId": "user_01j9brznsffphvzqn9gzyj1frw",
365
+ "avgPrice": 72.65,
366
+ "broker": "wizzer",
367
+ "brokerOrderId": "dssorder_175344314711184",
368
+ "createdAt": "2025-07-25T11:32:28.567Z",
369
+ "exchange": "NSE",
370
+ "exchangeOrderId": "dssx_1753443147",
371
+ "exchangeTimestamp": "2025-07-25T11:32:28.147Z",
372
+ "exchangeToken": 11184,
373
+ "isDividend": false,
374
+ "meta": {
375
+ "dss": {"orderId": "order_01k10np7weemkv812nx55bppw5"},
376
+ "source": "wizzer.dss",
377
+ "wizzer": {"mode": "paper_trading"}
378
+ },
379
+ "orderId": "order_01k10np7weemkv812nx55bppw5",
380
+ "orderType": "MARKET",
381
+ "parentOrderId": "",
382
+ "parentTradeId": "",
383
+ "product": "CNC",
384
+ "qty": 1,
385
+ "segment": "NSECM",
386
+ "strategy": {
387
+ "id": "stgy_01jtn7t3erf8ss78spqy40k8en",
388
+ "identifier": "S-608377",
389
+ "name": "Test 1122"
390
+ },
391
+ "tradeId": "dsstrade_50PccSfWvsCg-Cr5dQE",
392
+ "tradingSymbol": "IDFCFIRSTB",
393
+ "transactionType": "BUY",
394
+ "updatedAt": "2025-07-25T11:32:28.567Z",
395
+ "userId": "user_01j9brznsffphvzqn9gzyj1frw"
396
+ }
397
+
398
+ # Example position event data
399
+ {
400
+ "_id": "pos_01k10np8t7e0fs63q52hacbx8m",
401
+ "accountEntityType": "client",
402
+ "accountId": "user_01j9brznsffphvzqn9gzyj1frw",
403
+ "bep": 0,
404
+ "broker": "wizzer",
405
+ "buyAvgPrice": 72.65,
406
+ "buyOrders": ["order_01k10np7weemkv812nx55bppw5"],
407
+ "buyQty": 1,
408
+ "buyTrades": ["trade_01k10np8rqef793zr93ctqj0wt"],
409
+ "buyValue": 72.65,
410
+ "closePrice": 0,
411
+ "createdAt": "2025-07-25T11:32:28.614Z",
412
+ "exchange": "NSE",
413
+ "exchangeToken": 11184,
414
+ "instrumentType": "EQLC",
415
+ "lotSize": 1,
416
+ "meta": {
417
+ "source": "wizzer.dss",
418
+ "wizzer": {"mode": "paper_trading"}
419
+ },
420
+ "netQty": 1,
421
+ "pnlPercentage": 0,
422
+ "product": "CNC",
423
+ "realisedPnlToday": 0,
424
+ "segment": "NSECM",
425
+ "sellAvgPrice": 0,
426
+ "sellOrders": [],
427
+ "sellQty": 0,
428
+ "sellTrades": [],
429
+ "sellValue": 0,
430
+ "slPrice": null,
431
+ "slTriggerPrice": null,
432
+ "status": "open",
433
+ "stoploss": 0,
434
+ "strategy": {
435
+ "id": "stgy_01jtn7t3erf8ss78spqy40k8en",
436
+ "identifier": "S-608377",
437
+ "name": "Test 1122"
438
+ },
439
+ "target": 0,
440
+ "tradingSymbol": "IDFCFIRSTB",
441
+ "unrealisedPnlToday": 0,
442
+ "updatedAt": "2025-07-25T11:32:28.614Z",
443
+ "userId": "user_01j9brznsffphvzqn9gzyj1frw"
444
+ }
445
+
446
+ # Example holding event data
447
+ {
448
+ "_id": "holding_01k10np8t7e0fs63q52hacbx8m",
449
+ "accountEntityType": "client",
450
+ "accountId": "user_01j9brznsffphvzqn9gzyj1frw",
451
+ "bep": 0,
452
+ "broker": "wizzer",
453
+ "buyAvgPrice": 72.65,
454
+ "buyOrders": ["order_01k10np7weemkv812nx55bppw5"],
455
+ "buyQty": 1,
456
+ "buyTrades": ["trade_01k10np8rqef793zr93ctqj0wt"],
457
+ "buyValue": 72.65,
458
+ "closePrice": 0,
459
+ "createdAt": "2025-07-25T11:32:28.614Z",
460
+ "exchange": "NSE",
461
+ "exchangeToken": 11184,
462
+ "instrumentType": "EQLC",
463
+ "lotSize": 1,
464
+ "meta": {
465
+ "source": "wizzer.dss",
466
+ "wizzer": {"mode": "paper_trading"}
467
+ },
468
+ "netQty": 1,
469
+ "pnlPercentage": 0,
470
+ "product": "CNC",
471
+ "realisedPnlToday": 0,
472
+ "segment": "NSECM",
473
+ "sellAvgPrice": 0,
474
+ "sellOrders": [],
475
+ "sellQty": 0,
476
+ "sellTrades": [],
477
+ "sellValue": 0,
478
+ "slPrice": null,
479
+ "slTriggerPrice": null,
480
+ "status": "open",
481
+ "stoploss": 0,
482
+ "strategy": {
483
+ "id": "stgy_01jtn7t3erf8ss78spqy40k8en",
484
+ "identifier": "S-608377",
485
+ "name": "Test 1122"
486
+ },
487
+ "target": 0,
488
+ "tradingSymbol": "IDFCFIRSTB",
489
+ "unrealisedPnlToday": 0,
490
+ "updatedAt": "2025-07-25T11:32:28.614Z",
491
+ "userId": "user_01j9brznsffphvzqn9gzyj1frw"
492
+ }
493
+ ```
494
+
495
+ #### Complete Event Handling Example
496
+
497
+ ```python
498
+ from wiz_trader import QuotesClient
499
+
500
+ # Initialize client
501
+ client = QuotesClient(
502
+ base_url="wss://websocket-url/quotes",
503
+ token="your-jwt-token",
504
+ log_level="info"
505
+ )
506
+
507
+ def on_order(ws, order):
508
+ # React to order status changes
509
+ if order['status'] == ORDER_STATUS_COMPLETED:
510
+ print(f"Order {order['_id']} completed!")
511
+ elif order['status'] == ORDER_STATUS_REJECTED:
512
+ print(f"Order {order['_id']} rejected: {order.get('remarks', 'Unknown')}")
513
+
514
+ def on_trade(ws, trade):
515
+ print(f"Trade executed: {trade['qty']} x {trade['tradingSymbol']} @ {trade['avgPrice']}")
516
+
517
+ def on_position(ws, position):
518
+ print(f"Position update: {position['tradingSymbol']} = {position['netQty']} (PnL: {position.get('unrealisedPnlToday', 0)})")
519
+
520
+ def on_holding(ws, holding):
521
+ print(f"Holding update: {holding['tradingSymbol']} = {holding['netQty']}")
522
+
523
+ def on_connect(ws):
524
+ print("Connected! Ready to receive events.")
525
+ # Also subscribe to some ticks if needed
526
+ ws.subscribe(["NSE:SBIN:3045"], mode="ticks")
527
+
528
+ # Register all handlers
529
+ client.on_order = on_order
530
+ client.on_trade = on_trade
531
+ client.on_position = on_position
532
+ client.on_holding = on_holding
533
+ client.on_connect = on_connect
534
+
535
+ # Connect and receive events
536
+ client.connect()
537
+ ```
538
+
539
+ #### Event-Driven Trading Strategy Example
540
+
541
+ ```python
542
+ class EventDrivenStrategy:
543
+ def __init__(self):
544
+ self.orders = {}
545
+ self.positions = {}
546
+ self.pending_orders = set()
547
+
548
+ def on_order_event(self, ws, order):
549
+ order_id = order['_id']
550
+ self.orders[order_id] = order
551
+
552
+ # Track pending orders
553
+ if order['status'] == ORDER_STATUS_OPEN:
554
+ self.pending_orders.add(order_id)
555
+ elif order_id in self.pending_orders:
556
+ self.pending_orders.remove(order_id)
557
+
558
+ # Log order lifecycle
559
+ print(f"Order {order_id}: {order['status']}")
560
+
561
+ # React to rejections
562
+ if order['status'] == ORDER_STATUS_REJECTED:
563
+ print(f"Order rejected! Reason: {order.get('remarks', 'Unknown')}")
564
+ # Implement retry logic or alternative strategy
565
+
566
+ def on_position_event(self, ws, position):
567
+ symbol = position['tradingSymbol']
568
+ self.positions[symbol] = position
569
+
570
+ # Monitor position changes
571
+ print(f"Position {symbol}: Qty={position['netQty']}, PnL={position.get('unrealisedPnlToday', 0)}")
572
+
573
+ # Implement stop-loss or take-profit logic
574
+ pnl = position.get('unrealisedPnlToday', 0)
575
+ if pnl < -100: # Stop loss at -100
576
+ print(f"Stop loss triggered for {symbol}")
577
+ # Place exit order
578
+ elif pnl > 200: # Take profit at 200
579
+ print(f"Take profit triggered for {symbol}")
580
+ # Place exit order
581
+
582
+ def on_trade_event(self, ws, trade):
583
+ print(f"Trade executed: {trade['qty']} @ {trade['avgPrice']}")
584
+ # Update internal trade log, calculate VWAP, etc.
585
+
586
+ # Initialize strategy
587
+ strategy = EventDrivenStrategy()
588
+
589
+ # Set up client with strategy handlers
590
+ client = QuotesClient(base_url="wss://...", token="...")
591
+ client.on_order = strategy.on_order_event
592
+ client.on_position = strategy.on_position_event
593
+ client.on_trade = strategy.on_trade_event
594
+
595
+ client.connect()
596
+ ```
597
+
256
598
  ### Complete Examples
257
599
 
258
600
  #### Blocking Example
@@ -425,6 +767,7 @@ Available constants:
425
767
  - Transaction types: `TRANSACTION_TYPE_BUY`, `TRANSACTION_TYPE_SELL`
426
768
  - Product types: `PRODUCT_CNC`, `PRODUCT_MIS`, `PRODUCT_NRML`
427
769
  - Order types: `ORDER_TYPE_MARKET`, `ORDER_TYPE_LIMIT`, `ORDER_TYPE_SL`, `ORDER_TYPE_SLM`
770
+ - Order status: `ORDER_STATUS_OPEN`, `ORDER_STATUS_CANCELLED`, `ORDER_STATUS_REJECTED`, `ORDER_STATUS_PENDING`, `ORDER_STATUS_COMPLETED`
428
771
  - Validity types: `VALIDITY_DAY`, `VALIDITY_IOC`, `VALIDITY_GTT`
429
772
  - Variety types: `VARIETY_REGULAR`, `VARIETY_AMO`, `VARIETY_BO`, `VARIETY_CO`
430
773
  - Exchanges: `EXCHANGE_NSE`, `EXCHANGE_BSE`, `EXCHANGE_WZR`
@@ -3663,3 +4006,243 @@ results = client.run_screener(
3663
4006
  limit=10
3664
4007
  )
3665
4008
  ```
4009
+
4010
+ ## KV Store Integration
4011
+
4012
+ The WizzerClient includes comprehensive Key-Value (KV) store functionality for persistent data management. This feature allows you to store configuration data, state information, user preferences, and other persistent data associated with your trading strategies.
4013
+
4014
+ ### Supported Data Types
4015
+
4016
+ The KV store supports all JSON-serializable data types:
4017
+
4018
+ - **STRING**: Text values (`"production"`, `"user123"`)
4019
+ - **NUMBER**: Integer and float values (`42`, `150.25`)
4020
+ - **BOOLEAN**: True/false values (`True`, `False`)
4021
+ - **ARRAY**: Lists of values (`["AAPL", "GOOGL", "MSFT"]`)
4022
+ - **OBJECT**: Dictionaries/objects (`{"positions": 30, "cash": 50000}`)
4023
+
4024
+ ### Available Constants
4025
+
4026
+ ```python
4027
+ client.KV_TYPE_STRING # "string"
4028
+ client.KV_TYPE_NUMBER # "number"
4029
+ client.KV_TYPE_BOOLEAN # "boolean"
4030
+ client.KV_TYPE_ARRAY # "array"
4031
+ client.KV_TYPE_OBJECT # "object"
4032
+ ```
4033
+
4034
+ ### Core CRUD Operations
4035
+
4036
+ #### Create Key-Value Pair
4037
+
4038
+ ```python
4039
+ # Store different data types
4040
+ client.create_kv("environment", "production")
4041
+ client.create_kv("trade_count", 42)
4042
+ client.create_kv("is_active", True)
4043
+ client.create_kv("symbols", ["AAPL", "GOOGL", "MSFT"])
4044
+
4045
+ # Store complex configuration with TTL (expires in 1 hour)
4046
+ config = {
4047
+ "risk_level": "medium",
4048
+ "max_positions": 10,
4049
+ "stop_loss_pct": 0.05,
4050
+ "take_profit_pct": 0.15
4051
+ }
4052
+ client.create_kv("strategy_config", config, ttl=3600)
4053
+ ```
4054
+
4055
+ #### Retrieve Key-Value Pair
4056
+
4057
+ ```python
4058
+ # Get a single KV pair
4059
+ data = client.get_kv("strategy_config")
4060
+ print(data["value"]) # The stored object
4061
+ print(data["type"]) # "object"
4062
+ print(data["ttl"]) # Remaining time to live (if set)
4063
+
4064
+ # Access nested values
4065
+ risk_level = data["value"]["risk_level"] # "medium"
4066
+ ```
4067
+
4068
+ #### Update Key-Value Pair (Complete Replacement)
4069
+
4070
+ ```python
4071
+ # Complete replacement of existing key
4072
+ new_config = {"risk_level": "high", "max_positions": 5}
4073
+ client.update_kv("strategy_config", new_config, ttl=1800)
4074
+ ```
4075
+
4076
+ #### Patch Key-Value Pair (Partial Update)
4077
+
4078
+ ```python
4079
+ # For objects - merges with existing data
4080
+ client.patch_kv("strategy_config", {"last_updated": "2024-01-15"})
4081
+
4082
+ # For non-objects - replaces entirely
4083
+ client.patch_kv("trade_count", 50)
4084
+
4085
+ # Update only TTL
4086
+ client.patch_kv("strategy_config", ttl=7200)
4087
+ ```
4088
+
4089
+ #### Delete Key-Value Pair
4090
+
4091
+ ```python
4092
+ response = client.delete_kv("old_config")
4093
+ if response["success"]:
4094
+ print("Key deleted successfully")
4095
+ ```
4096
+
4097
+ ### List Operations
4098
+
4099
+ #### Get All KV Pairs (with values)
4100
+
4101
+ ```python
4102
+ # Get first page (20 items)
4103
+ kvs = client.get_all_kvs(page_no=1)
4104
+
4105
+ # Get all pages automatically
4106
+ all_kvs = client.get_all_kvs(paginate=True)
4107
+
4108
+ for kv in all_kvs:
4109
+ print(f"Key: {kv['key']}")
4110
+ print(f"Type: {kv['type']}")
4111
+ print(f"Value: {kv['value']}")
4112
+ ```
4113
+
4114
+ #### Get Keys Only (memory efficient)
4115
+
4116
+ ```python
4117
+ # Get all keys without values (faster for large datasets)
4118
+ keys = client.get_kv_keys(paginate=True)
4119
+
4120
+ for key_info in keys:
4121
+ print(f"Key: {key_info['key']}, Type: {key_info['type']}")
4122
+ if 'ttl' in key_info:
4123
+ print(f" TTL: {key_info['ttl']} seconds remaining")
4124
+ ```
4125
+
4126
+ ### Advanced Usage Examples
4127
+
4128
+ #### Strategy Configuration Management
4129
+
4130
+ ```python
4131
+ # Store strategy parameters
4132
+ params = {
4133
+ "moving_average_period": 20,
4134
+ "rsi_oversold": 30,
4135
+ "rsi_overbought": 70,
4136
+ "position_size": 0.02
4137
+ }
4138
+ client.create_kv("strategy_params", params)
4139
+
4140
+ # Update specific parameters using patch (merges with existing)
4141
+ client.patch_kv("strategy_params", {"moving_average_period": 50})
4142
+ ```
4143
+
4144
+ #### State Persistence
4145
+
4146
+ ```python
4147
+ # Save current portfolio state
4148
+ portfolio_state = {
4149
+ "cash_balance": 50000,
4150
+ "open_positions": 5,
4151
+ "daily_pnl": 1250.75,
4152
+ "last_updated": "2024-01-15T15:30:00Z"
4153
+ }
4154
+ client.create_kv("portfolio_state", portfolio_state, ttl=3600)
4155
+
4156
+ # Update state periodically
4157
+ client.patch_kv("portfolio_state", {
4158
+ "daily_pnl": 1500.25,
4159
+ "last_updated": "2024-01-15T16:00:00Z"
4160
+ })
4161
+ ```
4162
+
4163
+ #### Feature Flags
4164
+
4165
+ ```python
4166
+ # Enable/disable features dynamically
4167
+ features = {
4168
+ "advanced_charts": True,
4169
+ "paper_trading": True,
4170
+ "options_trading": False,
4171
+ "algo_trading": True
4172
+ }
4173
+ client.create_kv("feature_flags", features)
4174
+
4175
+ # Toggle a feature
4176
+ client.patch_kv("feature_flags", {"options_trading": True})
4177
+ ```
4178
+
4179
+ #### Data Caching with TTL
4180
+
4181
+ ```python
4182
+ # Cache market data with short TTL (5 minutes)
4183
+ market_status = {
4184
+ "nse_open": True,
4185
+ "bse_open": True,
4186
+ "last_checked": "2024-01-15T09:15:00Z"
4187
+ }
4188
+ client.create_kv("market_status", market_status, ttl=300)
4189
+ ```
4190
+
4191
+ ### Object Merge Behavior
4192
+
4193
+ For OBJECT type data, the `patch_kv` method performs intelligent merging:
4194
+
4195
+ ```python
4196
+ # Initial object
4197
+ client.create_kv("user_prefs", {
4198
+ "theme": "dark",
4199
+ "notifications": True,
4200
+ "language": "en"
4201
+ })
4202
+
4203
+ # Patch merges new fields with existing ones
4204
+ client.patch_kv("user_prefs", {
4205
+ "notifications": False, # Updates existing field
4206
+ "timezone": "UTC" # Adds new field
4207
+ })
4208
+
4209
+ # Result: {"theme": "dark", "notifications": False, "language": "en", "timezone": "UTC"}
4210
+ ```
4211
+
4212
+ ### Delete All KV Pairs
4213
+
4214
+ The `delete_all_kv` method allows you to remove all key-value pairs for a strategy at once:
4215
+
4216
+ ```python
4217
+ # Delete all KV pairs for the current strategy
4218
+ response = client.delete_all_kv()
4219
+ print(f"Deleted {response['deleted']} key-value pairs")
4220
+
4221
+ # Example response:
4222
+ # {
4223
+ # "success": True,
4224
+ # "deleted": 15,
4225
+ # "message": "Successfully deleted 15 key-value pairs"
4226
+ # }
4227
+
4228
+ # Common usage - reset strategy state before reinitialization
4229
+ def reset_strategy_state():
4230
+ """Reset all persistent state for the strategy."""
4231
+ # Clear all existing KV pairs
4232
+ result = client.delete_all_kv()
4233
+ print(f"Cleared {result['deleted']} KV pairs")
4234
+
4235
+ # Reinitialize with default configuration
4236
+ client.create_kv("config", {
4237
+ "mode": "production",
4238
+ "risk_percentage": 0.02,
4239
+ "max_positions": 10
4240
+ })
4241
+ client.create_kv("state", {
4242
+ "initialized": True,
4243
+ "start_time": datetime.now().isoformat()
4244
+ })
4245
+ print("Strategy state reset successfully")
4246
+
4247
+ # Use with caution - this operation cannot be undone!
4248
+ ```
@@ -0,0 +1,9 @@
1
+ wiz_trader/__init__.py,sha256=hOpsoebZsI_XebFP-JQ-CenvIShBGtVDsJhl72ARJxU,183
2
+ wiz_trader/apis/__init__.py,sha256=6sUr1nzmplNdld0zryMrQSt0jHT2GhOiFYgKKVHzk8U,133
3
+ wiz_trader/apis/client.py,sha256=znwlyDIHbaiQuDqxp7pn1NPiiYB99oA74JWt4AiLkBg,80088
4
+ wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
5
+ wiz_trader/quotes/client.py,sha256=cAGaysLCljZilYgX8Sf3V88F6dWlcewJf6TMOpSKb7I,20862
6
+ wiz_trader-0.36.0.dist-info/METADATA,sha256=AYUF296FcogkDFjLk5WDhVeAaGvH1PhO8Gyl1g4aH3Q,155156
7
+ wiz_trader-0.36.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ wiz_trader-0.36.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
9
+ wiz_trader-0.36.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- wiz_trader/__init__.py,sha256=Bd8yqFEqK9MNrzHUVgrbEntlyIHZhArgA6AZT9SEFjY,183
2
- wiz_trader/apis/__init__.py,sha256=6sUr1nzmplNdld0zryMrQSt0jHT2GhOiFYgKKVHzk8U,133
3
- wiz_trader/apis/client.py,sha256=WzsKxrNi9T-xbtXWBT2J41bJ-sz0KsGFYV5Oyddo3gg,65487
4
- wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
5
- wiz_trader/quotes/client.py,sha256=aZ5LVlrj0mKfgHgFxERmk2HDZraB6RMaormTOMlqWZc,14915
6
- wiz_trader-0.34.0.dist-info/METADATA,sha256=OAi5g0xbSdp9bc-F1Fgl7d8oMRIsfimTohtJ0oA-FX0,138653
7
- wiz_trader-0.34.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- wiz_trader-0.34.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
9
- wiz_trader-0.34.0.dist-info/RECORD,,