viper-execution 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. viper_execution-0.2.3/PKG-INFO +272 -0
  2. viper_execution-0.2.3/README.md +224 -0
  3. {viper_execution-0.2.2 → viper_execution-0.2.3}/pyproject.toml +2 -2
  4. viper_execution-0.2.2/PKG-INFO +0 -183
  5. viper_execution-0.2.2/README.md +0 -135
  6. {viper_execution-0.2.2 → viper_execution-0.2.3}/.github/workflows/release.yml +0 -0
  7. {viper_execution-0.2.2 → viper_execution-0.2.3}/.gitignore +0 -0
  8. {viper_execution-0.2.2 → viper_execution-0.2.3}/LICENSE +0 -0
  9. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/__init__.py +0 -0
  10. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/__init__.py +0 -0
  11. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/__main__.py +0 -0
  12. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/_algo_common.py +0 -0
  13. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/_algo_rest_common.py +0 -0
  14. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/detect_and_fire_glidemaker.py +0 -0
  15. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/smart_exit.py +0 -0
  16. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/start_flowband.py +0 -0
  17. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/start_flowscale.py +0 -0
  18. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/start_ghostsweep.py +0 -0
  19. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/start_glidemaker.py +0 -0
  20. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/start_pacemaker.py +0 -0
  21. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/examples/stream_account_state.py +0 -0
  22. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/exceptions.py +0 -0
  23. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/py.typed +0 -0
  24. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/rest.py +0 -0
  25. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/rest_types.py +0 -0
  26. {viper_execution-0.2.2 → viper_execution-0.2.3}/src/viper/ws.py +0 -0
  27. {viper_execution-0.2.2 → viper_execution-0.2.3}/tests/test_handshake_status.py +0 -0
  28. {viper_execution-0.2.2 → viper_execution-0.2.3}/tests/test_resync_map.py +0 -0
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: viper-execution
3
+ Version: 0.2.3
4
+ Summary: Institutional-grade Python SDK for the Viper Execution trading API on Hyperliquid.
5
+ Project-URL: Homepage, https://viperexecution.com
6
+ Project-URL: Documentation, https://docs.viperexecution.com
7
+ Project-URL: Repository, https://github.com/viperexecution/viper-sdk-python
8
+ Project-URL: Issues, https://github.com/viperexecution/viper-sdk-python/issues
9
+ Author: Viper Execution
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Viper Execution
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: algotrading,hyperliquid,trading,viper,websocket
33
+ Classifier: Development Status :: 5 - Production/Stable
34
+ Classifier: Intended Audience :: Financial and Insurance Industry
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Typing :: Typed
41
+ Requires-Python: >=3.10
42
+ Requires-Dist: httpx>=0.27.0
43
+ Requires-Dist: websockets>=13.0
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
46
+ Requires-Dist: pytest>=8.0; extra == 'dev'
47
+ Description-Content-Type: text/markdown
48
+
49
+ # Viper Execution Python SDK
50
+
51
+ Institutional-grade Python client for the [Viper Execution](https://viperexecution.com) trading API on Hyperliquid.
52
+
53
+ > **Status:** SDK `0.2.x`. Ships a typed async REST client (`ViperRestClient`) and a resilient WebSocket client (`ViperWSClient`). The SDK version is independent of the API version — this is SDK 0.x against API v1.
54
+
55
+ The SDK is a convenience layer over the raw HMAC + REST/WebSocket surface — never required. Every response is returned as a plain `dict`, so you are never boxed out of the raw payload; the typed signatures and `TypedDict` hints are there for editor and type-checker support only.
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pip install viper-execution
61
+ ```
62
+
63
+ Requires Python ≥ 3.10.
64
+
65
+ ## Quickstart (REST)
66
+
67
+ ```python
68
+ import asyncio
69
+ from viper import ViperRestClient
70
+
71
+ async def main():
72
+ # from_env() reads VIPER_API_KEY / VIPER_API_SECRET / VIPER_HANDLE /
73
+ # VIPER_WALLET. Pass anything explicitly to override the environment.
74
+ async with ViperRestClient.from_env() as viper:
75
+ # market data
76
+ btc = (await viper.instrument("BTC"))["instrument"]
77
+ print("BTC mark:", btc["mark_price"])
78
+
79
+ # launch a Glidemaker (idempotency key is auto-generated)
80
+ res = await viper.execute(
81
+ algo="glidemaker", symbol="BTC", side="buy", total_size=0.001,
82
+ params={"strategy": "neutral", "limit_price": 65000},
83
+ )
84
+ print(res["execution_id"], res["status"])
85
+
86
+ asyncio.run(main())
87
+ ```
88
+
89
+ ## Quickstart (WebSocket)
90
+
91
+ ```python
92
+ import os
93
+ import asyncio
94
+ from viper import ViperWSClient
95
+
96
+ async def main():
97
+ wallet = os.environ["VIPER_WALLET"].lower()
98
+ client = ViperWSClient.from_env(
99
+ on_event=lambda f: print(f["channel"], f.get("event")),
100
+ )
101
+ await client.start()
102
+ await client.subscribe("account.state", wallet)
103
+ await asyncio.sleep(30)
104
+ await client.close()
105
+
106
+ asyncio.run(main())
107
+ ```
108
+
109
+ ## Using credentials
110
+
111
+ Both clients take credentials two ways, both first-class — pick whichever fits how your process gets its secrets.
112
+
113
+ **From the environment (quickest, and production-correct).** `from_env()` reads `VIPER_API_KEY`, `VIPER_API_SECRET`, `VIPER_HANDLE`, and `VIPER_WALLET`. This is also the right pattern for deployment: containers, CI, and secret managers all inject secrets as env vars, so the same code runs unchanged from laptop to production. Anything passed explicitly overrides the environment:
114
+
115
+ ```python
116
+ viper = ViperRestClient.from_env(handle="override-handle")
117
+ ```
118
+
119
+ **Explicitly (your own secret store).** If your keys live in Vault, AWS Secrets Manager, an HSM, or a config file, fetch them in your code and pass them to the constructor directly — `from_env()` is never required:
120
+
121
+ ```python
122
+ api_key_id, api_secret = my_secret_store.get("viper") # however you fetch them
123
+ viper = ViperRestClient(
124
+ api_key_id=api_key_id,
125
+ api_secret=api_secret,
126
+ handle="your-handle",
127
+ wallet="0x...",
128
+ )
129
+ ```
130
+
131
+ `ViperWSClient` constructs identically.
132
+
133
+ ## Using the REST client
134
+
135
+ `ViperRestClient` is async and instance-based (no global singleton). It covers the core trading surface: execute and executions, orders, account, positions, market data (instruments, price, orderbook), leverage, and limits.
136
+
137
+ ```python
138
+ async with ViperRestClient.from_env() as viper:
139
+ # reads — return the raw dict as-is
140
+ state = await viper.account_state()
141
+ pos = await viper.positions()
142
+ price = await viper.price("BTC")
143
+
144
+ # mutating calls auto-generate an Idempotency-Key and are throttled
145
+ order = await viper.place_order(symbol="BTC", side="buy", size=0.001,
146
+ order_type="limit", price=60000, post_only=True)
147
+ await viper.cancel_order(symbol="BTC", order_id=order["order_ids"][0])
148
+ ```
149
+
150
+ What the client handles for you:
151
+
152
+ - **Signing** — HMAC-SHA256 over the canonical `{timestamp}{method}{path}{body}`, signed over the exact bytes sent. You never construct a signature.
153
+ - **Idempotency** — every mutating call (`execute`, `place_order`, `cancel_*`, `modify_order`, `close_*`, execution lifecycle, `set_leverage`, `update_settings`, `nuke`) auto-generates an `Idempotency-Key` unless you pass one. Reads don't.
154
+ - **Replay spacing** — a per-instance throttle keeps mutating calls ≥ 1.1s apart; reads are never blocked.
155
+ - **Configurable transport** — inject your own `httpx.AsyncClient` via `http_client=...` to set timeouts, proxies, or pools.
156
+
157
+ `nuke()` (cancel all orders + close all positions) requires `confirm=True` with no default — the conscious step the raw API gets from its mandatory `Idempotency-Key`, which the client otherwise fills for you.
158
+
159
+ ### Errors
160
+
161
+ The API error envelope is mapped to typed exceptions, all subclasses of `ViperError`:
162
+
163
+ | Exception | Maps from |
164
+ |---|---|
165
+ | `ViperValidationError` | `validation_error`, `bad_request`, `missing_field`, … (400/422) |
166
+ | `ViperAuthError` | `unauthorized`, `insufficient_scope`, `forbidden`, `tenancy_denied` (401/403) |
167
+ | `ViperConflictError` | `conflict`, `idempotency_mismatch`, `state_transition_forbidden` (409) |
168
+ | `ViperNotFoundError` | `not_found`, `unknown_route`, `scope_not_found` (404) |
169
+ | `ViperRateLimitError` | `rate_limited`, `venue_rate_limit` (429) — carries `retry_after` |
170
+ | `ViperAPIError` | anything else |
171
+
172
+ Every exception carries `.code` (the machine-readable error code), `.status`, and `.payload`, so you can branch precisely — e.g. tell a state `conflict` from an `idempotency_mismatch`:
173
+
174
+ ```python
175
+ from viper import ViperConflictError
176
+
177
+ try:
178
+ await viper.execute(algo="glidemaker", symbol="BTC", side="buy",
179
+ total_size=0.001, params={"strategy": "neutral"})
180
+ except ViperConflictError as e:
181
+ if e.code == "idempotency_mismatch":
182
+ ... # reused key with a different body — a client bug
183
+ ```
184
+
185
+ ## Runnable examples
186
+
187
+ Examples ship inside the package — no extra downloads. List the catalog and
188
+ run one by name or number:
189
+
190
+ ```bash
191
+ viper-examples # list the catalog
192
+ viper-examples stream-account-state # run by name
193
+ viper-examples 01 # ...or by number
194
+ ```
195
+
196
+ Set the env vars the examples read — bash/zsh:
197
+
198
+ ```bash
199
+ export VIPER_API_KEY=vk_...
200
+ export VIPER_API_SECRET=vs_...
201
+ export VIPER_HANDLE=your-handle # optional
202
+ export VIPER_WALLET=0x... # the wallet to trade/stream
203
+ ```
204
+
205
+ …or PowerShell:
206
+
207
+ ```powershell
208
+ $env:VIPER_API_KEY = "vk_..."
209
+ $env:VIPER_API_SECRET = "vs_..."
210
+ $env:VIPER_HANDLE = "your-handle" # optional
211
+ $env:VIPER_WALLET = "0x..." # the wallet to trade/stream
212
+ ```
213
+
214
+ ### Live algo examples
215
+
216
+ Several examples fire a real algo. They place **real orders on mainnet** (there
217
+ is no testnet). Each reads the live BTC mark, sizes to a USD notional (default
218
+ ~$250), discloses exactly what it will do with a short Ctrl-C abort window, fires
219
+ **once**, observes, then cancels.
220
+
221
+ Over the WebSocket command surface:
222
+
223
+ ```bash
224
+ viper-examples start-glidemaker # passive limit
225
+ viper-examples start-pacemaker # TWAP
226
+ ```
227
+
228
+ Over the REST client:
229
+
230
+ ```bash
231
+ viper-examples detect-and-fire-glidemaker # poll a signal, then fire on it
232
+ viper-examples start-ghostsweep # hidden stop
233
+ viper-examples start-flowscale # scaled ladder
234
+ viper-examples start-flowband # floating stealth scale
235
+ viper-examples smart-exit # reduce-only stop on an existing long
236
+ ```
237
+
238
+ Optional knobs:
239
+
240
+ ```bash
241
+ export VIPER_EXAMPLE_USD=250 # target notional (default 250)
242
+ export VIPER_EXAMPLE_OBSERVE_S=10 # seconds to observe before cancel (default 10)
243
+ export VIPER_EXAMPLE_NO_CANCEL=1 # leave the execution running instead of cancelling
244
+ ```
245
+
246
+ The source for each example lives in
247
+ [`src/viper/examples/`](src/viper/examples/).
248
+
249
+ ## What the WebSocket client handles for you
250
+
251
+ The `/v1/ws` stream has a number of behaviors a naive client gets wrong. `ViperWSClient` handles them as a built-in contract:
252
+
253
+ - **Liveness** — transport ping/pong plus a data-staleness watchdog; silent half-open connections are detected and reconnected.
254
+ - **Reconnect with resume** — on drop, it reconnects with exponential backoff and resubscribes every scope carrying its `last_seq` cursor, so you resume exactly where you left off (replay from the per-scope ring buffer).
255
+ - **Resync recovery** — when the server can't satisfy a cursor (`buffer_overflow` / `last_seq_ahead_of_server` / `scope_not_found`), it REST-fetches authoritative current state and resubscribes fresh.
256
+ - **Multi-wallet attribution** — every data frame is routed by `data.wallet`, so one socket can carry many wallets without cross-attribution. (Control markers such as `hydrated` carry no `data.wallet`; route those by `scope_id`.)
257
+ - **Slow hydration** — `account.state` hydration is server-slow (~5s; it gathers balance + HIP-3 collateral across all dexes, then bursts frames). The client does not mistake that for a dead stream, and neither should your application logic.
258
+ - **Terminal conditions** — credential revocation (close `4013`), a handshake auth rejection (HTTP `401`/`403` — revoked/invalid key or insufficient scope), or an exhausted reconnect budget all stop the loop permanently via `on_terminal` rather than reconnect-hammering.
259
+
260
+ ## Callbacks
261
+
262
+ | Callback | Fires on |
263
+ |---|---|
264
+ | `on_event(frame)` | Every classified data frame (the main path) |
265
+ | `on_meta(frame)` | `_meta` frames: welcome + upstream connectivity events |
266
+ | `on_terminal(code)` | Terminal stop. `code` is the WS close code (`4013` = credentials revoked), the handshake HTTP status (`401`/`403`), or `-1` (reconnect budget exhausted) |
267
+ | `on_command_result(frame)` | Subscribe acks / command errors (no correlation id) |
268
+ | `on_raw(frame)` | Optional advanced tap: every frame pre-classification (audit/metrics) |
269
+
270
+ ## License
271
+
272
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,224 @@
1
+ # Viper Execution Python SDK
2
+
3
+ Institutional-grade Python client for the [Viper Execution](https://viperexecution.com) trading API on Hyperliquid.
4
+
5
+ > **Status:** SDK `0.2.x`. Ships a typed async REST client (`ViperRestClient`) and a resilient WebSocket client (`ViperWSClient`). The SDK version is independent of the API version — this is SDK 0.x against API v1.
6
+
7
+ The SDK is a convenience layer over the raw HMAC + REST/WebSocket surface — never required. Every response is returned as a plain `dict`, so you are never boxed out of the raw payload; the typed signatures and `TypedDict` hints are there for editor and type-checker support only.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install viper-execution
13
+ ```
14
+
15
+ Requires Python ≥ 3.10.
16
+
17
+ ## Quickstart (REST)
18
+
19
+ ```python
20
+ import asyncio
21
+ from viper import ViperRestClient
22
+
23
+ async def main():
24
+ # from_env() reads VIPER_API_KEY / VIPER_API_SECRET / VIPER_HANDLE /
25
+ # VIPER_WALLET. Pass anything explicitly to override the environment.
26
+ async with ViperRestClient.from_env() as viper:
27
+ # market data
28
+ btc = (await viper.instrument("BTC"))["instrument"]
29
+ print("BTC mark:", btc["mark_price"])
30
+
31
+ # launch a Glidemaker (idempotency key is auto-generated)
32
+ res = await viper.execute(
33
+ algo="glidemaker", symbol="BTC", side="buy", total_size=0.001,
34
+ params={"strategy": "neutral", "limit_price": 65000},
35
+ )
36
+ print(res["execution_id"], res["status"])
37
+
38
+ asyncio.run(main())
39
+ ```
40
+
41
+ ## Quickstart (WebSocket)
42
+
43
+ ```python
44
+ import os
45
+ import asyncio
46
+ from viper import ViperWSClient
47
+
48
+ async def main():
49
+ wallet = os.environ["VIPER_WALLET"].lower()
50
+ client = ViperWSClient.from_env(
51
+ on_event=lambda f: print(f["channel"], f.get("event")),
52
+ )
53
+ await client.start()
54
+ await client.subscribe("account.state", wallet)
55
+ await asyncio.sleep(30)
56
+ await client.close()
57
+
58
+ asyncio.run(main())
59
+ ```
60
+
61
+ ## Using credentials
62
+
63
+ Both clients take credentials two ways, both first-class — pick whichever fits how your process gets its secrets.
64
+
65
+ **From the environment (quickest, and production-correct).** `from_env()` reads `VIPER_API_KEY`, `VIPER_API_SECRET`, `VIPER_HANDLE`, and `VIPER_WALLET`. This is also the right pattern for deployment: containers, CI, and secret managers all inject secrets as env vars, so the same code runs unchanged from laptop to production. Anything passed explicitly overrides the environment:
66
+
67
+ ```python
68
+ viper = ViperRestClient.from_env(handle="override-handle")
69
+ ```
70
+
71
+ **Explicitly (your own secret store).** If your keys live in Vault, AWS Secrets Manager, an HSM, or a config file, fetch them in your code and pass them to the constructor directly — `from_env()` is never required:
72
+
73
+ ```python
74
+ api_key_id, api_secret = my_secret_store.get("viper") # however you fetch them
75
+ viper = ViperRestClient(
76
+ api_key_id=api_key_id,
77
+ api_secret=api_secret,
78
+ handle="your-handle",
79
+ wallet="0x...",
80
+ )
81
+ ```
82
+
83
+ `ViperWSClient` constructs identically.
84
+
85
+ ## Using the REST client
86
+
87
+ `ViperRestClient` is async and instance-based (no global singleton). It covers the core trading surface: execute and executions, orders, account, positions, market data (instruments, price, orderbook), leverage, and limits.
88
+
89
+ ```python
90
+ async with ViperRestClient.from_env() as viper:
91
+ # reads — return the raw dict as-is
92
+ state = await viper.account_state()
93
+ pos = await viper.positions()
94
+ price = await viper.price("BTC")
95
+
96
+ # mutating calls auto-generate an Idempotency-Key and are throttled
97
+ order = await viper.place_order(symbol="BTC", side="buy", size=0.001,
98
+ order_type="limit", price=60000, post_only=True)
99
+ await viper.cancel_order(symbol="BTC", order_id=order["order_ids"][0])
100
+ ```
101
+
102
+ What the client handles for you:
103
+
104
+ - **Signing** — HMAC-SHA256 over the canonical `{timestamp}{method}{path}{body}`, signed over the exact bytes sent. You never construct a signature.
105
+ - **Idempotency** — every mutating call (`execute`, `place_order`, `cancel_*`, `modify_order`, `close_*`, execution lifecycle, `set_leverage`, `update_settings`, `nuke`) auto-generates an `Idempotency-Key` unless you pass one. Reads don't.
106
+ - **Replay spacing** — a per-instance throttle keeps mutating calls ≥ 1.1s apart; reads are never blocked.
107
+ - **Configurable transport** — inject your own `httpx.AsyncClient` via `http_client=...` to set timeouts, proxies, or pools.
108
+
109
+ `nuke()` (cancel all orders + close all positions) requires `confirm=True` with no default — the conscious step the raw API gets from its mandatory `Idempotency-Key`, which the client otherwise fills for you.
110
+
111
+ ### Errors
112
+
113
+ The API error envelope is mapped to typed exceptions, all subclasses of `ViperError`:
114
+
115
+ | Exception | Maps from |
116
+ |---|---|
117
+ | `ViperValidationError` | `validation_error`, `bad_request`, `missing_field`, … (400/422) |
118
+ | `ViperAuthError` | `unauthorized`, `insufficient_scope`, `forbidden`, `tenancy_denied` (401/403) |
119
+ | `ViperConflictError` | `conflict`, `idempotency_mismatch`, `state_transition_forbidden` (409) |
120
+ | `ViperNotFoundError` | `not_found`, `unknown_route`, `scope_not_found` (404) |
121
+ | `ViperRateLimitError` | `rate_limited`, `venue_rate_limit` (429) — carries `retry_after` |
122
+ | `ViperAPIError` | anything else |
123
+
124
+ Every exception carries `.code` (the machine-readable error code), `.status`, and `.payload`, so you can branch precisely — e.g. tell a state `conflict` from an `idempotency_mismatch`:
125
+
126
+ ```python
127
+ from viper import ViperConflictError
128
+
129
+ try:
130
+ await viper.execute(algo="glidemaker", symbol="BTC", side="buy",
131
+ total_size=0.001, params={"strategy": "neutral"})
132
+ except ViperConflictError as e:
133
+ if e.code == "idempotency_mismatch":
134
+ ... # reused key with a different body — a client bug
135
+ ```
136
+
137
+ ## Runnable examples
138
+
139
+ Examples ship inside the package — no extra downloads. List the catalog and
140
+ run one by name or number:
141
+
142
+ ```bash
143
+ viper-examples # list the catalog
144
+ viper-examples stream-account-state # run by name
145
+ viper-examples 01 # ...or by number
146
+ ```
147
+
148
+ Set the env vars the examples read — bash/zsh:
149
+
150
+ ```bash
151
+ export VIPER_API_KEY=vk_...
152
+ export VIPER_API_SECRET=vs_...
153
+ export VIPER_HANDLE=your-handle # optional
154
+ export VIPER_WALLET=0x... # the wallet to trade/stream
155
+ ```
156
+
157
+ …or PowerShell:
158
+
159
+ ```powershell
160
+ $env:VIPER_API_KEY = "vk_..."
161
+ $env:VIPER_API_SECRET = "vs_..."
162
+ $env:VIPER_HANDLE = "your-handle" # optional
163
+ $env:VIPER_WALLET = "0x..." # the wallet to trade/stream
164
+ ```
165
+
166
+ ### Live algo examples
167
+
168
+ Several examples fire a real algo. They place **real orders on mainnet** (there
169
+ is no testnet). Each reads the live BTC mark, sizes to a USD notional (default
170
+ ~$250), discloses exactly what it will do with a short Ctrl-C abort window, fires
171
+ **once**, observes, then cancels.
172
+
173
+ Over the WebSocket command surface:
174
+
175
+ ```bash
176
+ viper-examples start-glidemaker # passive limit
177
+ viper-examples start-pacemaker # TWAP
178
+ ```
179
+
180
+ Over the REST client:
181
+
182
+ ```bash
183
+ viper-examples detect-and-fire-glidemaker # poll a signal, then fire on it
184
+ viper-examples start-ghostsweep # hidden stop
185
+ viper-examples start-flowscale # scaled ladder
186
+ viper-examples start-flowband # floating stealth scale
187
+ viper-examples smart-exit # reduce-only stop on an existing long
188
+ ```
189
+
190
+ Optional knobs:
191
+
192
+ ```bash
193
+ export VIPER_EXAMPLE_USD=250 # target notional (default 250)
194
+ export VIPER_EXAMPLE_OBSERVE_S=10 # seconds to observe before cancel (default 10)
195
+ export VIPER_EXAMPLE_NO_CANCEL=1 # leave the execution running instead of cancelling
196
+ ```
197
+
198
+ The source for each example lives in
199
+ [`src/viper/examples/`](src/viper/examples/).
200
+
201
+ ## What the WebSocket client handles for you
202
+
203
+ The `/v1/ws` stream has a number of behaviors a naive client gets wrong. `ViperWSClient` handles them as a built-in contract:
204
+
205
+ - **Liveness** — transport ping/pong plus a data-staleness watchdog; silent half-open connections are detected and reconnected.
206
+ - **Reconnect with resume** — on drop, it reconnects with exponential backoff and resubscribes every scope carrying its `last_seq` cursor, so you resume exactly where you left off (replay from the per-scope ring buffer).
207
+ - **Resync recovery** — when the server can't satisfy a cursor (`buffer_overflow` / `last_seq_ahead_of_server` / `scope_not_found`), it REST-fetches authoritative current state and resubscribes fresh.
208
+ - **Multi-wallet attribution** — every data frame is routed by `data.wallet`, so one socket can carry many wallets without cross-attribution. (Control markers such as `hydrated` carry no `data.wallet`; route those by `scope_id`.)
209
+ - **Slow hydration** — `account.state` hydration is server-slow (~5s; it gathers balance + HIP-3 collateral across all dexes, then bursts frames). The client does not mistake that for a dead stream, and neither should your application logic.
210
+ - **Terminal conditions** — credential revocation (close `4013`), a handshake auth rejection (HTTP `401`/`403` — revoked/invalid key or insufficient scope), or an exhausted reconnect budget all stop the loop permanently via `on_terminal` rather than reconnect-hammering.
211
+
212
+ ## Callbacks
213
+
214
+ | Callback | Fires on |
215
+ |---|---|
216
+ | `on_event(frame)` | Every classified data frame (the main path) |
217
+ | `on_meta(frame)` | `_meta` frames: welcome + upstream connectivity events |
218
+ | `on_terminal(code)` | Terminal stop. `code` is the WS close code (`4013` = credentials revoked), the handshake HTTP status (`401`/`403`), or `-1` (reconnect budget exhausted) |
219
+ | `on_command_result(frame)` | Subscribe acks / command errors (no correlation id) |
220
+ | `on_raw(frame)` | Optional advanced tap: every frame pre-classification (audit/metrics) |
221
+
222
+ ## License
223
+
224
+ MIT — see [LICENSE](LICENSE).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "viper-execution"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Institutional-grade Python SDK for the Viper Execution trading API on Hyperliquid."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -12,7 +12,7 @@ license = { file = "LICENSE" }
12
12
  authors = [{ name = "Viper Execution" }]
13
13
  keywords = ["viper", "hyperliquid", "trading", "algotrading", "websocket"]
14
14
  classifiers = [
15
- "Development Status :: 4 - Beta",
15
+ "Development Status :: 5 - Production/Stable",
16
16
  "License :: OSI Approved :: MIT License",
17
17
  "Intended Audience :: Financial and Insurance Industry",
18
18
  "Programming Language :: Python :: 3",
@@ -1,183 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: viper-execution
3
- Version: 0.2.2
4
- Summary: Institutional-grade Python SDK for the Viper Execution trading API on Hyperliquid.
5
- Project-URL: Homepage, https://viperexecution.com
6
- Project-URL: Documentation, https://docs.viperexecution.com
7
- Project-URL: Repository, https://github.com/viperexecution/viper-sdk-python
8
- Project-URL: Issues, https://github.com/viperexecution/viper-sdk-python/issues
9
- Author: Viper Execution
10
- License: MIT License
11
-
12
- Copyright (c) 2026 Viper Execution
13
-
14
- Permission is hereby granted, free of charge, to any person obtaining a copy
15
- of this software and associated documentation files (the "Software"), to deal
16
- in the Software without restriction, including without limitation the rights
17
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
- copies of the Software, and to permit persons to whom the Software is
19
- furnished to do so, subject to the following conditions:
20
-
21
- The above copyright notice and this permission notice shall be included in all
22
- copies or substantial portions of the Software.
23
-
24
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
- SOFTWARE.
31
- License-File: LICENSE
32
- Keywords: algotrading,hyperliquid,trading,viper,websocket
33
- Classifier: Development Status :: 4 - Beta
34
- Classifier: Intended Audience :: Financial and Insurance Industry
35
- Classifier: License :: OSI Approved :: MIT License
36
- Classifier: Programming Language :: Python :: 3
37
- Classifier: Programming Language :: Python :: 3.10
38
- Classifier: Programming Language :: Python :: 3.11
39
- Classifier: Programming Language :: Python :: 3.12
40
- Classifier: Typing :: Typed
41
- Requires-Python: >=3.10
42
- Requires-Dist: httpx>=0.27.0
43
- Requires-Dist: websockets>=13.0
44
- Provides-Extra: dev
45
- Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
46
- Requires-Dist: pytest>=8.0; extra == 'dev'
47
- Description-Content-Type: text/markdown
48
-
49
- # Viper Execution Python SDK
50
-
51
- Institutional-grade Python client for the [Viper Execution](https://viperexecution.com) trading API on Hyperliquid.
52
-
53
- > **Status:** SDK `0.2.0` (beta). Ships the resilient WebSocket client and the resync REST-fetch mapping. The full typed REST client lands in a subsequent release. The SDK version is independent of the API version — this is SDK 0.x against API v1.
54
-
55
- ## Install
56
-
57
- ```bash
58
- pip install viper-execution
59
- ```
60
-
61
- Requires Python ≥ 3.10.
62
-
63
- ## Quickstart
64
-
65
- ```python
66
- import os
67
- import asyncio
68
- from viper import ViperWSClient
69
-
70
- async def main():
71
- # from_env() reads VIPER_API_KEY / VIPER_API_SECRET / VIPER_HANDLE /
72
- # VIPER_WALLET. Pass anything explicitly to override the environment.
73
- wallet = os.environ["VIPER_WALLET"].lower()
74
- client = ViperWSClient.from_env(
75
- on_event=lambda f: print(f["channel"], f.get("event")),
76
- )
77
- await client.start()
78
- await client.subscribe("account.state", wallet)
79
- await asyncio.sleep(30)
80
- await client.close()
81
-
82
- asyncio.run(main())
83
- ```
84
-
85
- ## Using credentials
86
-
87
- `ViperWSClient` takes credentials two ways, both first-class — pick whichever fits how your process gets its secrets.
88
-
89
- **From the environment (quickest, and production-correct).** `from_env()` reads `VIPER_API_KEY`, `VIPER_API_SECRET`, `VIPER_HANDLE`, and `VIPER_WALLET`. This is also the right pattern for deployment: containers, CI, and secret managers all inject secrets as env vars, so the same code runs unchanged from laptop to production. Anything passed explicitly overrides the environment:
90
-
91
- ```python
92
- client = ViperWSClient.from_env(handle="override-handle")
93
- ```
94
-
95
- **Explicitly (your own secret store).** If your keys live in Vault, AWS Secrets Manager, an HSM, or a config file, fetch them in your code and pass them to the constructor directly — `from_env()` is never required:
96
-
97
- ```python
98
- api_key_id, api_secret = my_secret_store.get("viper") # however you fetch them
99
- client = ViperWSClient(
100
- api_key_id=api_key_id,
101
- api_secret=api_secret,
102
- handle="your-handle",
103
- wallet="0x...",
104
- on_event=lambda f: print(f["channel"], f.get("event")),
105
- )
106
- ```
107
-
108
- ## Runnable examples
109
-
110
- Examples ship inside the package — no extra downloads. List the catalog and
111
- run one by name or number:
112
-
113
- ```bash
114
- viper-examples # list the catalog
115
- viper-examples stream-account-state # run by name
116
- viper-examples 01 # ...or by number
117
- ```
118
-
119
- Set the env vars the examples read — bash/zsh:
120
-
121
- ```bash
122
- export VIPER_API_KEY=vk_...
123
- export VIPER_API_SECRET=vs_...
124
- export VIPER_HANDLE=your-handle # optional
125
- export VIPER_WALLET=0x... # the wallet to stream
126
- ```
127
-
128
- …or PowerShell:
129
-
130
- ```powershell
131
- $env:VIPER_API_KEY = "vk_..."
132
- $env:VIPER_API_SECRET = "vs_..."
133
- $env:VIPER_HANDLE = "your-handle" # optional
134
- $env:VIPER_WALLET = "0x..." # the wallet to stream
135
- ```
136
-
137
- ### Live algo examples
138
-
139
- Two examples fire a real algo over the WebSocket command surface:
140
-
141
- ```bash
142
- viper-examples start-glidemaker # passive limit
143
- viper-examples start-pacemaker # TWAP
144
- ```
145
-
146
- These place **real orders on mainnet** (there is no testnet). Each one reads
147
- the live BTC mark from `/v1/instruments`, sizes the order to a USD notional,
148
- prints exactly what it is about to do with a short Ctrl-C abort window, fires
149
- once, streams the execution's frames (fills, slices, status), then cancels. Optional knobs:
150
-
151
- ```bash
152
- export VIPER_EXAMPLE_USD=250 # target notional (default 250)
153
- export VIPER_EXAMPLE_OBSERVE_S=10 # seconds to observe before cancel (default 10)
154
- export VIPER_EXAMPLE_NO_CANCEL=1 # leave the execution running instead of cancelling
155
- ```
156
-
157
- The source for each example lives in
158
- [`src/viper/examples/`](src/viper/examples/).
159
-
160
- ## What the WebSocket client handles for you
161
-
162
- The `/v1/ws` stream has a number of behaviors a naive client gets wrong. `ViperWSClient` handles them as a built-in contract:
163
-
164
- - **Liveness** — transport ping/pong plus a data-staleness watchdog; silent half-open connections are detected and reconnected.
165
- - **Reconnect with resume** — on drop, it reconnects with exponential backoff and resubscribes every scope carrying its `last_seq` cursor, so you resume exactly where you left off (replay from the per-scope ring buffer).
166
- - **Resync recovery** — when the server can't satisfy a cursor (`buffer_overflow` / `last_seq_ahead_of_server` / `scope_not_found`), it REST-fetches authoritative current state and resubscribes fresh.
167
- - **Multi-wallet attribution** — every data frame is routed by `data.wallet`, so one socket can carry many wallets without cross-attribution. (Control markers such as `hydrated` carry no `data.wallet`; route those by `scope_id`.)
168
- - **Slow hydration** — `account.state` hydration is server-slow (~5s; it gathers balance + HIP-3 collateral across all dexes, then bursts frames). The client does not mistake that for a dead stream, and neither should your application logic.
169
- - **Terminal conditions** — credential revocation (close `4013`), a handshake auth rejection (HTTP `401`/`403` — revoked/invalid key or insufficient scope), or an exhausted reconnect budget all stop the loop permanently via `on_terminal` rather than reconnect-hammering.
170
-
171
- ## Callbacks
172
-
173
- | Callback | Fires on |
174
- |---|---|
175
- | `on_event(frame)` | Every classified data frame (the main path) |
176
- | `on_meta(frame)` | `_meta` frames: welcome + upstream connectivity events |
177
- | `on_terminal(code)` | Terminal stop. `code` is the WS close code (`4013` = credentials revoked), the handshake HTTP status (`401`/`403`), or `-1` (reconnect budget exhausted) |
178
- | `on_command_result(frame)` | Subscribe acks / command errors (no correlation id) |
179
- | `on_raw(frame)` | Optional advanced tap: every frame pre-classification (audit/metrics) |
180
-
181
- ## License
182
-
183
- MIT — see [LICENSE](LICENSE).
@@ -1,135 +0,0 @@
1
- # Viper Execution Python SDK
2
-
3
- Institutional-grade Python client for the [Viper Execution](https://viperexecution.com) trading API on Hyperliquid.
4
-
5
- > **Status:** SDK `0.2.0` (beta). Ships the resilient WebSocket client and the resync REST-fetch mapping. The full typed REST client lands in a subsequent release. The SDK version is independent of the API version — this is SDK 0.x against API v1.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pip install viper-execution
11
- ```
12
-
13
- Requires Python ≥ 3.10.
14
-
15
- ## Quickstart
16
-
17
- ```python
18
- import os
19
- import asyncio
20
- from viper import ViperWSClient
21
-
22
- async def main():
23
- # from_env() reads VIPER_API_KEY / VIPER_API_SECRET / VIPER_HANDLE /
24
- # VIPER_WALLET. Pass anything explicitly to override the environment.
25
- wallet = os.environ["VIPER_WALLET"].lower()
26
- client = ViperWSClient.from_env(
27
- on_event=lambda f: print(f["channel"], f.get("event")),
28
- )
29
- await client.start()
30
- await client.subscribe("account.state", wallet)
31
- await asyncio.sleep(30)
32
- await client.close()
33
-
34
- asyncio.run(main())
35
- ```
36
-
37
- ## Using credentials
38
-
39
- `ViperWSClient` takes credentials two ways, both first-class — pick whichever fits how your process gets its secrets.
40
-
41
- **From the environment (quickest, and production-correct).** `from_env()` reads `VIPER_API_KEY`, `VIPER_API_SECRET`, `VIPER_HANDLE`, and `VIPER_WALLET`. This is also the right pattern for deployment: containers, CI, and secret managers all inject secrets as env vars, so the same code runs unchanged from laptop to production. Anything passed explicitly overrides the environment:
42
-
43
- ```python
44
- client = ViperWSClient.from_env(handle="override-handle")
45
- ```
46
-
47
- **Explicitly (your own secret store).** If your keys live in Vault, AWS Secrets Manager, an HSM, or a config file, fetch them in your code and pass them to the constructor directly — `from_env()` is never required:
48
-
49
- ```python
50
- api_key_id, api_secret = my_secret_store.get("viper") # however you fetch them
51
- client = ViperWSClient(
52
- api_key_id=api_key_id,
53
- api_secret=api_secret,
54
- handle="your-handle",
55
- wallet="0x...",
56
- on_event=lambda f: print(f["channel"], f.get("event")),
57
- )
58
- ```
59
-
60
- ## Runnable examples
61
-
62
- Examples ship inside the package — no extra downloads. List the catalog and
63
- run one by name or number:
64
-
65
- ```bash
66
- viper-examples # list the catalog
67
- viper-examples stream-account-state # run by name
68
- viper-examples 01 # ...or by number
69
- ```
70
-
71
- Set the env vars the examples read — bash/zsh:
72
-
73
- ```bash
74
- export VIPER_API_KEY=vk_...
75
- export VIPER_API_SECRET=vs_...
76
- export VIPER_HANDLE=your-handle # optional
77
- export VIPER_WALLET=0x... # the wallet to stream
78
- ```
79
-
80
- …or PowerShell:
81
-
82
- ```powershell
83
- $env:VIPER_API_KEY = "vk_..."
84
- $env:VIPER_API_SECRET = "vs_..."
85
- $env:VIPER_HANDLE = "your-handle" # optional
86
- $env:VIPER_WALLET = "0x..." # the wallet to stream
87
- ```
88
-
89
- ### Live algo examples
90
-
91
- Two examples fire a real algo over the WebSocket command surface:
92
-
93
- ```bash
94
- viper-examples start-glidemaker # passive limit
95
- viper-examples start-pacemaker # TWAP
96
- ```
97
-
98
- These place **real orders on mainnet** (there is no testnet). Each one reads
99
- the live BTC mark from `/v1/instruments`, sizes the order to a USD notional,
100
- prints exactly what it is about to do with a short Ctrl-C abort window, fires
101
- once, streams the execution's frames (fills, slices, status), then cancels. Optional knobs:
102
-
103
- ```bash
104
- export VIPER_EXAMPLE_USD=250 # target notional (default 250)
105
- export VIPER_EXAMPLE_OBSERVE_S=10 # seconds to observe before cancel (default 10)
106
- export VIPER_EXAMPLE_NO_CANCEL=1 # leave the execution running instead of cancelling
107
- ```
108
-
109
- The source for each example lives in
110
- [`src/viper/examples/`](src/viper/examples/).
111
-
112
- ## What the WebSocket client handles for you
113
-
114
- The `/v1/ws` stream has a number of behaviors a naive client gets wrong. `ViperWSClient` handles them as a built-in contract:
115
-
116
- - **Liveness** — transport ping/pong plus a data-staleness watchdog; silent half-open connections are detected and reconnected.
117
- - **Reconnect with resume** — on drop, it reconnects with exponential backoff and resubscribes every scope carrying its `last_seq` cursor, so you resume exactly where you left off (replay from the per-scope ring buffer).
118
- - **Resync recovery** — when the server can't satisfy a cursor (`buffer_overflow` / `last_seq_ahead_of_server` / `scope_not_found`), it REST-fetches authoritative current state and resubscribes fresh.
119
- - **Multi-wallet attribution** — every data frame is routed by `data.wallet`, so one socket can carry many wallets without cross-attribution. (Control markers such as `hydrated` carry no `data.wallet`; route those by `scope_id`.)
120
- - **Slow hydration** — `account.state` hydration is server-slow (~5s; it gathers balance + HIP-3 collateral across all dexes, then bursts frames). The client does not mistake that for a dead stream, and neither should your application logic.
121
- - **Terminal conditions** — credential revocation (close `4013`), a handshake auth rejection (HTTP `401`/`403` — revoked/invalid key or insufficient scope), or an exhausted reconnect budget all stop the loop permanently via `on_terminal` rather than reconnect-hammering.
122
-
123
- ## Callbacks
124
-
125
- | Callback | Fires on |
126
- |---|---|
127
- | `on_event(frame)` | Every classified data frame (the main path) |
128
- | `on_meta(frame)` | `_meta` frames: welcome + upstream connectivity events |
129
- | `on_terminal(code)` | Terminal stop. `code` is the WS close code (`4013` = credentials revoked), the handshake HTTP status (`401`/`403`), or `-1` (reconnect budget exhausted) |
130
- | `on_command_result(frame)` | Subscribe acks / command errors (no correlation id) |
131
- | `on_raw(frame)` | Optional advanced tap: every frame pre-classification (audit/metrics) |
132
-
133
- ## License
134
-
135
- MIT — see [LICENSE](LICENSE).
File without changes