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 +1 -1
- wiz_trader/apis/client.py +441 -1
- wiz_trader/quotes/client.py +112 -6
- {wiz_trader-0.34.0.dist-info → wiz_trader-0.36.0.dist-info}/METADATA +584 -1
- wiz_trader-0.36.0.dist-info/RECORD +9 -0
- wiz_trader-0.34.0.dist-info/RECORD +0 -9
- {wiz_trader-0.34.0.dist-info → wiz_trader-0.36.0.dist-info}/WHEEL +0 -0
- {wiz_trader-0.34.0.dist-info → wiz_trader-0.36.0.dist-info}/top_level.txt +0 -0
wiz_trader/__init__.py
CHANGED
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)
|
wiz_trader/quotes/client.py
CHANGED
@@ -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
|
-
-
|
37
|
-
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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,,
|
File without changes
|
File without changes
|