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.
@@ -0,0 +1,4 @@
1
+ __pycache__/
2
+ dist/
3
+ *.egg-info/
4
+ .env
@@ -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.
@@ -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