hunterkiller 0.1.0__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.
- hunterkiller-0.1.0/PKG-INFO +38 -0
- hunterkiller-0.1.0/README.md +28 -0
- hunterkiller-0.1.0/hunterkiller/__init__.py +127 -0
- hunterkiller-0.1.0/hunterkiller.egg-info/PKG-INFO +38 -0
- hunterkiller-0.1.0/hunterkiller.egg-info/SOURCES.txt +7 -0
- hunterkiller-0.1.0/hunterkiller.egg-info/dependency_links.txt +1 -0
- hunterkiller-0.1.0/hunterkiller.egg-info/top_level.txt +1 -0
- hunterkiller-0.1.0/pyproject.toml +19 -0
- hunterkiller-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hunterkiller
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hunter Killer Desk Read client: check where liquidation money is stacked before a leveraged trade, and watch for walls coming into range.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://hunterkiller.io/for-agents
|
|
7
|
+
Keywords: crypto,liquidations,perpetuals,trading,agents,mcp
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# hunterkiller (Python)
|
|
12
|
+
|
|
13
|
+
Client for the Hunter Killer Desk Read. Standard library only.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install hunterkiller
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import hunterkiller as hk
|
|
21
|
+
|
|
22
|
+
read = hk.desk_read("BTCUSDT", side="long", entry=81000, leverage=20)
|
|
23
|
+
print(read["verdict"], read["text"])
|
|
24
|
+
|
|
25
|
+
print(hk.symbols()["covered"])
|
|
26
|
+
|
|
27
|
+
for event in hk.watch("BTCUSDT", threshold_pct=1.5): # needs a key
|
|
28
|
+
print(event["reason"], event["text"])
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`desk_read` works without a key (delayed data). `watch` needs a key: a free key (delayed, one
|
|
32
|
+
stream) is returned as `api_key` when you sign up at https://hunterkiller.io/signup. Pass `key=`
|
|
33
|
+
or set `HK_API_KEY`. A paid key is live and gets five streams. Errors raise `HunterKillerError`
|
|
34
|
+
with `.status` and `.body`.
|
|
35
|
+
|
|
36
|
+
`watch` yields the `desk_read` payload plus `event`, `reason` (`connect`, `wall_in_range:above`,
|
|
37
|
+
`wall_in_range:below`, `pull_flip`) and `threshold_pct`. It reconnects on its own after the
|
|
38
|
+
30 minute cap. Descriptive data, not a forecast. Docs: https://hunterkiller.io/for-agents
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# hunterkiller (Python)
|
|
2
|
+
|
|
3
|
+
Client for the Hunter Killer Desk Read. Standard library only.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pip install hunterkiller
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import hunterkiller as hk
|
|
11
|
+
|
|
12
|
+
read = hk.desk_read("BTCUSDT", side="long", entry=81000, leverage=20)
|
|
13
|
+
print(read["verdict"], read["text"])
|
|
14
|
+
|
|
15
|
+
print(hk.symbols()["covered"])
|
|
16
|
+
|
|
17
|
+
for event in hk.watch("BTCUSDT", threshold_pct=1.5): # needs a key
|
|
18
|
+
print(event["reason"], event["text"])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`desk_read` works without a key (delayed data). `watch` needs a key: a free key (delayed, one
|
|
22
|
+
stream) is returned as `api_key` when you sign up at https://hunterkiller.io/signup. Pass `key=`
|
|
23
|
+
or set `HK_API_KEY`. A paid key is live and gets five streams. Errors raise `HunterKillerError`
|
|
24
|
+
with `.status` and `.body`.
|
|
25
|
+
|
|
26
|
+
`watch` yields the `desk_read` payload plus `event`, `reason` (`connect`, `wall_in_range:above`,
|
|
27
|
+
`wall_in_range:below`, `pull_flip`) and `threshold_pct`. It reconnects on its own after the
|
|
28
|
+
30 minute cap. Descriptive data, not a forecast. Docs: https://hunterkiller.io/for-agents
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""hunterkiller: Python client for the Hunter Killer Desk Read. Standard library only.
|
|
2
|
+
|
|
3
|
+
import hunterkiller as hk
|
|
4
|
+
read = hk.desk_read("BTCUSDT", side="long", entry=81000, leverage=20, key="hk_live_...")
|
|
5
|
+
print(read["verdict"], read["text"])
|
|
6
|
+
|
|
7
|
+
for event in hk.watch("BTCUSDT", threshold_pct=1.5, key="hk_live_..."):
|
|
8
|
+
print(event["reason"], event["text"])
|
|
9
|
+
|
|
10
|
+
A key is optional for desk_read (keyless calls get the delayed tier) and required for watch.
|
|
11
|
+
Get a free key (delayed data) at https://hunterkiller.io/signup. The key can also come from the
|
|
12
|
+
HK_API_KEY environment variable. Descriptive data, not a forecast."""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import time
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.parse
|
|
20
|
+
import urllib.request
|
|
21
|
+
|
|
22
|
+
__all__ = ["desk_read", "watch", "symbols", "HunterKillerError", "BASE_URL"]
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
BASE_URL = os.environ.get("HK_BASE_URL", "https://hunterkiller.io")
|
|
26
|
+
_UA = "hunterkiller-python/" + __version__
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class HunterKillerError(Exception):
|
|
30
|
+
"""An HTTP error from the API. .status is the code, .body the parsed JSON error body."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, status, body):
|
|
33
|
+
self.status, self.body = status, body
|
|
34
|
+
msg = body.get("message") or body.get("error") if isinstance(body, dict) else None
|
|
35
|
+
super().__init__(f"HTTP {status}: {msg or body}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _headers(key):
|
|
39
|
+
key = key or os.environ.get("HK_API_KEY")
|
|
40
|
+
h = {"User-Agent": _UA, "Accept": "application/json"}
|
|
41
|
+
if key:
|
|
42
|
+
h["Authorization"] = "Bearer " + key
|
|
43
|
+
return h
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _open(url, headers, timeout):
|
|
47
|
+
req = urllib.request.Request(url, headers=headers)
|
|
48
|
+
try:
|
|
49
|
+
return urllib.request.urlopen(req, timeout=timeout)
|
|
50
|
+
except urllib.error.HTTPError as e:
|
|
51
|
+
raw = e.read().decode("utf-8", "replace")
|
|
52
|
+
try:
|
|
53
|
+
body = json.loads(raw)
|
|
54
|
+
except ValueError:
|
|
55
|
+
body = {"error": raw[:200]}
|
|
56
|
+
raise HunterKillerError(e.code, body) from None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_json(path, params, key, base_url, timeout=15):
|
|
60
|
+
q = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None})
|
|
61
|
+
with _open(f"{base_url or BASE_URL}{path}" + (f"?{q}" if q else ""), _headers(key), timeout) as r:
|
|
62
|
+
return json.load(r)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def desk_read(symbol, side=None, entry=None, leverage=None, key=None, base_url=None):
|
|
66
|
+
"""The Desk Read for one perp pair: pull, hurt, cascade, crowd, plan, verdict, lines, text.
|
|
67
|
+
side, entry and leverage together add where your liquidation sits and a verdict."""
|
|
68
|
+
return _get_json("/api/desk_read", {"symbol": symbol, "side": side, "entry": entry,
|
|
69
|
+
"leverage": leverage, "ref": "hunterkiller-python"},
|
|
70
|
+
key, base_url)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def symbols(base_url=None):
|
|
74
|
+
"""Covered symbols per tier and the free-tier delay."""
|
|
75
|
+
return _get_json("/api/desk_read/symbols", {}, None, base_url)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _sse(lines):
|
|
79
|
+
"""Yield (event, data) from an iterable of decoded SSE lines. Comments are skipped."""
|
|
80
|
+
event, data = "message", []
|
|
81
|
+
for raw in lines:
|
|
82
|
+
line = raw.rstrip("\r\n")
|
|
83
|
+
if line == "":
|
|
84
|
+
if data:
|
|
85
|
+
yield event, "\n".join(data)
|
|
86
|
+
event, data = "message", []
|
|
87
|
+
elif line.startswith(":"):
|
|
88
|
+
continue
|
|
89
|
+
elif line.startswith("event:"):
|
|
90
|
+
event = line[6:].strip()
|
|
91
|
+
elif line.startswith("data:"):
|
|
92
|
+
data.append(line[5:].lstrip())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def watch(symbol, threshold_pct=1.5, key=None, base_url=None, reconnect=True, max_events=None):
|
|
96
|
+
"""Generator of desk_read events: one on connect, then whenever the nearest wall on either
|
|
97
|
+
side comes within threshold_pct of price or the pull direction flips. Reconnects on the
|
|
98
|
+
server's 30-minute reconnect event and on dropped connections (back off up to 30 s).
|
|
99
|
+
Auth and limit errors raise HunterKillerError (a keyless call is 401). max_events stops
|
|
100
|
+
the generator after that many events (handy in tests)."""
|
|
101
|
+
q = urllib.parse.urlencode({"symbol": symbol, "threshold_pct": threshold_pct,
|
|
102
|
+
"ref": "hunterkiller-python"})
|
|
103
|
+
url = f"{base_url or BASE_URL}/api/desk_read/watch?{q}"
|
|
104
|
+
headers = dict(_headers(key), Accept="text/event-stream")
|
|
105
|
+
sent, delay = 0, 1.0
|
|
106
|
+
while True:
|
|
107
|
+
try:
|
|
108
|
+
with _open(url, headers, 90) as resp:
|
|
109
|
+
delay = 1.0
|
|
110
|
+
for event, data in _sse(line.decode("utf-8", "replace") for line in resp):
|
|
111
|
+
if event == "desk_read":
|
|
112
|
+
yield json.loads(data)
|
|
113
|
+
sent += 1
|
|
114
|
+
if max_events and sent >= max_events:
|
|
115
|
+
return
|
|
116
|
+
elif event == "reconnect":
|
|
117
|
+
break
|
|
118
|
+
except HunterKillerError as e:
|
|
119
|
+
if e.status != 429 or not reconnect:
|
|
120
|
+
raise
|
|
121
|
+
except OSError:
|
|
122
|
+
if not reconnect:
|
|
123
|
+
return
|
|
124
|
+
if not reconnect:
|
|
125
|
+
return
|
|
126
|
+
time.sleep(delay)
|
|
127
|
+
delay = min(delay * 2, 30.0)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hunterkiller
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hunter Killer Desk Read client: check where liquidation money is stacked before a leveraged trade, and watch for walls coming into range.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://hunterkiller.io/for-agents
|
|
7
|
+
Keywords: crypto,liquidations,perpetuals,trading,agents,mcp
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# hunterkiller (Python)
|
|
12
|
+
|
|
13
|
+
Client for the Hunter Killer Desk Read. Standard library only.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
pip install hunterkiller
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import hunterkiller as hk
|
|
21
|
+
|
|
22
|
+
read = hk.desk_read("BTCUSDT", side="long", entry=81000, leverage=20)
|
|
23
|
+
print(read["verdict"], read["text"])
|
|
24
|
+
|
|
25
|
+
print(hk.symbols()["covered"])
|
|
26
|
+
|
|
27
|
+
for event in hk.watch("BTCUSDT", threshold_pct=1.5): # needs a key
|
|
28
|
+
print(event["reason"], event["text"])
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`desk_read` works without a key (delayed data). `watch` needs a key: a free key (delayed, one
|
|
32
|
+
stream) is returned as `api_key` when you sign up at https://hunterkiller.io/signup. Pass `key=`
|
|
33
|
+
or set `HK_API_KEY`. A paid key is live and gets five streams. Errors raise `HunterKillerError`
|
|
34
|
+
with `.status` and `.body`.
|
|
35
|
+
|
|
36
|
+
`watch` yields the `desk_read` payload plus `event`, `reason` (`connect`, `wall_in_range:above`,
|
|
37
|
+
`wall_in_range:below`, `pull_flip`) and `threshold_pct`. It reconnects on its own after the
|
|
38
|
+
30 minute cap. Descriptive data, not a forecast. Docs: https://hunterkiller.io/for-agents
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hunterkiller
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "hunterkiller"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Hunter Killer Desk Read client: check where liquidation money is stacked before a leveraged trade, and watch for walls coming into range."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = []
|
|
13
|
+
keywords = ["crypto", "liquidations", "perpetuals", "trading", "agents", "mcp"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://hunterkiller.io/for-agents"
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
packages = ["hunterkiller"]
|