vextorium 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.
- vextorium-0.1.0/.gitignore +4 -0
- vextorium-0.1.0/LICENSE +9 -0
- vextorium-0.1.0/PKG-INFO +70 -0
- vextorium-0.1.0/README.md +50 -0
- vextorium-0.1.0/pyproject.toml +22 -0
- vextorium-0.1.0/vextorium/__init__.py +103 -0
- vextorium-0.1.0/vextorium/freqtrade.py +67 -0
vextorium-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vextorium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
vextorium-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vextorium
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 550+ pay-per-call data services for trading bots and AI agents, paid automatically in USDC on Base via x402. No API keys.
|
|
5
|
+
Project-URL: Homepage, https://vextorium.com
|
|
6
|
+
Project-URL: Catalog, https://api.vextorium.com/catalog.json
|
|
7
|
+
Project-URL: Source, https://github.com/bru1noia/vextorium-python
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agents,base,crypto,freqtrade,funding-rate,liquidations,trading,usdc,x402
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Requires-Dist: eth-account>=0.10
|
|
15
|
+
Requires-Dist: requests>=2.28
|
|
16
|
+
Requires-Dist: x402[evm]>=2.25
|
|
17
|
+
Provides-Extra: freqtrade
|
|
18
|
+
Requires-Dist: pandas; extra == 'freqtrade'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# vextorium (Python)
|
|
22
|
+
|
|
23
|
+
550+ pay-per-call data services for trading bots and AI agents: perp funding, liquidation maps, Hyperliquid
|
|
24
|
+
markets and whales, market regime, prices, on-chain data on 30+ chains, DeFi, official statistics and more.
|
|
25
|
+
|
|
26
|
+
**No API keys, no sign-up.** A Base wallet with a little USDC is the account; each call is paid automatically
|
|
27
|
+
via [x402](https://x402.org) (typically $0.003–$0.01). Failed calls are not charged.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install "vextorium @ git+https://github.com/bru1noia/vextorium-python"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from vextorium import Vextorium
|
|
35
|
+
|
|
36
|
+
vx = Vextorium(private_key="0x...", budget_usd=1.0, max_price_usd=0.02) # use a separate small wallet
|
|
37
|
+
vx.call("perp-funding", currency="BTC") # funding, premium, open interest
|
|
38
|
+
vx.call("hyperliquid-liquidation-map", currency="ETH", brackets=10)
|
|
39
|
+
vx.call("market-regime") # risk-on / risk-off score
|
|
40
|
+
vx.services("funding") # free: search the catalog
|
|
41
|
+
print(vx.spent / 1e6, "USDC spent")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Safety: every call is capped by `max_price_usd`, the session by `budget_usd` (`BudgetExceeded` is raised
|
|
45
|
+
before paying), payments only go out on Base USDC, and one payment runs at a time per wallet.
|
|
46
|
+
|
|
47
|
+
## freqtrade
|
|
48
|
+
|
|
49
|
+
Add funding, nearest liquidations and market regime as columns (latest candle only, no look-ahead), cached so
|
|
50
|
+
a bot calls each service at most once per `refresh_minutes`:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from vextorium.freqtrade import VextoriumContext
|
|
54
|
+
|
|
55
|
+
class MyStrategy(IStrategy):
|
|
56
|
+
vx = VextoriumContext(budget_usd=2.0, refresh_minutes=60) # key from VEXTORIUM_KEY
|
|
57
|
+
|
|
58
|
+
def populate_indicators(self, dataframe, metadata):
|
|
59
|
+
dataframe = self.vx.add_columns(dataframe, metadata["pair"])
|
|
60
|
+
# vx_funding_annual_pct, vx_premium_pct, vx_open_interest_usd, vx_nearest_long_liq_pct,
|
|
61
|
+
# vx_nearest_short_liq_pct, vx_long_at_risk_usd, vx_short_at_risk_usd, vx_regime_score
|
|
62
|
+
return dataframe
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Cost example: 1 pair, refresh 60 min, 3 signals ≈ 24 × 0.019 = 0.46 USDC/day.
|
|
66
|
+
|
|
67
|
+
Full catalog: https://api.vextorium.com/catalog.json · OpenAPI: https://api.vextorium.com/openapi.json ·
|
|
68
|
+
MCP server: https://github.com/bru1noia/vextorium-mcp
|
|
69
|
+
|
|
70
|
+
License: MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# vextorium (Python)
|
|
2
|
+
|
|
3
|
+
550+ pay-per-call data services for trading bots and AI agents: perp funding, liquidation maps, Hyperliquid
|
|
4
|
+
markets and whales, market regime, prices, on-chain data on 30+ chains, DeFi, official statistics and more.
|
|
5
|
+
|
|
6
|
+
**No API keys, no sign-up.** A Base wallet with a little USDC is the account; each call is paid automatically
|
|
7
|
+
via [x402](https://x402.org) (typically $0.003–$0.01). Failed calls are not charged.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install "vextorium @ git+https://github.com/bru1noia/vextorium-python"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from vextorium import Vextorium
|
|
15
|
+
|
|
16
|
+
vx = Vextorium(private_key="0x...", budget_usd=1.0, max_price_usd=0.02) # use a separate small wallet
|
|
17
|
+
vx.call("perp-funding", currency="BTC") # funding, premium, open interest
|
|
18
|
+
vx.call("hyperliquid-liquidation-map", currency="ETH", brackets=10)
|
|
19
|
+
vx.call("market-regime") # risk-on / risk-off score
|
|
20
|
+
vx.services("funding") # free: search the catalog
|
|
21
|
+
print(vx.spent / 1e6, "USDC spent")
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Safety: every call is capped by `max_price_usd`, the session by `budget_usd` (`BudgetExceeded` is raised
|
|
25
|
+
before paying), payments only go out on Base USDC, and one payment runs at a time per wallet.
|
|
26
|
+
|
|
27
|
+
## freqtrade
|
|
28
|
+
|
|
29
|
+
Add funding, nearest liquidations and market regime as columns (latest candle only, no look-ahead), cached so
|
|
30
|
+
a bot calls each service at most once per `refresh_minutes`:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from vextorium.freqtrade import VextoriumContext
|
|
34
|
+
|
|
35
|
+
class MyStrategy(IStrategy):
|
|
36
|
+
vx = VextoriumContext(budget_usd=2.0, refresh_minutes=60) # key from VEXTORIUM_KEY
|
|
37
|
+
|
|
38
|
+
def populate_indicators(self, dataframe, metadata):
|
|
39
|
+
dataframe = self.vx.add_columns(dataframe, metadata["pair"])
|
|
40
|
+
# vx_funding_annual_pct, vx_premium_pct, vx_open_interest_usd, vx_nearest_long_liq_pct,
|
|
41
|
+
# vx_nearest_short_liq_pct, vx_long_at_risk_usd, vx_short_at_risk_usd, vx_regime_score
|
|
42
|
+
return dataframe
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Cost example: 1 pair, refresh 60 min, 3 signals ≈ 24 × 0.019 = 0.46 USDC/day.
|
|
46
|
+
|
|
47
|
+
Full catalog: https://api.vextorium.com/catalog.json · OpenAPI: https://api.vextorium.com/openapi.json ·
|
|
48
|
+
MCP server: https://github.com/bru1noia/vextorium-mcp
|
|
49
|
+
|
|
50
|
+
License: MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vextorium"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "550+ pay-per-call data services for trading bots and AI agents, paid automatically in USDC on Base via x402. No API keys."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = ["x402[evm]>=2.25", "requests>=2.28", "eth-account>=0.10"]
|
|
13
|
+
keywords = ["x402", "usdc", "base", "trading", "freqtrade", "crypto", "funding-rate", "liquidations", "ai-agents"]
|
|
14
|
+
classifiers = ["Programming Language :: Python :: 3", "Topic :: Office/Business :: Financial :: Investment"]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
freqtrade = ["pandas"]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://vextorium.com"
|
|
21
|
+
Catalog = "https://api.vextorium.com/catalog.json"
|
|
22
|
+
Source = "https://github.com/bru1noia/vextorium-python"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Vextorium: 550+ pay-per-call data services for bots and AI agents, paid automatically in USDC on Base (x402).
|
|
2
|
+
|
|
3
|
+
No API keys, no sign-up: a Base wallet with a little USDC is the account. Every call is capped (max_price) and
|
|
4
|
+
there is a spend limit per session (budget), so a bot left running can never spend more than you allow.
|
|
5
|
+
|
|
6
|
+
from vextorium import Vextorium
|
|
7
|
+
vx = Vextorium(private_key=os.environ["VEXTORIUM_KEY"], budget_usd=1.0)
|
|
8
|
+
vx.call("perp-funding", symbol="BTC")
|
|
9
|
+
vx.services(query="funding") # free: search the catalog
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import threading
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.0"
|
|
19
|
+
__all__ = ["Vextorium", "VextoriumError", "BudgetExceeded"]
|
|
20
|
+
|
|
21
|
+
API = "https://api.vextorium.com"
|
|
22
|
+
BASE = "eip155:8453"
|
|
23
|
+
USDC_BASE = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class VextoriumError(Exception):
|
|
27
|
+
"""The service answered with an error (failed calls are not charged)."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BudgetExceeded(VextoriumError):
|
|
31
|
+
"""The call would go over the spend limit you set."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Vextorium:
|
|
35
|
+
def __init__(self, private_key: str | None = None, budget_usd: float = 1.0, max_price_usd: float = 0.02,
|
|
36
|
+
api: str = API, timeout: float = 60):
|
|
37
|
+
from eth_account import Account
|
|
38
|
+
from x402 import x402ClientSync
|
|
39
|
+
from x402.client_base import max_amount, prefer_network
|
|
40
|
+
from x402.http.clients import x402_requests
|
|
41
|
+
from x402.mechanisms.evm.exact import register_exact_evm_client
|
|
42
|
+
from x402.mechanisms.evm.signers import EthAccountSigner
|
|
43
|
+
|
|
44
|
+
key = private_key or os.environ.get("VEXTORIUM_KEY")
|
|
45
|
+
if not key:
|
|
46
|
+
raise ValueError("private_key (or VEXTORIUM_KEY): a Base wallet with USDC; use a separate small wallet")
|
|
47
|
+
self.account = Account.from_key(key)
|
|
48
|
+
self.api, self.timeout = api.rstrip("/"), timeout
|
|
49
|
+
self.budget = int(round(budget_usd * 1e6))
|
|
50
|
+
self.max_price = int(round(max_price_usd * 1e6))
|
|
51
|
+
self.spent = 0 # USDC atomic units actually charged in this session
|
|
52
|
+
self._lock = threading.Lock()
|
|
53
|
+
client = x402ClientSync()
|
|
54
|
+
register_exact_evm_client(client, EthAccountSigner(self.account), networks=[BASE],
|
|
55
|
+
policies=[prefer_network(BASE), max_amount(self.max_price)])
|
|
56
|
+
self._pay = x402_requests(client)
|
|
57
|
+
self._free = requests.Session()
|
|
58
|
+
|
|
59
|
+
# -- free -----------------------------------------------------------------
|
|
60
|
+
def services(self, query: str | None = None) -> list[dict]:
|
|
61
|
+
"""Catalog (free). Optional text filter on path/description."""
|
|
62
|
+
items = self._free.get(f"{self.api}/catalog.json", timeout=self.timeout).json()["services"]
|
|
63
|
+
if query:
|
|
64
|
+
q = query.lower()
|
|
65
|
+
items = [s for s in items if q in (str(s.get("endpoint", "")) + str(s.get("description", "")) + " ".join(s.get("tags") or [])).lower()]
|
|
66
|
+
return items
|
|
67
|
+
|
|
68
|
+
# -- paid -----------------------------------------------------------------
|
|
69
|
+
def call(self, service: str, **params):
|
|
70
|
+
"""Call one service, e.g. call("crypto-price", symbol="ETH"). Pays the listed price (≤ max_price)."""
|
|
71
|
+
path = service if service.startswith("/") else "/v1/" + service
|
|
72
|
+
with self._lock: # one payment at a time per wallet (nonces) and an exact budget
|
|
73
|
+
probe = self._free.post(self.api + path, json=params, timeout=self.timeout)
|
|
74
|
+
if probe.status_code != 402:
|
|
75
|
+
return self._result(probe)
|
|
76
|
+
price = self._price(probe)
|
|
77
|
+
if price is None:
|
|
78
|
+
raise VextoriumError(f"{path}: no USDC-on-Base offer within max_price")
|
|
79
|
+
if self.spent + price > self.budget:
|
|
80
|
+
raise BudgetExceeded(f"{path} costs {price / 1e6} USDC; spent {self.spent / 1e6} of {self.budget / 1e6}")
|
|
81
|
+
r = self._pay.post(self.api + path, json=params, timeout=self.timeout)
|
|
82
|
+
if r.ok and (r.headers.get("PAYMENT-RESPONSE") or r.headers.get("X-PAYMENT-RESPONSE")):
|
|
83
|
+
self.spent += price
|
|
84
|
+
return self._result(r)
|
|
85
|
+
|
|
86
|
+
def _price(self, r) -> int | None:
|
|
87
|
+
try:
|
|
88
|
+
offers = r.json().get("accepts") or []
|
|
89
|
+
except ValueError:
|
|
90
|
+
return None
|
|
91
|
+
ok = [int(o["amount"]) for o in offers if o.get("network") == BASE and str(o.get("asset", "")).lower() == USDC_BASE
|
|
92
|
+
and int(o.get("amount", 0)) <= self.max_price]
|
|
93
|
+
return min(ok) if ok else None
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _result(r):
|
|
97
|
+
try:
|
|
98
|
+
data = r.json()
|
|
99
|
+
except ValueError:
|
|
100
|
+
data = r.text
|
|
101
|
+
if not r.ok:
|
|
102
|
+
raise VextoriumError(f"HTTP {r.status_code}: {str(data)[:300]}")
|
|
103
|
+
return data
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""freqtrade helper: add Vextorium market context (funding, liquidations, market regime) to a strategy.
|
|
2
|
+
|
|
3
|
+
Calls are cached per candle, so a 5m bot on 1 pair makes at most 1 call per signal per candle
|
|
4
|
+
(e.g. funding: 288 calls/day x 0.009 = 2.6 USDC/day). Use refresh_minutes to call less often.
|
|
5
|
+
|
|
6
|
+
from vextorium.freqtrade import VextoriumContext
|
|
7
|
+
class MyStrategy(IStrategy):
|
|
8
|
+
vx = VextoriumContext(budget_usd=2.0, refresh_minutes=60)
|
|
9
|
+
def populate_indicators(self, dataframe, metadata):
|
|
10
|
+
return self.vx.add_columns(dataframe, metadata["pair"])
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
from . import Vextorium, VextoriumError
|
|
18
|
+
|
|
19
|
+
log = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class VextoriumContext:
|
|
23
|
+
def __init__(self, private_key: str | None = None, budget_usd: float = 1.0, refresh_minutes: float = 60,
|
|
24
|
+
signals=("funding", "liquidations", "regime"), client: Vextorium | None = None):
|
|
25
|
+
self.vx = client or Vextorium(private_key=private_key, budget_usd=budget_usd)
|
|
26
|
+
self.refresh = refresh_minutes * 60
|
|
27
|
+
self.signals = set(signals)
|
|
28
|
+
self._cache: dict[tuple, tuple[float, dict]] = {}
|
|
29
|
+
|
|
30
|
+
def _get(self, service: str, **params) -> dict | None:
|
|
31
|
+
key = (service, tuple(sorted(params.items())))
|
|
32
|
+
hit = self._cache.get(key)
|
|
33
|
+
if hit and time.time() - hit[0] < self.refresh:
|
|
34
|
+
return hit[1]
|
|
35
|
+
try:
|
|
36
|
+
data = self.vx.call(service, **params)
|
|
37
|
+
except VextoriumError as e: # failed calls are not charged; the bot keeps running without the column
|
|
38
|
+
log.warning("vextorium %s: %s", service, e)
|
|
39
|
+
return hit[1] if hit else None
|
|
40
|
+
self._cache[key] = (time.time(), data)
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
def context(self, pair: str) -> dict:
|
|
44
|
+
"""Latest values for a pair like 'BTC/USDT' or 'BTC/USDT:USDT'."""
|
|
45
|
+
coin = pair.split("/")[0].upper()
|
|
46
|
+
out: dict = {}
|
|
47
|
+
if "funding" in self.signals and (f := self._get("perp-funding", currency=coin)):
|
|
48
|
+
out["vx_funding_annual_pct"] = f.get("annual_funding_pct")
|
|
49
|
+
out["vx_premium_pct"] = f.get("premium_pct")
|
|
50
|
+
out["vx_open_interest_usd"] = f.get("open_interest_usd")
|
|
51
|
+
if "liquidations" in self.signals and (m := self._get("hyperliquid-liquidation-map", currency=coin, brackets=10)):
|
|
52
|
+
out["vx_nearest_long_liq_pct"] = ((m.get("nearest_long_liquidations") or [{}])[0]).get("distance_pct")
|
|
53
|
+
out["vx_nearest_short_liq_pct"] = ((m.get("nearest_short_liquidations") or [{}])[0]).get("distance_pct")
|
|
54
|
+
out["vx_long_at_risk_usd"] = m.get("long_notional_at_risk_usd")
|
|
55
|
+
out["vx_short_at_risk_usd"] = m.get("short_notional_at_risk_usd")
|
|
56
|
+
if "regime" in self.signals and (r := self._get("market-regime")):
|
|
57
|
+
out["vx_regime_score"] = r.get("score")
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
def add_columns(self, dataframe, pair: str):
|
|
61
|
+
"""Writes the latest values on the last row (live/dry-run); past rows stay NaN (no look-ahead)."""
|
|
62
|
+
for col, val in self.context(pair).items():
|
|
63
|
+
if col not in dataframe:
|
|
64
|
+
dataframe[col] = float("nan")
|
|
65
|
+
if val is not None and len(dataframe):
|
|
66
|
+
dataframe.loc[dataframe.index[-1], col] = val
|
|
67
|
+
return dataframe
|