viper-execution 0.2.0__tar.gz → 0.2.2__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.
- viper_execution-0.2.2/.github/workflows/release.yml +74 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/PKG-INFO +1 -1
- {viper_execution-0.2.0 → viper_execution-0.2.2}/pyproject.toml +1 -1
- viper_execution-0.2.2/src/viper/examples/_algo_rest_common.py +123 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/detect_and_fire_glidemaker.py +10 -7
- viper_execution-0.2.2/src/viper/examples/smart_exit.py +85 -0
- viper_execution-0.2.2/src/viper/examples/start_flowband.py +32 -0
- viper_execution-0.2.2/src/viper/examples/start_flowscale.py +32 -0
- viper_execution-0.2.2/src/viper/examples/start_ghostsweep.py +32 -0
- viper_execution-0.2.0/.github/workflows/release.yml +0 -43
- {viper_execution-0.2.0 → viper_execution-0.2.2}/.gitignore +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/LICENSE +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/README.md +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/__init__.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/__init__.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/__main__.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/_algo_common.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/start_glidemaker.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/start_pacemaker.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/stream_account_state.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/exceptions.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/py.typed +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/rest.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/rest_types.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/ws.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/tests/test_handshake_status.py +0 -0
- {viper_execution-0.2.0 → viper_execution-0.2.2}/tests/test_resync_map.py +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
name: Build distribution
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
# full history + main, so the guard can compare the tag to main's tip
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
|
|
21
|
+
# Release guard. The publish job (needs: build) runs only if this passes,
|
|
22
|
+
# so a tag failing either check never reaches PyPI, where versions are
|
|
23
|
+
# immutable. Two checks:
|
|
24
|
+
# 1. Tag name matches the pyproject version.
|
|
25
|
+
# 2. Tag commit is the tip of main. A version comparison alone is
|
|
26
|
+
# insufficient: a tag can reference a commit behind main while the
|
|
27
|
+
# version string is unchanged, so commit identity is compared directly.
|
|
28
|
+
- name: Validate release tag
|
|
29
|
+
run: |
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
TAG="${GITHUB_REF_NAME}"
|
|
32
|
+
VER="v$(grep -E '^version *= *"' pyproject.toml | head -1 | sed -E 's/.*"([^"]+)".*/\1/')"
|
|
33
|
+
echo "tag=$TAG pyproject=$VER"
|
|
34
|
+
if [ "$TAG" != "$VER" ]; then
|
|
35
|
+
echo "::error::Tag $TAG does not match pyproject version $VER."
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
git fetch --quiet origin main
|
|
39
|
+
TAG_SHA="$(git rev-parse HEAD)"
|
|
40
|
+
MAIN_SHA="$(git rev-parse origin/main)"
|
|
41
|
+
echo "tag_sha=$TAG_SHA main_sha=$MAIN_SHA"
|
|
42
|
+
if [ "$TAG_SHA" != "$MAIN_SHA" ]; then
|
|
43
|
+
echo "::error::Tag commit is not the tip of main. Re-point the tag to main before releasing."
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
echo "Release tag validated: $TAG at $TAG_SHA"
|
|
47
|
+
|
|
48
|
+
- name: Build sdist and wheel
|
|
49
|
+
run: |
|
|
50
|
+
python -m pip install --upgrade build
|
|
51
|
+
python -m build
|
|
52
|
+
- name: Verify metadata
|
|
53
|
+
run: |
|
|
54
|
+
python -m pip install --upgrade twine
|
|
55
|
+
twine check dist/*
|
|
56
|
+
- uses: actions/upload-artifact@v4
|
|
57
|
+
with:
|
|
58
|
+
name: dist
|
|
59
|
+
path: dist/
|
|
60
|
+
|
|
61
|
+
publish:
|
|
62
|
+
name: Publish to PyPI
|
|
63
|
+
needs: build
|
|
64
|
+
runs-on: ubuntu-latest
|
|
65
|
+
environment: pypi
|
|
66
|
+
permissions:
|
|
67
|
+
id-token: write
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/download-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
- name: Publish
|
|
74
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: viper-execution
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Institutional-grade Python SDK for the Viper Execution trading API on Hyperliquid.
|
|
5
5
|
Project-URL: Homepage, https://viperexecution.com
|
|
6
6
|
Project-URL: Documentation, https://docs.viperexecution.com
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "viper-execution"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
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"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared REST launch flow for the price-level algo examples (GhostSweep,
|
|
3
|
+
FlowScale, FlowBand). NOT discovered as an example itself (leading underscore).
|
|
4
|
+
|
|
5
|
+
run_buy_algo does, in order:
|
|
6
|
+
1. Read live BTC mark + sizing fields via rest.instrument("BTC").
|
|
7
|
+
2. Size to a USD notional (default $250), floored to the instrument's lot.
|
|
8
|
+
3. Build the algo's params from the live mark (per-algo callback).
|
|
9
|
+
4. Disclose exactly what will be sent, with a Ctrl-C abort window (nothing
|
|
10
|
+
sent yet).
|
|
11
|
+
5. Fire EXACTLY ONCE via rest.execute(...) — one idempotency key up front.
|
|
12
|
+
6. Poll rest.execution(id) for status, then cancel unless told otherwise.
|
|
13
|
+
|
|
14
|
+
These place REAL orders on mainnet. There is no testnet. The default params
|
|
15
|
+
arm each algo away from the market (a few % out), so within the short observe
|
|
16
|
+
window they rest rather than fill — then get cancelled.
|
|
17
|
+
|
|
18
|
+
Env: VIPER_API_KEY, VIPER_API_SECRET, VIPER_HANDLE, VIPER_WALLET (required);
|
|
19
|
+
VIPER_EXAMPLE_USD (250), VIPER_EXAMPLE_ABORT_S (3),
|
|
20
|
+
VIPER_EXAMPLE_OBSERVE_S (10), VIPER_EXAMPLE_POLL_S (2.0),
|
|
21
|
+
VIPER_EXAMPLE_NO_CANCEL (set to leave it running).
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import math
|
|
27
|
+
import uuid
|
|
28
|
+
import asyncio
|
|
29
|
+
from typing import Callable, Optional
|
|
30
|
+
|
|
31
|
+
from viper import ViperRestClient, ViperError
|
|
32
|
+
|
|
33
|
+
SYMBOL = "BTC"
|
|
34
|
+
USD = float(os.environ.get("VIPER_EXAMPLE_USD", "250"))
|
|
35
|
+
ABORT_S = float(os.environ.get("VIPER_EXAMPLE_ABORT_S", "3"))
|
|
36
|
+
OBSERVE_S = float(os.environ.get("VIPER_EXAMPLE_OBSERVE_S", "10"))
|
|
37
|
+
POLL_S = float(os.environ.get("VIPER_EXAMPLE_POLL_S", "2.0"))
|
|
38
|
+
NO_CANCEL = bool(os.environ.get("VIPER_EXAMPLE_NO_CANCEL"))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def require_env() -> None:
|
|
42
|
+
missing = [k for k in ("VIPER_API_KEY", "VIPER_API_SECRET") if not os.environ.get(k)]
|
|
43
|
+
if missing:
|
|
44
|
+
raise SystemExit(f"# missing required env: {', '.join(missing)}")
|
|
45
|
+
if not os.environ.get("VIPER_WALLET"):
|
|
46
|
+
print("# note: VIPER_WALLET not set — the resolved wallet comes from your handle.")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def compute_size(usd: float, mark: float, sz_decimals: int, min_size: float) -> float:
|
|
50
|
+
"""USD notional -> base size, floored to the instrument's lot, min-clamped."""
|
|
51
|
+
q = 10 ** int(sz_decimals)
|
|
52
|
+
sized = math.floor((usd / mark) * q) / q
|
|
53
|
+
if min_size and sized < min_size:
|
|
54
|
+
sized = min_size
|
|
55
|
+
return sized
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
async def instrument_mark(rest: ViperRestClient) -> tuple:
|
|
59
|
+
"""Return (mark, sz_decimals, min_order_value_size) for BTC."""
|
|
60
|
+
rec = (await rest.instrument(SYMBOL))["instrument"]
|
|
61
|
+
return (float(rec["mark_price"]), int(rec.get("sz_decimals", 5)),
|
|
62
|
+
float(rec.get("min_order_value_size") or 0.0))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def poll_and_cancel(rest: ViperRestClient, exec_id: str, label: str) -> None:
|
|
66
|
+
"""Poll execution status for the observe window, then cancel (unless
|
|
67
|
+
VIPER_EXAMPLE_NO_CANCEL). Status/fill live under the `state` object."""
|
|
68
|
+
deadline = asyncio.get_event_loop().time() + OBSERVE_S
|
|
69
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
70
|
+
await asyncio.sleep(max(2.0, POLL_S))
|
|
71
|
+
try:
|
|
72
|
+
st = await rest.execution(exec_id)
|
|
73
|
+
except ViperError as e:
|
|
74
|
+
print(f"# status poll failed: {type(e).__name__} code={e.code}")
|
|
75
|
+
break
|
|
76
|
+
state = st.get("state") or {}
|
|
77
|
+
print(f"# status={state.get('status')} "
|
|
78
|
+
f"filled={state.get('filled_size')}/{state.get('total_size')} "
|
|
79
|
+
f"({state.get('filled_pct')}%)")
|
|
80
|
+
if NO_CANCEL:
|
|
81
|
+
print(f"# VIPER_EXAMPLE_NO_CANCEL set — leaving {exec_id} running.")
|
|
82
|
+
else:
|
|
83
|
+
try:
|
|
84
|
+
c = await rest.cancel_execution(exec_id)
|
|
85
|
+
print(f"# {label}: cancel -> {c.get('status') or c.get('result') or 'ok'}")
|
|
86
|
+
except ViperError as e:
|
|
87
|
+
print(f"# cancel failed: {type(e).__name__} code={e.code}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def run_buy_algo(*, algo: str, build_params: Callable[[float], dict],
|
|
91
|
+
label: str) -> None:
|
|
92
|
+
"""Size off live mark, disclose + abort window, fire one buy via REST, poll,
|
|
93
|
+
cancel. `build_params(mark)` returns the per-algo params dict."""
|
|
94
|
+
require_env()
|
|
95
|
+
rest = ViperRestClient.from_env()
|
|
96
|
+
async with rest:
|
|
97
|
+
mark, sz_decimals, min_size = await instrument_mark(rest)
|
|
98
|
+
size = compute_size(USD, mark, sz_decimals, min_size)
|
|
99
|
+
params = build_params(mark)
|
|
100
|
+
|
|
101
|
+
print(f"# {label}: LIVE buy {size} {SYMBOL} (~${USD:.0f} at mark {mark}) "
|
|
102
|
+
f"on MAINNET — this places a real order.")
|
|
103
|
+
print(f"# params={params}")
|
|
104
|
+
print(f"# Ctrl-C within {ABORT_S:.0f}s to abort (nothing sent yet)...")
|
|
105
|
+
try:
|
|
106
|
+
await asyncio.sleep(ABORT_S)
|
|
107
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
108
|
+
print("\n# aborted — nothing was sent.")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
idem = uuid.uuid4().hex
|
|
112
|
+
try:
|
|
113
|
+
res = await rest.execute(algo=algo, symbol=SYMBOL, side="buy",
|
|
114
|
+
total_size=size, params=params,
|
|
115
|
+
idempotency_key=idem)
|
|
116
|
+
except ViperError as e:
|
|
117
|
+
print(f"# execute failed: {type(e).__name__} code={e.code} status={e.status}")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
exec_id = res.get("execution_id") or res.get("id")
|
|
121
|
+
print(f"# {label}: launched execution_id={exec_id} status={res.get('status')}")
|
|
122
|
+
if exec_id:
|
|
123
|
+
await poll_and_cancel(rest, exec_id, label)
|
{viper_execution-0.2.0 → viper_execution-0.2.2}/src/viper/examples/detect_and_fire_glidemaker.py
RENAMED
|
@@ -101,11 +101,12 @@ async def main():
|
|
|
101
101
|
_require_env()
|
|
102
102
|
rest = ViperRestClient.from_env()
|
|
103
103
|
async with rest:
|
|
104
|
-
# 1) size off the live instrument
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
# 1) size off the live instrument. GET /v1/instruments/{symbol} wraps the
|
|
105
|
+
# record under "instrument" (alongside a validation_example).
|
|
106
|
+
rec = (await rest.instrument(SYMBOL))["instrument"]
|
|
107
|
+
mark = float(rec["mark_price"])
|
|
108
|
+
size = _compute_size(USD, mark, rec.get("sz_decimals", 5),
|
|
109
|
+
float(rec.get("min_order_value_size") or 0))
|
|
109
110
|
|
|
110
111
|
# 2) watch for the signal
|
|
111
112
|
if not await _detect_signal(rest):
|
|
@@ -149,8 +150,10 @@ async def main():
|
|
|
149
150
|
except ViperError as e:
|
|
150
151
|
print(f"# status poll failed: {type(e).__name__} code={e.code}")
|
|
151
152
|
break
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
state = st.get("state") or {}
|
|
154
|
+
print(f"# execution status={state.get('status')} "
|
|
155
|
+
f"filled={state.get('filled_size')}/{state.get('total_size')} "
|
|
156
|
+
f"({state.get('filled_pct')}%) maker={state.get('maker_fill_size')}")
|
|
154
157
|
if NO_CANCEL:
|
|
155
158
|
print(f"# VIPER_EXAMPLE_NO_CANCEL set — leaving {exec_id} running.")
|
|
156
159
|
else:
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fire Smart Exit (conditional reduce-only exit) on an existing BTC long — REST,
|
|
4
|
+
live mainnet.
|
|
5
|
+
|
|
6
|
+
Smart Exit is a reduce-only stop: for a SELL exit it fires when ask < limit_price.
|
|
7
|
+
This example finds your existing BTC long, arms a sell-stop 5% BELOW mark (so it
|
|
8
|
+
rests as protection and does NOT fire within the observe window), then cancels.
|
|
9
|
+
reduce_only is forced by the algo — the request does not declare it.
|
|
10
|
+
|
|
11
|
+
Smart Exit exits a position; it does not open one. If you have no BTC long, the
|
|
12
|
+
example tells you and exits without firing. Open a small long (~$250) first, then
|
|
13
|
+
re-run. There is no testnet.
|
|
14
|
+
|
|
15
|
+
Env: VIPER_API_KEY, VIPER_API_SECRET, VIPER_HANDLE, VIPER_WALLET (+ optional
|
|
16
|
+
VIPER_EXAMPLE_OBSERVE_S / _NO_CANCEL). Run:
|
|
17
|
+
viper-examples smart-exit
|
|
18
|
+
"""
|
|
19
|
+
import uuid
|
|
20
|
+
import asyncio
|
|
21
|
+
|
|
22
|
+
from viper import ViperRestClient, ViperError
|
|
23
|
+
from viper.examples._algo_rest_common import (
|
|
24
|
+
require_env, instrument_mark, poll_and_cancel, ABORT_S, SYMBOL,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
ORDER = 8
|
|
28
|
+
DESCRIPTION = "Arm Smart Exit (reduce-only stop) on an existing BTC long over REST — live, auto-cancels."
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _find_long(positions: dict):
|
|
32
|
+
"""Return the signed size of the BTC long, or None. size>0 is long."""
|
|
33
|
+
for p in (positions or {}).get("items", []):
|
|
34
|
+
if str(p.get("symbol", "")).upper() == SYMBOL:
|
|
35
|
+
size = float(p.get("size") or 0)
|
|
36
|
+
if size > 0:
|
|
37
|
+
return size
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def main():
|
|
42
|
+
require_env()
|
|
43
|
+
rest = ViperRestClient.from_env()
|
|
44
|
+
async with rest:
|
|
45
|
+
long_size = _find_long(await rest.positions())
|
|
46
|
+
if not long_size:
|
|
47
|
+
print(f"# Smart Exit exits an existing {SYMBOL} long; none found.")
|
|
48
|
+
print(f"# Open a small {SYMBOL} long (~$250) first, then re-run.")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
mark, _, _ = await instrument_mark(rest)
|
|
52
|
+
# SELL exit fires when ask < limit_price; 5% below mark -> arms as a
|
|
53
|
+
# stop, won't fire now.
|
|
54
|
+
limit_price = round(mark * 0.95)
|
|
55
|
+
params = {"strategy": "neutral", "limit_price": limit_price}
|
|
56
|
+
|
|
57
|
+
print(f"# Smart Exit: LIVE reduce-only sell-stop on {long_size} {SYMBOL} "
|
|
58
|
+
f"long — limit {limit_price} (~5% below mark {mark}).")
|
|
59
|
+
print(f"# params={params}")
|
|
60
|
+
print(f"# Ctrl-C within {ABORT_S:.0f}s to abort (nothing sent yet)...")
|
|
61
|
+
try:
|
|
62
|
+
await asyncio.sleep(ABORT_S)
|
|
63
|
+
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
64
|
+
print("\n# aborted — nothing was sent.")
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
idem = uuid.uuid4().hex
|
|
68
|
+
try:
|
|
69
|
+
# No reduce_only kwarg: Smart Exit forces it at the algo layer, and
|
|
70
|
+
# the request schema rejects a declared reduce_only.
|
|
71
|
+
res = await rest.execute(algo="smart_exit", symbol=SYMBOL, side="sell",
|
|
72
|
+
total_size=long_size, params=params,
|
|
73
|
+
idempotency_key=idem)
|
|
74
|
+
except ViperError as e:
|
|
75
|
+
print(f"# execute failed: {type(e).__name__} code={e.code} status={e.status}")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
exec_id = res.get("execution_id") or res.get("id")
|
|
79
|
+
print(f"# Smart Exit: launched execution_id={exec_id} status={res.get('status')}")
|
|
80
|
+
if exec_id:
|
|
81
|
+
await poll_and_cancel(rest, exec_id, "Smart Exit")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fire FlowBand (floating stealth scale) on BTC over REST — live mainnet.
|
|
4
|
+
|
|
5
|
+
FlowBand floats a stealth scale of levels across a percentage band. This example
|
|
6
|
+
uses a 0.5%-2% band (the required range params); for a BUY the levels rest below
|
|
7
|
+
market and won't fully fill in the short observe window, then get cancelled.
|
|
8
|
+
Places real orders (~$250 total). There is no testnet.
|
|
9
|
+
|
|
10
|
+
Env: VIPER_API_KEY, VIPER_API_SECRET, VIPER_HANDLE, VIPER_WALLET (+ optional
|
|
11
|
+
VIPER_EXAMPLE_USD / _OBSERVE_S / _NO_CANCEL). Run:
|
|
12
|
+
viper-examples start-flowband
|
|
13
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
|
|
16
|
+
from viper.examples._algo_rest_common import run_buy_algo
|
|
17
|
+
|
|
18
|
+
ORDER = 7
|
|
19
|
+
DESCRIPTION = "Fire FlowBand (floating stealth scale) on BTC over REST — live ~$250, auto-cancels."
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _params(mark: float) -> dict:
|
|
23
|
+
# required percentage band; num_levels/flow_band/bias take server defaults.
|
|
24
|
+
return {"range_from_pct": 0.5, "range_to_pct": 2.0}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def main():
|
|
28
|
+
await run_buy_algo(algo="flowband", build_params=_params, label="FlowBand")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fire FlowScale (scaled ladder) on BTC over REST — live mainnet.
|
|
4
|
+
|
|
5
|
+
FlowScale lays a scaled ladder of clips across a percentage band relative to the
|
|
6
|
+
market. This example uses a 0.5%-2% band with the minimum 5 clips; for a BUY the
|
|
7
|
+
ladder rests below market and won't fully fill in the short observe window, then
|
|
8
|
+
gets cancelled. Places real orders (~$250 total). There is no testnet.
|
|
9
|
+
|
|
10
|
+
Env: VIPER_API_KEY, VIPER_API_SECRET, VIPER_HANDLE, VIPER_WALLET (+ optional
|
|
11
|
+
VIPER_EXAMPLE_USD / _OBSERVE_S / _NO_CANCEL). Run:
|
|
12
|
+
viper-examples start-flowscale
|
|
13
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
|
|
16
|
+
from viper.examples._algo_rest_common import run_buy_algo
|
|
17
|
+
|
|
18
|
+
ORDER = 6
|
|
19
|
+
DESCRIPTION = "Fire FlowScale (scaled ladder) on BTC over REST — live ~$250, auto-cancels."
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _params(mark: float) -> dict:
|
|
23
|
+
# scaled ladder 0.5%-2% out, 5 clips (the minimum). pct band -> no absolute price.
|
|
24
|
+
return {"range_from_pct": 0.5, "range_to_pct": 2.0, "num_clips": 5}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def main():
|
|
28
|
+
await run_buy_algo(algo="flowscale", build_params=_params, label="FlowScale")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Fire GhostSweep (hidden stop/take) on BTC over REST — live mainnet.
|
|
4
|
+
|
|
5
|
+
GhostSweep arms a hidden sweep at a trigger price. For a BUY it activates when
|
|
6
|
+
ask <= trigger_price, so this example sets trigger 5% BELOW mark: the sweep arms
|
|
7
|
+
as a hidden buy-stop on a dip and does NOT fire within the observe window, then
|
|
8
|
+
gets cancelled. Places a real order (~$250). There is no testnet.
|
|
9
|
+
|
|
10
|
+
Env: VIPER_API_KEY, VIPER_API_SECRET, VIPER_HANDLE, VIPER_WALLET (+ optional
|
|
11
|
+
VIPER_EXAMPLE_USD / _OBSERVE_S / _NO_CANCEL). Run:
|
|
12
|
+
viper-examples start-ghostsweep
|
|
13
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
|
|
16
|
+
from viper.examples._algo_rest_common import run_buy_algo
|
|
17
|
+
|
|
18
|
+
ORDER = 5
|
|
19
|
+
DESCRIPTION = "Fire GhostSweep (hidden stop) on BTC over REST — live ~$250, auto-cancels."
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _params(mark: float) -> dict:
|
|
23
|
+
# BUY sweep arms when ask <= trigger_price; 5% below mark -> arms, won't fire now.
|
|
24
|
+
return {"strategy": "neutral", "trigger_price": round(mark * 0.95)}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def main():
|
|
28
|
+
await run_buy_algo(algo="ghostsweep", build_params=_params, label="GhostSweep")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
asyncio.run(main())
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
name: release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- "v*"
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
build:
|
|
10
|
-
name: Build distribution
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v4
|
|
14
|
-
- uses: actions/setup-python@v5
|
|
15
|
-
with:
|
|
16
|
-
python-version: "3.12"
|
|
17
|
-
- name: Build sdist and wheel
|
|
18
|
-
run: |
|
|
19
|
-
python -m pip install --upgrade build
|
|
20
|
-
python -m build
|
|
21
|
-
- name: Verify metadata
|
|
22
|
-
run: |
|
|
23
|
-
python -m pip install --upgrade twine
|
|
24
|
-
twine check dist/*
|
|
25
|
-
- uses: actions/upload-artifact@v4
|
|
26
|
-
with:
|
|
27
|
-
name: dist
|
|
28
|
-
path: dist/
|
|
29
|
-
|
|
30
|
-
publish:
|
|
31
|
-
name: Publish to PyPI
|
|
32
|
-
needs: build
|
|
33
|
-
runs-on: ubuntu-latest
|
|
34
|
-
environment: pypi
|
|
35
|
-
permissions:
|
|
36
|
-
id-token: write
|
|
37
|
-
steps:
|
|
38
|
-
- uses: actions/download-artifact@v4
|
|
39
|
-
with:
|
|
40
|
-
name: dist
|
|
41
|
-
path: dist/
|
|
42
|
-
- name: Publish
|
|
43
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|