perp-cli 0.12.15 → 0.14.0
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.
- package/README.md +278 -31
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +74 -84
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +157 -682
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +14 -0
- package/dist/landing.js +36 -0
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +338 -134
- package/skills/perp-cli/references/agent-operations.md +181 -92
- package/skills/perp-cli/references/commands.md +475 -132
- package/skills/perp-cli/references/strategies.md +44 -38
- package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
- package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +33 -16
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public market data for the legacy venues without a native `getVenueMarkets`
|
|
3
|
+
* (pacifica, hyperliquid core, aster) — design §10.6. Lighter uses
|
|
4
|
+
* exchanges/lighter-public.ts.
|
|
5
|
+
*
|
|
6
|
+
* Network-aware: the REST base comes from `legacyPublicBaseUrl(venue, net)`,
|
|
7
|
+
* never from the mutable `*_API_URL` globals. A network without a known public
|
|
8
|
+
* API throws NOT_IMPLEMENTED.
|
|
9
|
+
*
|
|
10
|
+
* FUNDING BASIS / SETTLEMENT
|
|
11
|
+
* pacifica, hyperliquid: the venue definition (fundingBasisOf / settlementIntervalOf).
|
|
12
|
+
* aster: per symbol from GET /fapi/v3/fundingInfo `fundingIntervalHours`
|
|
13
|
+
* (live 2026-09-30: 1h/2h/4h/8h mixed). One quoted rate covers one interval,
|
|
14
|
+
* so basis hours == settlement hours. A symbol missing from fundingInfo is
|
|
15
|
+
* excluded with a warning (its period would be a guess).
|
|
16
|
+
*
|
|
17
|
+
* ASSET CLASS
|
|
18
|
+
* hyperliquid core: crypto (HIP-3 dexes are not served here).
|
|
19
|
+
* pacifica: crypto-centric; PACIFICA_NON_CRYPTO lists every non-crypto perp
|
|
20
|
+
* listed on 2026-09-30.
|
|
21
|
+
* aster: exchangeInfo `underlyingSubType` (STOCK / ETF / Commodities / pre-launch).
|
|
22
|
+
* AMBIGUOUS tickers are excluded with a warning so they cannot produce a false
|
|
23
|
+
* cross-venue match (SPX = the SPX6900 token, whose ticker equals the S&P 500
|
|
24
|
+
* canonical key).
|
|
25
|
+
*
|
|
26
|
+
* Fields a venue does not publish on this path are null (HL has no fixed tick
|
|
27
|
+
* size; Aster leverage brackets need a signed call; Aster OI is per-symbol only).
|
|
28
|
+
*/
|
|
29
|
+
import { HL_MIN_ORDER_VALUE_USD } from "../constants.js";
|
|
30
|
+
import { PerpError } from "../errors.js";
|
|
31
|
+
import { venueRateString } from "../utils/numeric.js";
|
|
32
|
+
import { assertOk } from "../api/public/_http.js";
|
|
33
|
+
import { legacyPublicBaseUrl } from "../api/public/urls.js";
|
|
34
|
+
import { canonicalFor } from "../exchanges/canonical-symbols.js";
|
|
35
|
+
import { fundingBasisOf, settlementIntervalOf } from "../exchanges/venue.js";
|
|
36
|
+
// ── Classification ──
|
|
37
|
+
const AMBIGUOUS = new Set(["SPX"]);
|
|
38
|
+
/** Pacifica non-crypto perps (live /info 2026-09-30). BP / CHIP: ticker collides with a listed equity/ETF. */
|
|
39
|
+
const PACIFICA_NON_CRYPTO = {
|
|
40
|
+
NVDA: "equity", TSLA: "equity", GOOGL: "equity", PLTR: "equity", HOOD: "equity", CRCL: "equity",
|
|
41
|
+
SPCX: "equity", SKHYNIX: "equity", SAMSUNG: "equity", MSTR: "equity", MU: "equity", SNDK: "equity",
|
|
42
|
+
META: "equity",
|
|
43
|
+
DRAM: "etf", URNM: "etf",
|
|
44
|
+
XAU: "commodity", XAG: "commodity", CL: "commodity", COPPER: "commodity", NATGAS: "commodity",
|
|
45
|
+
PLATINUM: "commodity",
|
|
46
|
+
USDJPY: "fx", EURUSD: "fx",
|
|
47
|
+
SP500: "index",
|
|
48
|
+
};
|
|
49
|
+
const PACIFICA_AMBIGUOUS = new Set(["BP", "CHIP"]);
|
|
50
|
+
function classifyPacifica(symbol) {
|
|
51
|
+
const up = symbol.toUpperCase();
|
|
52
|
+
if (AMBIGUOUS.has(up) || PACIFICA_AMBIGUOUS.has(up))
|
|
53
|
+
return null;
|
|
54
|
+
return PACIFICA_NON_CRYPTO[up] ?? "crypto"; // crypto-centric venue: non-crypto is listed explicitly
|
|
55
|
+
}
|
|
56
|
+
function classifyHyperliquid(symbol) {
|
|
57
|
+
return AMBIGUOUS.has(symbol.toUpperCase()) ? null : "crypto";
|
|
58
|
+
}
|
|
59
|
+
function classifyAster(symbol, subTypes) {
|
|
60
|
+
if (AMBIGUOUS.has(symbol.toUpperCase()))
|
|
61
|
+
return null;
|
|
62
|
+
if (subTypes.includes("pre-launch"))
|
|
63
|
+
return "preIpo";
|
|
64
|
+
if (subTypes.includes("ETF"))
|
|
65
|
+
return "etf";
|
|
66
|
+
if (subTypes.includes("STOCK"))
|
|
67
|
+
return "equity";
|
|
68
|
+
if (subTypes.includes("Commodities"))
|
|
69
|
+
return "commodity";
|
|
70
|
+
return "crypto";
|
|
71
|
+
}
|
|
72
|
+
function warnExcluded(venue, what, symbols) {
|
|
73
|
+
if (symbols.length > 0) {
|
|
74
|
+
console.warn(`[${venue}] excluded ${symbols.length} ${what}: ${symbols.join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ── HTTP ──
|
|
78
|
+
async function getJson(venue, url, label, init) {
|
|
79
|
+
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15_000) });
|
|
80
|
+
await assertOk(res, `${venue} ${label}`);
|
|
81
|
+
const text = await res.text();
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(text);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: invalid JSON`, { body: text.slice(0, 500) });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function arrayField(venue, label, v) {
|
|
90
|
+
if (!Array.isArray(v))
|
|
91
|
+
throw new PerpError("EXCHANGE_ERROR", `${venue} ${label}: expected an array`);
|
|
92
|
+
return v;
|
|
93
|
+
}
|
|
94
|
+
function numStr(v) {
|
|
95
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
96
|
+
return String(v);
|
|
97
|
+
if (typeof v === "string" && v.length > 0 && Number.isFinite(Number(v)))
|
|
98
|
+
return v;
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/** A funding rate field: a JSON number without float noise (venueRateString), a numeric string verbatim; else null. */
|
|
102
|
+
function rateStr(v) {
|
|
103
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
104
|
+
return venueRateString(v);
|
|
105
|
+
return numStr(v);
|
|
106
|
+
}
|
|
107
|
+
function positiveNumStr(v) {
|
|
108
|
+
const s = numStr(v);
|
|
109
|
+
return s !== null && Number(s) > 0 ? s : null;
|
|
110
|
+
}
|
|
111
|
+
function stepFromDecimals(d) {
|
|
112
|
+
return d === 0 ? "1" : `0.${"0".repeat(d - 1)}1`;
|
|
113
|
+
}
|
|
114
|
+
// ── Pacifica ──
|
|
115
|
+
async function pacificaRows(net) {
|
|
116
|
+
const base = legacyPublicBaseUrl("pacifica", net);
|
|
117
|
+
const [infoJson, pricesJson] = await Promise.all([
|
|
118
|
+
getJson("pacifica", `${base}/info`, "info"),
|
|
119
|
+
getJson("pacifica", `${base}/info/prices`, "info/prices"),
|
|
120
|
+
]);
|
|
121
|
+
const info = arrayField("pacifica", "info", infoJson.data);
|
|
122
|
+
const prices = arrayField("pacifica", "info/prices", pricesJson.data);
|
|
123
|
+
const priceBySymbol = new Map(prices.map(p => [String(p.symbol), p]));
|
|
124
|
+
const basis = fundingBasisOf("pacifica");
|
|
125
|
+
const settlement = settlementIntervalOf("pacifica");
|
|
126
|
+
const out = [];
|
|
127
|
+
const unclassified = [];
|
|
128
|
+
const unpriced = [];
|
|
129
|
+
for (const m of info) {
|
|
130
|
+
// /info lists spot markets too (e.g. SOL-USDC). A row without the field cannot be classified.
|
|
131
|
+
if (typeof m.instrument_type !== "string") {
|
|
132
|
+
throw new PerpError("EXCHANGE_ERROR", `Pacifica /info row ${String(m.symbol)} has no instrument_type`, { exchange: "pacifica" });
|
|
133
|
+
}
|
|
134
|
+
if (m.instrument_type === "spot")
|
|
135
|
+
continue;
|
|
136
|
+
const symbol = String(m.symbol ?? "");
|
|
137
|
+
const assetClass = classifyPacifica(symbol);
|
|
138
|
+
if (assetClass === null) {
|
|
139
|
+
unclassified.push(symbol);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const p = priceBySymbol.get(symbol);
|
|
143
|
+
const mark = p ? positiveNumStr(p.mark) : null;
|
|
144
|
+
if (!p || mark === null) {
|
|
145
|
+
unpriced.push(symbol);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const maxLeverage = Number(m.max_leverage);
|
|
149
|
+
out.push({
|
|
150
|
+
market: {
|
|
151
|
+
venue: "pacifica",
|
|
152
|
+
venueSymbol: symbol,
|
|
153
|
+
symbol,
|
|
154
|
+
canonical: canonicalFor("pacifica", symbol, assetClass),
|
|
155
|
+
assetClass,
|
|
156
|
+
settle: "USDC",
|
|
157
|
+
quoteAsset: null,
|
|
158
|
+
markPrice: mark,
|
|
159
|
+
indexPrice: positiveNumStr(p.oracle),
|
|
160
|
+
fundingRate: rateStr(p.next_funding),
|
|
161
|
+
fundingRateBasis: basis,
|
|
162
|
+
settlementIntervalHours: settlement,
|
|
163
|
+
openInterest: numStr(p.open_interest),
|
|
164
|
+
volume24h: numStr(p.volume_24h),
|
|
165
|
+
tickSize: positiveNumStr(m.tick_size),
|
|
166
|
+
stepSize: positiveNumStr(m.lot_size),
|
|
167
|
+
minSize: null,
|
|
168
|
+
minNotional: positiveNumStr(m.min_order_size),
|
|
169
|
+
maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
|
|
170
|
+
marginTiers: null,
|
|
171
|
+
session: null,
|
|
172
|
+
tradable: true,
|
|
173
|
+
},
|
|
174
|
+
nextFundingTime: null, // Pacifica publishes no next-funding timestamp
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
warnExcluded("pacifica", "unclassified/ambiguous market(s)", unclassified);
|
|
178
|
+
warnExcluded("pacifica", "market(s) without a mark price", unpriced);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
// ── Hyperliquid core ──
|
|
182
|
+
async function hyperliquidRows(net) {
|
|
183
|
+
const base = legacyPublicBaseUrl("hyperliquid", net);
|
|
184
|
+
const json = await getJson("hyperliquid", `${base}/info`, "info metaAndAssetCtxs", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({ type: "metaAndAssetCtxs" }),
|
|
188
|
+
});
|
|
189
|
+
if (!Array.isArray(json) || json.length < 2) {
|
|
190
|
+
throw new PerpError("EXCHANGE_ERROR", "hyperliquid metaAndAssetCtxs: expected [meta, assetCtxs]");
|
|
191
|
+
}
|
|
192
|
+
const universe = arrayField("hyperliquid", "meta.universe", json[0].universe);
|
|
193
|
+
const ctxs = arrayField("hyperliquid", "assetCtxs", json[1]);
|
|
194
|
+
if (ctxs.length !== universe.length) {
|
|
195
|
+
throw new PerpError("EXCHANGE_ERROR", `hyperliquid metaAndAssetCtxs: ${universe.length} assets vs ${ctxs.length} contexts`);
|
|
196
|
+
}
|
|
197
|
+
const basis = fundingBasisOf("hyperliquid");
|
|
198
|
+
const settlement = settlementIntervalOf("hyperliquid");
|
|
199
|
+
const out = [];
|
|
200
|
+
const unclassified = [];
|
|
201
|
+
const unpriced = [];
|
|
202
|
+
universe.forEach((a, i) => {
|
|
203
|
+
const ctx = ctxs[i];
|
|
204
|
+
const symbol = String(a.name ?? "");
|
|
205
|
+
const assetClass = classifyHyperliquid(symbol);
|
|
206
|
+
if (assetClass === null) {
|
|
207
|
+
unclassified.push(symbol);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const mark = positiveNumStr(ctx.markPx);
|
|
211
|
+
if (mark === null) {
|
|
212
|
+
unpriced.push(symbol);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const szDecimals = Number(a.szDecimals);
|
|
216
|
+
const maxLeverage = Number(a.maxLeverage);
|
|
217
|
+
out.push({
|
|
218
|
+
market: {
|
|
219
|
+
venue: "hyperliquid",
|
|
220
|
+
venueSymbol: symbol,
|
|
221
|
+
symbol,
|
|
222
|
+
canonical: canonicalFor("hyperliquid", symbol, assetClass),
|
|
223
|
+
assetClass,
|
|
224
|
+
settle: "USDC",
|
|
225
|
+
quoteAsset: null,
|
|
226
|
+
markPrice: mark,
|
|
227
|
+
indexPrice: positiveNumStr(ctx.oraclePx),
|
|
228
|
+
fundingRate: rateStr(ctx.funding),
|
|
229
|
+
fundingRateBasis: basis,
|
|
230
|
+
settlementIntervalHours: settlement,
|
|
231
|
+
openInterest: numStr(ctx.openInterest),
|
|
232
|
+
volume24h: numStr(ctx.dayNtlVlm),
|
|
233
|
+
tickSize: null, // HL prices use a significant-figure rule, not a fixed tick
|
|
234
|
+
stepSize: Number.isInteger(szDecimals) && szDecimals >= 0 ? stepFromDecimals(szDecimals) : null,
|
|
235
|
+
minSize: null,
|
|
236
|
+
minNotional: HL_MIN_ORDER_VALUE_USD,
|
|
237
|
+
maxLeverage: Number.isFinite(maxLeverage) && maxLeverage > 0 ? maxLeverage : null,
|
|
238
|
+
marginTiers: null,
|
|
239
|
+
session: null,
|
|
240
|
+
tradable: a.isDelisted !== true,
|
|
241
|
+
},
|
|
242
|
+
nextFundingTime: null, // not published in metaAndAssetCtxs
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
warnExcluded("hyperliquid", "unclassified/ambiguous market(s)", unclassified);
|
|
246
|
+
warnExcluded("hyperliquid", "market(s) without a mark price", unpriced);
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
// ── Aster ──
|
|
250
|
+
function asterFilter(filters, type, field) {
|
|
251
|
+
if (!Array.isArray(filters))
|
|
252
|
+
return null;
|
|
253
|
+
const f = filters.find(x => x.filterType === type);
|
|
254
|
+
return f ? positiveNumStr(f[field]) : null;
|
|
255
|
+
}
|
|
256
|
+
async function asterRows(net) {
|
|
257
|
+
const base = legacyPublicBaseUrl("aster", net);
|
|
258
|
+
const [infoJson, premiumJson, tickerJson, fundingInfoJson] = await Promise.all([
|
|
259
|
+
getJson("aster", `${base}/fapi/v1/exchangeInfo`, "exchangeInfo"),
|
|
260
|
+
getJson("aster", `${base}/fapi/v1/premiumIndex`, "premiumIndex"),
|
|
261
|
+
getJson("aster", `${base}/fapi/v1/ticker/24hr`, "ticker/24hr"),
|
|
262
|
+
getJson("aster", `${base}/fapi/v3/fundingInfo`, "fundingInfo"),
|
|
263
|
+
]);
|
|
264
|
+
const symbols = arrayField("aster", "exchangeInfo.symbols", infoJson.symbols);
|
|
265
|
+
const premiumBySymbol = new Map(arrayField("aster", "premiumIndex", premiumJson).map(p => [String(p.symbol), p]));
|
|
266
|
+
const tickerBySymbol = new Map(arrayField("aster", "ticker/24hr", tickerJson).map(t => [String(t.symbol), t]));
|
|
267
|
+
const intervalBySymbol = new Map();
|
|
268
|
+
for (const f of arrayField("aster", "fundingInfo", fundingInfoJson)) {
|
|
269
|
+
const h = Number(f.fundingIntervalHours);
|
|
270
|
+
if (Number.isFinite(h) && h > 0)
|
|
271
|
+
intervalBySymbol.set(String(f.symbol), h);
|
|
272
|
+
}
|
|
273
|
+
const out = [];
|
|
274
|
+
const unclassified = [];
|
|
275
|
+
const unpriced = [];
|
|
276
|
+
const noInterval = [];
|
|
277
|
+
for (const s of symbols) {
|
|
278
|
+
if (s.contractType !== "PERPETUAL" || s.quoteAsset !== "USDT")
|
|
279
|
+
continue;
|
|
280
|
+
const venueSymbol = String(s.symbol);
|
|
281
|
+
const symbol = String(s.baseAsset);
|
|
282
|
+
const subTypes = Array.isArray(s.underlyingSubType) ? s.underlyingSubType.map(String) : [];
|
|
283
|
+
const assetClass = classifyAster(symbol, subTypes);
|
|
284
|
+
if (assetClass === null) {
|
|
285
|
+
unclassified.push(venueSymbol);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const p = premiumBySymbol.get(venueSymbol);
|
|
289
|
+
const mark = p ? positiveNumStr(p.markPrice) : null;
|
|
290
|
+
if (!p || mark === null) {
|
|
291
|
+
unpriced.push(venueSymbol);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const hours = intervalBySymbol.get(venueSymbol);
|
|
295
|
+
if (hours === undefined) {
|
|
296
|
+
noInterval.push(venueSymbol);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (typeof s.marginAsset !== "string" || s.marginAsset.length === 0) {
|
|
300
|
+
throw new PerpError("EXCHANGE_ERROR", `aster exchangeInfo: ${venueSymbol} has no marginAsset`);
|
|
301
|
+
}
|
|
302
|
+
const next = Number(p.nextFundingTime);
|
|
303
|
+
const t = tickerBySymbol.get(venueSymbol);
|
|
304
|
+
out.push({
|
|
305
|
+
market: {
|
|
306
|
+
venue: "aster",
|
|
307
|
+
venueSymbol,
|
|
308
|
+
symbol,
|
|
309
|
+
canonical: canonicalFor("aster", symbol, assetClass),
|
|
310
|
+
assetClass,
|
|
311
|
+
settle: s.marginAsset,
|
|
312
|
+
quoteAsset: s.quoteAsset,
|
|
313
|
+
markPrice: mark,
|
|
314
|
+
indexPrice: positiveNumStr(p.indexPrice),
|
|
315
|
+
fundingRate: rateStr(p.lastFundingRate),
|
|
316
|
+
fundingRateBasis: { kind: "perHours", hours },
|
|
317
|
+
settlementIntervalHours: hours,
|
|
318
|
+
openInterest: null, // per-symbol endpoint only
|
|
319
|
+
volume24h: t ? numStr(t.quoteVolume) : null,
|
|
320
|
+
tickSize: asterFilter(s.filters, "PRICE_FILTER", "tickSize"),
|
|
321
|
+
stepSize: asterFilter(s.filters, "LOT_SIZE", "stepSize"),
|
|
322
|
+
minSize: asterFilter(s.filters, "LOT_SIZE", "minQty"),
|
|
323
|
+
minNotional: asterFilter(s.filters, "MIN_NOTIONAL", "notional"),
|
|
324
|
+
maxLeverage: null, // leverage brackets require a signed call
|
|
325
|
+
marginTiers: null,
|
|
326
|
+
session: null,
|
|
327
|
+
tradable: s.status === "TRADING",
|
|
328
|
+
},
|
|
329
|
+
nextFundingTime: Number.isFinite(next) && next > 0 ? next : null,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
warnExcluded("aster", "unclassified/ambiguous market(s)", unclassified);
|
|
333
|
+
warnExcluded("aster", "market(s) without a mark price", unpriced);
|
|
334
|
+
warnExcluded("aster", "market(s) missing from fundingInfo", noInterval);
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
// ── VenuePublicData ──
|
|
338
|
+
function rowsFor(venue, net) {
|
|
339
|
+
switch (venue) {
|
|
340
|
+
case "pacifica": return pacificaRows(net);
|
|
341
|
+
case "hyperliquid": return hyperliquidRows(net);
|
|
342
|
+
case "aster": return asterRows(net);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
export function legacyPublicData(venue) {
|
|
346
|
+
return {
|
|
347
|
+
async fetchMarkets(net) {
|
|
348
|
+
return (await rowsFor(venue, net)).map(r => r.market);
|
|
349
|
+
},
|
|
350
|
+
async fetchFundingRates(net) {
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const { market: m, nextFundingTime } of await rowsFor(venue, net)) {
|
|
353
|
+
if (!m.tradable || m.fundingRate === null)
|
|
354
|
+
continue;
|
|
355
|
+
out.push({
|
|
356
|
+
venue: m.venue,
|
|
357
|
+
venueSymbol: m.venueSymbol,
|
|
358
|
+
symbol: m.symbol,
|
|
359
|
+
canonical: m.canonical,
|
|
360
|
+
rate: m.fundingRate,
|
|
361
|
+
basis: m.fundingRateBasis,
|
|
362
|
+
markPrice: m.markPrice,
|
|
363
|
+
nextFundingTime,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
}
|
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Funding rate normalization.
|
|
2
|
+
* Funding rate normalization (design §10.1, §10.13 #1).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - Lighter: per 8 HOURS (API returns 8h rate, settles every 1h)
|
|
4
|
+
* The number of hours one quoted rate covers (the funding BASIS) comes from the
|
|
5
|
+
* venue definition — `VenueDefinition.fundingRateBasis`, read through
|
|
6
|
+
* `fundingBasisOf(ref)`. Settlement timing is a separate concept (arb/utils.ts).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* A venue ref is a venue id/alias, or "hl:<dex>" for Hyperliquid HIP-3 dexes
|
|
9
|
+
* (those resolve to the Hyperliquid core basis — current behavior preserved).
|
|
10
|
+
* Unknown refs throw UNKNOWN_VENUE; per-market venues
|
|
11
|
+
* have no single hours value — use the market's basis with the `*ByBasis`
|
|
12
|
+
* variants below.
|
|
13
|
+
*/
|
|
14
|
+
import { type FundingRateBasis } from "../exchanges/venue-types.js";
|
|
15
|
+
/**
|
|
16
|
+
* Funding basis of a venue ref. Unknown refs (e.g. a stale value in persisted
|
|
17
|
+
* arb/bot state) surface as UNKNOWN_VENUE with remediation instead of an
|
|
18
|
+
* anonymous crash deep inside a strategy loop.
|
|
13
19
|
*/
|
|
20
|
+
export declare function venueFundingBasis(ref: string): FundingRateBasis;
|
|
14
21
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* HL and PAC are 1h, Lighter API returns 8h rates.
|
|
22
|
+
* Hours one quoted funding rate covers for a venue ref.
|
|
23
|
+
* Throws INVALID_PARAMS for per-market or annualized venues — use the market's basis.
|
|
18
24
|
*/
|
|
19
|
-
export declare function getFundingHours(
|
|
20
|
-
/** Convert a raw
|
|
21
|
-
export declare function
|
|
25
|
+
export declare function getFundingHours(ref: string): number;
|
|
26
|
+
/** Convert a raw quoted rate to a per-hour rate using an explicit (market) basis. */
|
|
27
|
+
export declare function toHourlyRateByBasis(rate: number, basis: FundingRateBasis): number;
|
|
28
|
+
/** Convert a raw funding rate to per-hour rate using the venue's basis. */
|
|
29
|
+
export declare function toHourlyRate(rate: number, ref: string): number;
|
|
22
30
|
/** Annualize a raw rate from a specific exchange */
|
|
23
31
|
export declare function annualizeRate(rate: number, exchange: string): number;
|
|
32
|
+
/** Annualize a raw rate quoted on an explicit (market) basis */
|
|
33
|
+
export declare function annualizeRateByBasis(rate: number, basis: FundingRateBasis): number;
|
|
24
34
|
/** Annualize an already-normalized hourly rate (no exchange conversion needed) */
|
|
25
35
|
export declare function annualizeHourlyRate(hourlyRate: number): number;
|
|
26
36
|
/**
|
|
@@ -39,9 +49,11 @@ export declare function computeSignedAnnualSpread(shortRate: number, shortExchan
|
|
|
39
49
|
/**
|
|
40
50
|
* Estimate hourly funding payment for a position.
|
|
41
51
|
* @param rate - raw funding rate from the exchange
|
|
42
|
-
* @param exchange -
|
|
52
|
+
* @param exchange - venue ref
|
|
43
53
|
* @param positionUsd - position notional in USD
|
|
44
54
|
* @param side - "long" or "short" (longs pay positive rate, shorts receive)
|
|
45
55
|
* @returns hourly payment in USD (positive = you pay, negative = you receive)
|
|
46
56
|
*/
|
|
47
57
|
export declare function estimateHourlyFunding(rate: number, exchange: string, positionUsd: number, side: "long" | "short"): number;
|
|
58
|
+
/** Same as estimateHourlyFunding, from an already-normalized hourly rate. */
|
|
59
|
+
export declare function estimateHourlyFundingFromHourly(hourlyRate: number, positionUsd: number, side: "long" | "short"): number;
|
|
@@ -1,42 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Funding rate normalization.
|
|
2
|
+
* Funding rate normalization (design §10.1, §10.13 #1).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - Lighter: per 8 HOURS (API returns 8h rate, settles every 1h)
|
|
4
|
+
* The number of hours one quoted rate covers (the funding BASIS) comes from the
|
|
5
|
+
* venue definition — `VenueDefinition.fundingRateBasis`, read through
|
|
6
|
+
* `fundingBasisOf(ref)`. Settlement timing is a separate concept (arb/utils.ts).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* A venue ref is a venue id/alias, or "hl:<dex>" for Hyperliquid HIP-3 dexes
|
|
9
|
+
* (those resolve to the Hyperliquid core basis — current behavior preserved).
|
|
10
|
+
* Unknown refs throw UNKNOWN_VENUE; per-market venues
|
|
11
|
+
* have no single hours value — use the market's basis with the `*ByBasis`
|
|
12
|
+
* variants below.
|
|
13
13
|
*/
|
|
14
|
+
import { PerpError } from "../errors.js";
|
|
15
|
+
import { fundingBasisOf, parseVenueRef } from "../exchanges/venue.js";
|
|
16
|
+
import { rateToHourly } from "../exchanges/venue-types.js";
|
|
17
|
+
import { ensureVenuesRegistered } from "../exchanges/venues/index.js";
|
|
14
18
|
/** Funding periods per year by convention */
|
|
15
19
|
const HOURLY_PERIODS = 24 * 365; // 8760
|
|
16
|
-
/** How many hours each exchange's rate covers */
|
|
17
|
-
const EXCHANGE_FUNDING_HOURS = {
|
|
18
|
-
hyperliquid: 1,
|
|
19
|
-
pacifica: 1,
|
|
20
|
-
lighter: 8,
|
|
21
|
-
aster: 8,
|
|
22
|
-
};
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Funding basis of a venue ref. Unknown refs (e.g. a stale value in persisted
|
|
22
|
+
* arb/bot state) surface as UNKNOWN_VENUE with remediation instead of an
|
|
23
|
+
* anonymous crash deep inside a strategy loop.
|
|
24
|
+
*/
|
|
25
|
+
export function venueFundingBasis(ref) {
|
|
26
|
+
ensureVenuesRegistered();
|
|
27
|
+
try {
|
|
28
|
+
parseVenueRef(ref);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err instanceof PerpError && err.structured.code === "UNKNOWN_VENUE") {
|
|
32
|
+
throw new PerpError("UNKNOWN_VENUE", `Cannot normalize funding for unknown venue reference "${ref}"`, {
|
|
33
|
+
remediation: "Use a registered venue id (or hl:<dex> for HIP-3). If the value came from persisted state "
|
|
34
|
+
+ "(~/.perp/arb-state.json or a bot state file), fix or remove that entry.",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
return fundingBasisOf(ref);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Hours one quoted funding rate covers for a venue ref.
|
|
43
|
+
* Throws INVALID_PARAMS for per-market or annualized venues — use the market's basis.
|
|
27
44
|
*/
|
|
28
|
-
export function getFundingHours(
|
|
29
|
-
|
|
45
|
+
export function getFundingHours(ref) {
|
|
46
|
+
const basis = venueFundingBasis(ref);
|
|
47
|
+
if (basis.kind !== "perHours") {
|
|
48
|
+
throw new PerpError("INVALID_PARAMS", `Funding basis of "${ref}" is annualized, not a number of hours`, {
|
|
49
|
+
remediation: "Use the market's fundingRateBasis with toHourlyRateByBasis()",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return basis.hours;
|
|
30
53
|
}
|
|
31
|
-
/** Convert a raw
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
/** Convert a raw quoted rate to a per-hour rate using an explicit (market) basis. */
|
|
55
|
+
export function toHourlyRateByBasis(rate, basis) {
|
|
56
|
+
return rateToHourly(rate, basis);
|
|
57
|
+
}
|
|
58
|
+
/** Convert a raw funding rate to per-hour rate using the venue's basis. */
|
|
59
|
+
export function toHourlyRate(rate, ref) {
|
|
60
|
+
return rateToHourly(rate, venueFundingBasis(ref));
|
|
35
61
|
}
|
|
36
62
|
/** Annualize a raw rate from a specific exchange */
|
|
37
63
|
export function annualizeRate(rate, exchange) {
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
return annualizeHourlyRate(toHourlyRate(rate, exchange));
|
|
65
|
+
}
|
|
66
|
+
/** Annualize a raw rate quoted on an explicit (market) basis */
|
|
67
|
+
export function annualizeRateByBasis(rate, basis) {
|
|
68
|
+
return annualizeHourlyRate(toHourlyRateByBasis(rate, basis));
|
|
40
69
|
}
|
|
41
70
|
/** Annualize an already-normalized hourly rate (no exchange conversion needed) */
|
|
42
71
|
export function annualizeHourlyRate(hourlyRate) {
|
|
@@ -66,13 +95,16 @@ export function computeSignedAnnualSpread(shortRate, shortExchange, longRate, lo
|
|
|
66
95
|
/**
|
|
67
96
|
* Estimate hourly funding payment for a position.
|
|
68
97
|
* @param rate - raw funding rate from the exchange
|
|
69
|
-
* @param exchange -
|
|
98
|
+
* @param exchange - venue ref
|
|
70
99
|
* @param positionUsd - position notional in USD
|
|
71
100
|
* @param side - "long" or "short" (longs pay positive rate, shorts receive)
|
|
72
101
|
* @returns hourly payment in USD (positive = you pay, negative = you receive)
|
|
73
102
|
*/
|
|
74
103
|
export function estimateHourlyFunding(rate, exchange, positionUsd, side) {
|
|
75
|
-
|
|
104
|
+
return estimateHourlyFundingFromHourly(toHourlyRate(rate, exchange), positionUsd, side);
|
|
105
|
+
}
|
|
106
|
+
/** Same as estimateHourlyFunding, from an already-normalized hourly rate. */
|
|
107
|
+
export function estimateHourlyFundingFromHourly(hourlyRate, positionUsd, side) {
|
|
76
108
|
// Long pays positive funding, short receives positive funding
|
|
77
109
|
const multiplier = side === "long" ? 1 : -1;
|
|
78
110
|
return hourlyRate * positionUsd * multiplier;
|
package/dist/funding/rates.d.ts
CHANGED
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Real-time
|
|
2
|
+
* Real-time cross-venue funding rate comparison.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Fans out over every venue with the "marketData" role (design §10.6) using
|
|
5
|
+
* each venue's `publicData.fetchFundingRates(net)`, normalizes every row to a
|
|
6
|
+
* per-hour rate with the row's own basis (Lighter
|
|
7
|
+
* family 8h, Aster per-symbol), and matches markets across venues by
|
|
8
|
+
* `canonical` (null → excluded, §10.9).
|
|
9
|
+
*
|
|
10
|
+
* A venue that fails is reported in `exchangeStatus` ("error") and
|
|
11
|
+
* `exchangeErrors` (message) — its rows are absent, never silently empty.
|
|
7
12
|
*/
|
|
8
13
|
import { type HistoricalAverages } from "./history.js";
|
|
14
|
+
import { type VenueDefinition } from "../exchanges/venue.js";
|
|
15
|
+
import { type FundingRateBasis, type Network, type VenueId } from "../exchanges/venue-types.js";
|
|
9
16
|
export interface ExchangeFundingRate {
|
|
10
|
-
exchange:
|
|
17
|
+
exchange: VenueId;
|
|
18
|
+
/** Venue display symbol (e.g. "kPEPE", "BTC"). */
|
|
11
19
|
symbol: string;
|
|
20
|
+
/** Cross-venue matching key (canonical-symbols.ts). */
|
|
21
|
+
canonical: string;
|
|
12
22
|
fundingRate: number;
|
|
23
|
+
basis: FundingRateBasis;
|
|
13
24
|
hourlyRate: number;
|
|
14
25
|
annualizedPct: number;
|
|
15
26
|
markPrice: number;
|
|
16
|
-
nextFundingTime
|
|
17
|
-
historicalAvg
|
|
27
|
+
nextFundingTime: number | null;
|
|
28
|
+
historicalAvg: HistoricalAverages | null;
|
|
29
|
+
/** false = the venue's funding rate is unverified (VenueDefinition.fundingVerified) — shown only on explicit request. */
|
|
30
|
+
fundingVerified: boolean;
|
|
18
31
|
}
|
|
19
32
|
export interface SymbolFundingComparison {
|
|
33
|
+
/** Canonical symbol shared by every row. */
|
|
20
34
|
symbol: string;
|
|
21
35
|
rates: ExchangeFundingRate[];
|
|
22
36
|
maxSpreadAnnual: number;
|
|
@@ -29,20 +43,38 @@ export interface FundingRateSnapshot {
|
|
|
29
43
|
timestamp: string;
|
|
30
44
|
symbols: SymbolFundingComparison[];
|
|
31
45
|
exchangeStatus: Record<string, "ok" | "error">;
|
|
46
|
+
/** Error message per venue whose status is "error" (§10.13 #3). */
|
|
47
|
+
exchangeErrors: Record<string, string>;
|
|
32
48
|
}
|
|
33
49
|
export declare const TOP_SYMBOLS: string[];
|
|
34
50
|
/**
|
|
35
|
-
* Fetch
|
|
51
|
+
* Fetch every venue in `venues` concurrently; failures land in status/errors.
|
|
52
|
+
* Venues whose funding is unverified (VenueDefinition.fundingVerified false — QFEX until its
|
|
53
|
+
* rate unit is confirmed) are DROPPED unless `includeUnverified`: every consumer that ranks,
|
|
54
|
+
* alerts on or trades funding spreads (arb scan, MCP, alerts, dashboard, portfolio) leaves it
|
|
55
|
+
* false. Only explicit, labelled reads pass true (`arb scan --include-unverified`).
|
|
56
|
+
*/
|
|
57
|
+
export declare function fetchVenuesRates(venues: VenueDefinition[], net: Network, includeUnverified: boolean): Promise<{
|
|
58
|
+
rates: ExchangeFundingRate[];
|
|
59
|
+
exchangeStatus: Record<string, "ok" | "error">;
|
|
60
|
+
exchangeErrors: Record<string, string>;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Fetch funding rates from every market-data venue in parallel, normalize, and compare.
|
|
36
64
|
* Returns rates sorted by max spread (descending).
|
|
65
|
+
* @param opts.network defaults to the network the CLI selected (setSharedApiNetwork)
|
|
37
66
|
*/
|
|
38
67
|
export declare function fetchAllFundingRates(opts?: {
|
|
39
68
|
symbols?: string[];
|
|
40
69
|
minSpread?: number;
|
|
70
|
+
network?: Network;
|
|
71
|
+
/** Also fetch venues whose funding rate is unverified (default false — see fetchVenuesRates). */
|
|
72
|
+
includeUnverified?: boolean;
|
|
41
73
|
}): Promise<FundingRateSnapshot>;
|
|
42
74
|
/**
|
|
43
|
-
* Fetch rates for a single symbol across all exchanges.
|
|
75
|
+
* Fetch rates for a single (canonical) symbol across all exchanges.
|
|
44
76
|
*/
|
|
45
|
-
export declare function fetchSymbolFundingRates(symbol: string): Promise<SymbolFundingComparison | null>;
|
|
77
|
+
export declare function fetchSymbolFundingRates(symbol: string, includeUnverified?: boolean): Promise<SymbolFundingComparison | null>;
|
|
46
78
|
export interface SpotPerpSpread {
|
|
47
79
|
symbol: string;
|
|
48
80
|
/** Perp exchange with the highest absolute funding rate */
|
|
@@ -65,7 +97,7 @@ export interface SpotPerpSpread {
|
|
|
65
97
|
/**
|
|
66
98
|
* Fetch spot+perp funding rate spreads.
|
|
67
99
|
* Since spot has 0 funding cost, the spread is simply |perp funding rate|.
|
|
68
|
-
* Only includes symbols available on at least one spot
|
|
100
|
+
* Only includes symbols available on at least one bot spot venue (def.spot, §12.4 #3).
|
|
69
101
|
*
|
|
70
102
|
* Key safety checks:
|
|
71
103
|
* - U-token mapping: UBTC→BTC, UETH→ETH, USOL→SOL, UFART→FARTCOIN
|
|
@@ -75,7 +107,10 @@ export interface SpotPerpSpread {
|
|
|
75
107
|
export declare function fetchSpotPerpSpreads(opts?: {
|
|
76
108
|
symbols?: string[];
|
|
77
109
|
minSpread?: number;
|
|
110
|
+
network?: Network;
|
|
78
111
|
}): Promise<{
|
|
79
112
|
timestamp: string;
|
|
80
113
|
spreads: SpotPerpSpread[];
|
|
114
|
+
exchangeStatus: Record<string, "ok" | "error">;
|
|
115
|
+
exchangeErrors: Record<string, string>;
|
|
81
116
|
}>;
|