perp-cli 0.14.3 → 0.16.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.ja.md +243 -0
- package/README.ko.md +243 -0
- package/README.md +138 -405
- package/README.zh.md +243 -0
- package/dist/agent-wallet/store.d.ts +3 -2
- package/dist/agent-wallet/store.js +2 -2
- package/dist/api/public/hyperliquid.js +4 -0
- package/dist/api/public/lighter.d.ts +5 -0
- package/dist/api/public/lighter.js +5 -0
- package/dist/api/public/pacifica.js +85 -15
- package/dist/api/public/urls.d.ts +2 -0
- package/dist/api/public/urls.js +3 -0
- package/dist/arb/sizing.js +4 -2
- package/dist/arb/state.d.ts +6 -0
- package/dist/arb/utils.d.ts +1 -2
- package/dist/arb/utils.js +1 -2
- package/dist/balance-by-address.d.ts +39 -0
- package/dist/balance-by-address.js +44 -0
- package/dist/bot/apex/pulse.js +3 -0
- package/dist/bot/apex/radar.js +2 -1
- package/dist/bot/engine.d.ts +10 -1
- package/dist/bot/engine.js +14 -6
- package/dist/bot/indicators.d.ts +7 -0
- package/dist/bot/indicators.js +16 -0
- package/dist/bot/perp-order.d.ts +4 -4
- package/dist/bot/perp-order.js +5 -5
- package/dist/bot/quarantine.d.ts +1 -1
- package/dist/bot/quarantine.js +1 -1
- package/dist/bot/quote-rules.js +1 -1
- package/dist/bot/strategies/apex-strategy.js +3 -2
- package/dist/bot/strategies/engine-mm.js +6 -5
- package/dist/bot/strategies/funding-arb-strategy.js +12 -5
- package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
- package/dist/bot/strategies/funding-arb-utils.js +53 -20
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
- package/dist/bot/strategies/liquidation-mm.js +8 -2
- package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
- package/dist/bot/strategies/momentum-breakout.js +11 -3
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
- package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
- package/dist/bot/strategies/spot-perp-executor.js +3 -3
- package/dist/bot/strategy-types.d.ts +2 -1
- package/dist/bot/tui/BotDashboard.d.ts +1 -1
- package/dist/bot/tui/BotDashboard.js +1 -1
- package/dist/cache.js +12 -6
- package/dist/cli-program.d.ts +15 -0
- package/dist/cli-program.js +126 -6
- package/dist/commands/account.js +168 -24
- package/dist/commands/agent-import.d.ts +7 -0
- package/dist/commands/agent-import.js +30 -1
- package/dist/commands/agent.d.ts +6 -3
- package/dist/commands/agent.js +160 -12
- package/dist/commands/arb/index.js +23 -81
- package/dist/commands/arb-auto.d.ts +24 -0
- package/dist/commands/arb-auto.js +175 -97
- package/dist/commands/arb.d.ts +20 -0
- package/dist/commands/arb.js +107 -163
- package/dist/commands/backtest.js +23 -21
- package/dist/commands/bot.js +1 -1
- package/dist/commands/builder.js +1 -1
- package/dist/commands/funds.js +113 -3
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +27 -7
- package/dist/commands/manage.js +23 -21
- package/dist/commands/market.d.ts +48 -0
- package/dist/commands/market.js +395 -100
- package/dist/commands/options.d.ts +14 -0
- package/dist/commands/options.js +151 -0
- package/dist/commands/pacifica-earn.d.ts +5 -0
- package/dist/commands/pacifica-earn.js +455 -0
- package/dist/commands/pacifica-orders.d.ts +14 -0
- package/dist/commands/pacifica-orders.js +142 -0
- package/dist/commands/portfolio.d.ts +16 -0
- package/dist/commands/portfolio.js +83 -55
- package/dist/commands/rebalance.d.ts +1 -0
- package/dist/commands/rebalance.js +15 -0
- package/dist/commands/risk.d.ts +11 -2
- package/dist/commands/risk.js +39 -44
- package/dist/commands/trade.d.ts +1 -0
- package/dist/commands/trade.js +67 -62
- package/dist/commands/wallet.js +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +15 -4
- package/dist/constants.js +6 -0
- package/dist/cross-chain-margin.d.ts +3 -1
- package/dist/cross-chain-margin.js +4 -2
- package/dist/dashboard/server.js +3 -3
- package/dist/dashboard/ui.js +9 -6
- package/dist/errors.d.ts +29 -1
- package/dist/errors.js +35 -3
- package/dist/exchanges/address.d.ts +8 -0
- package/dist/exchanges/address.js +47 -0
- package/dist/exchanges/arcus/adapter.d.ts +16 -12
- package/dist/exchanges/arcus/adapter.js +45 -18
- package/dist/exchanges/arcus/agent.d.ts +1 -1
- package/dist/exchanges/arcus/agent.js +1 -1
- package/dist/exchanges/arcus/client.js +1 -1
- package/dist/exchanges/arcus/public.js +3 -2
- package/dist/exchanges/arcus/signing.d.ts +2 -3
- package/dist/exchanges/arcus/signing.js +1 -1
- package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
- package/dist/exchanges/arcus/spot-chain.js +1 -1
- package/dist/exchanges/arcus/spot-router.d.ts +1 -1
- package/dist/exchanges/arcus/spot-router.js +2 -2
- package/dist/exchanges/arcus/spot.d.ts +2 -3
- package/dist/exchanges/arcus/spot.js +2 -3
- package/dist/exchanges/aster-spot.d.ts +3 -3
- package/dist/exchanges/aster-spot.js +6 -9
- package/dist/exchanges/aster-typed-data.d.ts +1 -1
- package/dist/exchanges/aster-typed-data.js +1 -1
- package/dist/exchanges/aster.d.ts +30 -10
- package/dist/exchanges/aster.js +153 -53
- package/dist/exchanges/builder-fee.d.ts +1 -1
- package/dist/exchanges/builder-fee.js +13 -2
- package/dist/exchanges/canonical-symbols.d.ts +8 -2
- package/dist/exchanges/canonical-symbols.js +46 -2
- package/dist/exchanges/capabilities.d.ts +2 -0
- package/dist/exchanges/capabilities.js +2 -1
- package/dist/exchanges/derive/client.d.ts +37 -0
- package/dist/exchanges/derive/client.js +135 -0
- package/dist/exchanges/derive/options.d.ts +73 -0
- package/dist/exchanges/derive/options.js +379 -0
- package/dist/exchanges/extended/adapter.d.ts +96 -0
- package/dist/exchanges/extended/adapter.js +241 -0
- package/dist/exchanges/extended/client.d.ts +38 -0
- package/dist/exchanges/extended/client.js +88 -0
- package/dist/exchanges/extended/public.d.ts +133 -0
- package/dist/exchanges/extended/public.js +436 -0
- package/dist/exchanges/grvt/adapter.d.ts +100 -0
- package/dist/exchanges/grvt/adapter.js +283 -0
- package/dist/exchanges/grvt/client.d.ts +25 -0
- package/dist/exchanges/grvt/client.js +86 -0
- package/dist/exchanges/grvt/public.d.ts +151 -0
- package/dist/exchanges/grvt/public.js +438 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
- package/dist/exchanges/hyperliquid-outcome.js +29 -1
- package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
- package/dist/exchanges/hyperliquid-spot.js +24 -16
- package/dist/exchanges/hyperliquid.d.ts +199 -22
- package/dist/exchanges/hyperliquid.js +550 -202
- package/dist/exchanges/index.d.ts +1 -1
- package/dist/exchanges/index.js +1 -1
- package/dist/exchanges/interface.d.ts +157 -13
- package/dist/exchanges/interface.js +47 -0
- package/dist/exchanges/ioc-outcome.d.ts +1 -1
- package/dist/exchanges/ioc-outcome.js +1 -1
- package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
- package/dist/exchanges/legacy-trading-v2.js +7 -2
- package/dist/exchanges/lighter-public.d.ts +67 -12
- package/dist/exchanges/lighter-public.js +250 -82
- package/dist/exchanges/lighter-signer.d.ts +1 -1
- package/dist/exchanges/lighter-signer.js +1 -1
- package/dist/exchanges/lighter-spot.d.ts +10 -5
- package/dist/exchanges/lighter-spot.js +105 -97
- package/dist/exchanges/lighter.d.ts +80 -21
- package/dist/exchanges/lighter.js +312 -105
- package/dist/exchanges/ondo/adapter.d.ts +38 -13
- package/dist/exchanges/ondo/adapter.js +86 -27
- package/dist/exchanges/ondo/agent.d.ts +1 -1
- package/dist/exchanges/ondo/agent.js +1 -1
- package/dist/exchanges/ondo/client.d.ts +1 -1
- package/dist/exchanges/ondo/client.js +2 -2
- package/dist/exchanges/ondo/public.d.ts +35 -3
- package/dist/exchanges/ondo/public.js +75 -32
- package/dist/exchanges/ondo/spot.d.ts +2 -2
- package/dist/exchanges/ondo/spot.js +3 -3
- package/dist/exchanges/options-interface.d.ts +158 -0
- package/dist/exchanges/options-interface.js +17 -0
- package/dist/exchanges/options-venues.d.ts +12 -0
- package/dist/exchanges/options-venues.js +35 -0
- package/dist/exchanges/outcome-interface.d.ts +4 -0
- package/dist/exchanges/pacifica-atomic.d.ts +211 -0
- package/dist/exchanges/pacifica-atomic.js +440 -0
- package/dist/exchanges/pacifica-decimal.d.ts +10 -0
- package/dist/exchanges/pacifica-decimal.js +51 -0
- package/dist/exchanges/pacifica-presigned.d.ts +53 -0
- package/dist/exchanges/pacifica-presigned.js +141 -0
- package/dist/exchanges/pacifica-print.d.ts +200 -0
- package/dist/exchanges/pacifica-print.js +417 -0
- package/dist/exchanges/pacifica-spot.d.ts +8 -2
- package/dist/exchanges/pacifica-spot.js +26 -18
- package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
- package/dist/exchanges/pacifica-typed-data.js +1 -1
- package/dist/exchanges/pacifica-vaults.d.ts +130 -0
- package/dist/exchanges/pacifica-vaults.js +277 -0
- package/dist/exchanges/pacifica.d.ts +108 -10
- package/dist/exchanges/pacifica.js +428 -171
- package/dist/exchanges/precision.d.ts +3 -1
- package/dist/exchanges/precision.js +9 -2
- package/dist/exchanges/public-balances.d.ts +25 -0
- package/dist/exchanges/public-balances.js +158 -0
- package/dist/exchanges/public-positions.d.ts +43 -0
- package/dist/exchanges/public-positions.js +154 -0
- package/dist/exchanges/qfex/adapter.d.ts +69 -13
- package/dist/exchanges/qfex/adapter.js +102 -17
- package/dist/exchanges/qfex/agent.d.ts +1 -1
- package/dist/exchanges/qfex/agent.js +1 -1
- package/dist/exchanges/qfex/client.d.ts +1 -1
- package/dist/exchanges/qfex/client.js +1 -1
- package/dist/exchanges/qfex/errors.d.ts +1 -1
- package/dist/exchanges/qfex/errors.js +2 -2
- package/dist/exchanges/qfex/public.d.ts +65 -12
- package/dist/exchanges/qfex/public.js +131 -25
- package/dist/exchanges/qfex/signing.d.ts +2 -2
- package/dist/exchanges/qfex/signing.js +4 -5
- package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
- package/dist/exchanges/qfex/ws-transport.js +3 -3
- package/dist/exchanges/rate-limiter.d.ts +1 -1
- package/dist/exchanges/rate-limiter.js +5 -2
- package/dist/exchanges/registry.d.ts +1 -1
- package/dist/exchanges/registry.js +1 -1
- package/dist/exchanges/risex/adapter.d.ts +160 -0
- package/dist/exchanges/risex/adapter.js +842 -0
- package/dist/exchanges/risex/agent.d.ts +154 -0
- package/dist/exchanges/risex/agent.js +384 -0
- package/dist/exchanges/risex/client.d.ts +47 -0
- package/dist/exchanges/risex/client.js +174 -0
- package/dist/exchanges/risex/nonce.d.ts +43 -0
- package/dist/exchanges/risex/nonce.js +212 -0
- package/dist/exchanges/risex/public.d.ts +64 -0
- package/dist/exchanges/risex/public.js +150 -0
- package/dist/exchanges/risex/session.d.ts +71 -0
- package/dist/exchanges/risex/session.js +171 -0
- package/dist/exchanges/risex/typed-data.d.ts +173 -0
- package/dist/exchanges/risex/typed-data.js +240 -0
- package/dist/exchanges/risex/units.d.ts +29 -0
- package/dist/exchanges/risex/units.js +104 -0
- package/dist/exchanges/slippage.d.ts +1 -1
- package/dist/exchanges/slippage.js +1 -1
- package/dist/exchanges/spot-factory.d.ts +3 -3
- package/dist/exchanges/spot-factory.js +8 -8
- package/dist/exchanges/spot-interface.d.ts +5 -5
- package/dist/exchanges/spot-interface.js +2 -2
- package/dist/exchanges/spot-result.d.ts +3 -3
- package/dist/exchanges/spot-result.js +3 -3
- package/dist/exchanges/trading-v2.d.ts +3 -3
- package/dist/exchanges/trading-v2.js +9 -6
- package/dist/exchanges/txflow/adapter.d.ts +61 -0
- package/dist/exchanges/txflow/adapter.js +150 -0
- package/dist/exchanges/txflow/client.d.ts +47 -0
- package/dist/exchanges/txflow/client.js +155 -0
- package/dist/exchanges/txflow/public.d.ts +87 -0
- package/dist/exchanges/txflow/public.js +355 -0
- package/dist/exchanges/unknown-symbol.d.ts +34 -0
- package/dist/exchanges/unknown-symbol.js +56 -0
- package/dist/exchanges/variational/adapter.d.ts +67 -0
- package/dist/exchanges/variational/adapter.js +137 -0
- package/dist/exchanges/variational/client.d.ts +21 -0
- package/dist/exchanges/variational/client.js +66 -0
- package/dist/exchanges/variational/public.d.ts +126 -0
- package/dist/exchanges/variational/public.js +430 -0
- package/dist/exchanges/venue-types.d.ts +52 -10
- package/dist/exchanges/venue-types.js +82 -11
- package/dist/exchanges/venue.d.ts +150 -11
- package/dist/exchanges/venue.js +126 -4
- package/dist/exchanges/venues/arcus.d.ts +1 -2
- package/dist/exchanges/venues/arcus.js +5 -4
- package/dist/exchanges/venues/aster.d.ts +2 -2
- package/dist/exchanges/venues/aster.js +6 -4
- package/dist/exchanges/venues/create-helpers.d.ts +1 -1
- package/dist/exchanges/venues/create-helpers.js +3 -3
- package/dist/exchanges/venues/extended.d.ts +21 -0
- package/dist/exchanges/venues/extended.js +96 -0
- package/dist/exchanges/venues/grvt.d.ts +30 -0
- package/dist/exchanges/venues/grvt.js +98 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
- package/dist/exchanges/venues/hyperliquid.js +19 -8
- package/dist/exchanges/venues/index.d.ts +7 -2
- package/dist/exchanges/venues/index.js +8 -2
- package/dist/exchanges/venues/lighter-family.d.ts +7 -9
- package/dist/exchanges/venues/lighter-family.js +9 -11
- package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
- package/dist/exchanges/venues/lighter-rh.js +20 -8
- package/dist/exchanges/venues/lighter.d.ts +1 -1
- package/dist/exchanges/venues/lighter.js +18 -6
- package/dist/exchanges/venues/ondo.d.ts +1 -1
- package/dist/exchanges/venues/ondo.js +5 -3
- package/dist/exchanges/venues/pacifica.d.ts +2 -2
- package/dist/exchanges/venues/pacifica.js +15 -4
- package/dist/exchanges/venues/qfex.d.ts +1 -1
- package/dist/exchanges/venues/qfex.js +4 -2
- package/dist/exchanges/venues/risex-release.d.ts +14 -0
- package/dist/exchanges/venues/risex-release.js +14 -0
- package/dist/exchanges/venues/risex.d.ts +31 -0
- package/dist/exchanges/venues/risex.js +131 -0
- package/dist/exchanges/venues/txflow.d.ts +18 -0
- package/dist/exchanges/venues/txflow.js +77 -0
- package/dist/exchanges/venues/variational.d.ts +21 -0
- package/dist/exchanges/venues/variational.js +85 -0
- package/dist/execution-log.d.ts +1 -1
- package/dist/funding/legacy-public.d.ts +6 -4
- package/dist/funding/legacy-public.js +42 -41
- package/dist/funding/normalize.d.ts +1 -1
- package/dist/funding/normalize.js +1 -1
- package/dist/funding/rates.d.ts +95 -6
- package/dist/funding/rates.js +99 -19
- package/dist/index.js +7 -6
- package/dist/jobs.d.ts +1 -0
- package/dist/jobs.js +25 -1
- package/dist/landing.d.ts +2 -0
- package/dist/landing.js +1 -1
- package/dist/liquidity.d.ts +3 -1
- package/dist/liquidity.js +8 -4
- package/dist/mcp-pacifica-earn.d.ts +9 -0
- package/dist/mcp-pacifica-earn.js +64 -0
- package/dist/mcp-server.js +211 -68
- package/dist/pacifica/client.d.ts +69 -5
- package/dist/pacifica/client.js +181 -8
- package/dist/pacifica/rate-limit.d.ts +144 -0
- package/dist/pacifica/rate-limit.js +267 -0
- package/dist/pacifica/types/order.d.ts +2 -1
- package/dist/plan-executor.js +7 -2
- package/dist/positions-by-address.d.ts +34 -0
- package/dist/positions-by-address.js +46 -0
- package/dist/rebalance.js +1 -1
- package/dist/risk.d.ts +2 -1
- package/dist/risk.js +2 -4
- package/dist/settings.d.ts +32 -3
- package/dist/signer/ows-evm.d.ts +1 -1
- package/dist/signer/ows-evm.js +1 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/smart-order.js +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +36 -63
- package/dist/trade-validator.js +2 -2
- package/dist/utils.d.ts +22 -3
- package/dist/utils.js +11 -3
- package/dist/venue-health.d.ts +40 -0
- package/dist/venue-health.js +75 -0
- package/dist/withdraw-gate.d.ts +1 -1
- package/dist/withdraw-gate.js +1 -1
- package/dist/ws/arcus-market.d.ts +9 -0
- package/dist/ws/arcus-market.js +9 -0
- package/dist/ws/aster-market.d.ts +33 -0
- package/dist/ws/aster-market.js +30 -0
- package/dist/ws/index.d.ts +980 -0
- package/dist/ws/index.js +4460 -0
- package/dist/ws/lighter-market.d.ts +14 -0
- package/dist/ws/lighter-market.js +13 -0
- package/dist/ws/node.d.ts +996 -0
- package/dist/ws/node.js +4554 -0
- package/dist/ws/pacifica-instrument.d.ts +8 -0
- package/dist/ws/pacifica-instrument.js +8 -0
- package/dist/ws/unclassified.d.ts +16 -0
- package/dist/ws/unclassified.js +43 -0
- package/dist/ws-cli/credentials.d.ts +62 -0
- package/dist/ws-cli/credentials.js +126 -0
- package/package.json +71 -10
- package/skills/perp-cli/SKILL.md +153 -36
- package/skills/perp-cli/package.json +15 -2
- package/skills/perp-cli/references/agent-operations.md +22 -12
- package/skills/perp-cli/references/commands.md +143 -17
- package/skills/perp-cli/references/strategies.md +38 -3
- package/skills/perp-cli/references/ws-library.md +523 -0
- package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
- package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
- package/skills/perp-cli/scripts/preflight.sh +27 -19
- package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
- package/skills/perp-cli/scripts/validate-arb.sh +56 -39
- package/dist/cli-spec.d.ts +0 -46
- package/dist/cli-spec.js +0 -81
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRVT public-only adapter — official Market Data API, read-only in this version (venues/grvt.ts).
|
|
3
|
+
*
|
|
4
|
+
* Market data (markets, orderbook, trades, funding history, klines) is implemented. Every account /
|
|
5
|
+
* trading method refuses with CAPABILITY_UNSUPPORTED before any request — there is no GRVT account,
|
|
6
|
+
* signing or order code. Unknown symbols are SYMBOL_NOT_FOUND from the instrument directory (GRVT
|
|
7
|
+
* answers an unknown instrument with HTTP 400 "malformed syntax", so it is never asked).
|
|
8
|
+
*/
|
|
9
|
+
import { PerpError } from "../../errors.js";
|
|
10
|
+
import { mulDecimal } from "../precision.js";
|
|
11
|
+
import { roleUnsupported } from "../venue.js";
|
|
12
|
+
import { grvtShapeError } from "./client.js";
|
|
13
|
+
import { fetchGrvtMarket, fetchGrvtMarkets, grvtClient, grvtDirectory, resolveGrvtMarket, } from "./public.js";
|
|
14
|
+
/** perp-cli interval → GRVT CandlestickInterval (api-docs.grvt.io/schemas/candlestick_interval). */
|
|
15
|
+
export const GRVT_KLINE_INTERVALS = {
|
|
16
|
+
"1m": "CI_1_M", "3m": "CI_3_M", "5m": "CI_5_M", "15m": "CI_15_M", "30m": "CI_30_M",
|
|
17
|
+
"1h": "CI_1_H", "2h": "CI_2_H", "4h": "CI_4_H", "6h": "CI_6_H", "8h": "CI_8_H", "12h": "CI_12_H",
|
|
18
|
+
"1d": "CI_1_D", "3d": "CI_3_D", "5d": "CI_5_D", "1w": "CI_1_W", "2w": "CI_2_W", "3w": "CI_3_W", "4w": "CI_4_W",
|
|
19
|
+
};
|
|
20
|
+
/** Fixed-length intervals (ms): `start_time` is floored to their boundary (epoch-aligned, UTC). */
|
|
21
|
+
const GRVT_KLINE_FIXED_MS = {
|
|
22
|
+
"1m": 60_000, "3m": 180_000, "5m": 300_000, "15m": 900_000, "30m": 1_800_000, "1h": 3_600_000, "2h": 7_200_000,
|
|
23
|
+
"4h": 14_400_000, "6h": 21_600_000, "8h": 28_800_000, "12h": 43_200_000, "1d": 86_400_000,
|
|
24
|
+
};
|
|
25
|
+
const GRVT_KLINE_NOMINAL_MS = {
|
|
26
|
+
...GRVT_KLINE_FIXED_MS, "3d": 259_200_000, "5d": 432_000_000, "1w": 604_800_000, "2w": 1_209_600_000, "3w": 1_814_400_000, "4w": 2_419_200_000,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Bars per kline page (the documented max) and the most pages one read may take. The `next`
|
|
30
|
+
* cursor runs dry after ~1000 bars (live 2026-10-19: page 2 had 2 rows and `next` ""), so pages
|
|
31
|
+
* walk `end_time` back to just before the oldest bar returned instead.
|
|
32
|
+
*/
|
|
33
|
+
export const GRVT_KLINE_PAGE = 1000;
|
|
34
|
+
/**
|
|
35
|
+
* Pages actually read before giving up. Real pages hold fewer than 1000 bars (minutes without a
|
|
36
|
+
* bar: 40 d of 1m took 58 pages for 57,583 bars), so the read budget is 25 % above the nominal
|
|
37
|
+
* GRVT_KLINE_MAX_PAGES. 75 requests stay well inside GRVT's 500 market-data requests / minute.
|
|
38
|
+
*/
|
|
39
|
+
export const GRVT_KLINE_READ_PAGES = 75;
|
|
40
|
+
/** Measured live 2026-10-19: /trade answers at most 1000 rows (limit 1500 → 1000, no cursor). */
|
|
41
|
+
export const GRVT_TRADES_CAP = 1000;
|
|
42
|
+
/** Measured live 2026-10-19: /funding honours limit 10,000 in one answer (BTC). */
|
|
43
|
+
export const GRVT_FUNDING_CAP = 10_000;
|
|
44
|
+
export const GRVT_KLINE_MAX_PAGES = 60;
|
|
45
|
+
const WARN = (msg) => { process.stderr.write(`${msg}\n`); };
|
|
46
|
+
/** Order book depths GRVT serves (api_orderbook_levels_request). */
|
|
47
|
+
export const GRVT_BOOK_DEPTHS = [10, 50, 100, 500];
|
|
48
|
+
const DEC = /^-?\d+(\.\d+)?$/;
|
|
49
|
+
function dec(v, what) {
|
|
50
|
+
if (typeof v !== "string" || !DEC.test(v))
|
|
51
|
+
throw grvtShapeError(`${what} is not a decimal string`);
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
function nsToMs(v, what) {
|
|
55
|
+
if (typeof v !== "string" || !/^\d+$/.test(v))
|
|
56
|
+
throw grvtShapeError(`${what} is not a nanosecond timestamp`);
|
|
57
|
+
return Number(BigInt(v) / 1000000n);
|
|
58
|
+
}
|
|
59
|
+
function msToNs(ms) {
|
|
60
|
+
return (BigInt(Math.floor(ms)) * 1000000n).toString();
|
|
61
|
+
}
|
|
62
|
+
export function toGrvtMarketInfo(m) {
|
|
63
|
+
return {
|
|
64
|
+
symbol: m.symbol,
|
|
65
|
+
markPrice: m.markPrice,
|
|
66
|
+
indexPrice: m.indexPrice,
|
|
67
|
+
fundingRate: m.fundingRate,
|
|
68
|
+
volume24h: m.volume24h,
|
|
69
|
+
openInterest: m.openInterest,
|
|
70
|
+
maxLeverage: m.maxLeverage,
|
|
71
|
+
sizeDecimals: null,
|
|
72
|
+
stepSize: m.stepSize,
|
|
73
|
+
fundingHours: m.settlementIntervalHours,
|
|
74
|
+
marginTiers: m.marginTiers,
|
|
75
|
+
venueSymbol: m.venueSymbol,
|
|
76
|
+
lastPrice: m.lastPrice,
|
|
77
|
+
bestBid: m.bestBid,
|
|
78
|
+
bestAsk: m.bestAsk,
|
|
79
|
+
tickSize: m.tickSize,
|
|
80
|
+
leverageAsOf: m.leverageAsOf,
|
|
81
|
+
minSize: m.minSize,
|
|
82
|
+
minNotional: m.minNotional,
|
|
83
|
+
nextFundingTime: m.nextFundingTime,
|
|
84
|
+
fundingRateKind: "predicted",
|
|
85
|
+
fundingVerified: false,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export class GrvtPublicAdapter {
|
|
89
|
+
venue;
|
|
90
|
+
network;
|
|
91
|
+
name = "grvt";
|
|
92
|
+
aliases = ["grv"];
|
|
93
|
+
client;
|
|
94
|
+
constructor(venue, network) {
|
|
95
|
+
this.venue = venue;
|
|
96
|
+
this.network = network;
|
|
97
|
+
this.client = grvtClient(network);
|
|
98
|
+
}
|
|
99
|
+
async market(symbol) {
|
|
100
|
+
return resolveGrvtMarket(await grvtDirectory(this.client, this.network, WARN), symbol);
|
|
101
|
+
}
|
|
102
|
+
// ── Market data ──
|
|
103
|
+
/** Every market with leverage (`market list`): margin_rules come from the 6 h file cache. */
|
|
104
|
+
async getVenueMarkets() {
|
|
105
|
+
return fetchGrvtMarkets(this.network, { leverage: true });
|
|
106
|
+
}
|
|
107
|
+
async getMarkets() {
|
|
108
|
+
return (await this.getVenueMarkets()).map(toGrvtMarketInfo);
|
|
109
|
+
}
|
|
110
|
+
/** One market (`market info`): the directory, its ticker and its margin rules — no fan-out. */
|
|
111
|
+
async getMarketInfo(symbol) {
|
|
112
|
+
return toGrvtMarketInfo(await fetchGrvtMarket(this.network, symbol));
|
|
113
|
+
}
|
|
114
|
+
async getOrderbook(symbol, depth = 50) {
|
|
115
|
+
const m = await this.market(symbol);
|
|
116
|
+
// The smallest served depth that covers the request; above 500 → INVALID_PARAMS (never clamped).
|
|
117
|
+
const d = GRVT_BOOK_DEPTHS.find(x => x >= depth);
|
|
118
|
+
if (d === undefined || !Number.isInteger(depth) || depth < 1) {
|
|
119
|
+
throw new PerpError("INVALID_PARAMS", `GRVT: book depth must be 1..500 (got ${depth})`, { exchange: "grvt", remediation: "Pass --depth 1..500" });
|
|
120
|
+
}
|
|
121
|
+
const res = await this.client.result("book", { instrument: m.instrument, depth: d });
|
|
122
|
+
if (res === null || typeof res !== "object" || !Array.isArray(res.bids) || !Array.isArray(res.asks)) {
|
|
123
|
+
throw grvtShapeError("book has no bids / asks arrays");
|
|
124
|
+
}
|
|
125
|
+
const side = (rows, what) => rows.map(r => {
|
|
126
|
+
const x = r;
|
|
127
|
+
return [dec(x.price, `${what} price`), dec(x.size, `${what} size`)];
|
|
128
|
+
});
|
|
129
|
+
// GRVT serves 10 / 50 / 100 / 500 levels: return exactly the `depth` requested.
|
|
130
|
+
return { bids: side(res.bids.slice(0, depth), "bid"), asks: side(res.asks.slice(0, depth), "ask") };
|
|
131
|
+
}
|
|
132
|
+
async getRecentTrades(symbol, limit = 50) {
|
|
133
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > GRVT_TRADES_CAP) {
|
|
134
|
+
throw new PerpError("INVALID_PARAMS", `GRVT: trades limit must be 1..${GRVT_TRADES_CAP} (got ${limit})`, {
|
|
135
|
+
remediation: `GRVT serves at most ${GRVT_TRADES_CAP} recent trades per request and no cursor for older ones`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const m = await this.market(symbol);
|
|
139
|
+
const res = await this.client.result("trade", { instrument: m.instrument, limit });
|
|
140
|
+
if (!Array.isArray(res))
|
|
141
|
+
throw grvtShapeError("trade result is not an array");
|
|
142
|
+
// The venue answers oldest first; every venue's tape is newest first.
|
|
143
|
+
return res.map(t => {
|
|
144
|
+
const x = t;
|
|
145
|
+
if (typeof x.is_taker_buyer !== "boolean")
|
|
146
|
+
throw grvtShapeError("trade without is_taker_buyer");
|
|
147
|
+
return {
|
|
148
|
+
time: nsToMs(x.event_time, "trade event_time"),
|
|
149
|
+
symbol: m.symbol,
|
|
150
|
+
side: x.is_taker_buyer ? "buy" : "sell",
|
|
151
|
+
price: dec(x.price, "trade price"),
|
|
152
|
+
size: dec(x.size, "trade size"),
|
|
153
|
+
fee: null,
|
|
154
|
+
};
|
|
155
|
+
}).sort((a, b) => b.time - a.time);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Settled funding (/full/v1/funding, one record per funding interval, newest first). `rate` is a
|
|
159
|
+
* fraction per the record's `intervalHours` (published in percentage points, converted exactly).
|
|
160
|
+
*/
|
|
161
|
+
async getFundingHistory(symbol, limit = 100) {
|
|
162
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > GRVT_FUNDING_CAP) {
|
|
163
|
+
throw new PerpError("INVALID_PARAMS", `GRVT: funding history limit must be 1..${GRVT_FUNDING_CAP} (got ${limit})`);
|
|
164
|
+
}
|
|
165
|
+
const m = await this.market(symbol);
|
|
166
|
+
// Sent as asked (it was silently clamped to 1000; the venue serves 10,000 in one answer).
|
|
167
|
+
const res = await this.client.result("funding", { instrument: m.instrument, limit });
|
|
168
|
+
if (!Array.isArray(res))
|
|
169
|
+
throw grvtShapeError("funding result is not an array");
|
|
170
|
+
return res.map(r => {
|
|
171
|
+
const x = r;
|
|
172
|
+
const fi = x.funding_interval_hours;
|
|
173
|
+
if (typeof fi !== "number" || !Number.isInteger(fi) || fi <= 0)
|
|
174
|
+
throw grvtShapeError("funding record without funding_interval_hours");
|
|
175
|
+
const mark = typeof x.mark_price === "string" && DEC.test(x.mark_price) && Number(x.mark_price) > 0 ? x.mark_price : null;
|
|
176
|
+
return {
|
|
177
|
+
time: nsToMs(x.funding_time, "funding_time"),
|
|
178
|
+
rate: mulDecimal(dec(x.funding_rate, "funding_rate"), "0.01"),
|
|
179
|
+
price: mark,
|
|
180
|
+
intervalHours: fi,
|
|
181
|
+
rateKind: "lastSettled",
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async getKlines(symbol, interval, startTime, endTime) {
|
|
186
|
+
const ci = GRVT_KLINE_INTERVALS[interval];
|
|
187
|
+
if (ci === undefined) {
|
|
188
|
+
throw new PerpError("INVALID_PARAMS", `GRVT: unsupported candle interval "${interval}"`, {
|
|
189
|
+
remediation: `Use one of: ${Object.keys(GRVT_KLINE_INTERVALS).join(", ")}`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (!(endTime > startTime))
|
|
193
|
+
throw new PerpError("INVALID_PARAMS", "GRVT: candle end time must be after the start time");
|
|
194
|
+
const fixed = GRVT_KLINE_FIXED_MS[interval];
|
|
195
|
+
// An off-grid start_time makes GRVT answer a truncated first bar labelled off the grid
|
|
196
|
+
// (live: 4h from 05:07 → a 06:00–08:00 bar), so the start is floored to the boundary.
|
|
197
|
+
const start = fixed === undefined ? startTime : Math.floor(startTime / fixed) * fixed;
|
|
198
|
+
const pagesNeeded = Math.ceil((endTime - start) / GRVT_KLINE_NOMINAL_MS[interval] / GRVT_KLINE_PAGE);
|
|
199
|
+
if (pagesNeeded > GRVT_KLINE_MAX_PAGES) {
|
|
200
|
+
throw new PerpError("INVALID_PARAMS", `GRVT: ${interval} candles over this range need ${pagesNeeded} requests (max ${GRVT_KLINE_MAX_PAGES})`, {
|
|
201
|
+
remediation: "Narrow --start / --end or use a longer interval",
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const m = await this.market(symbol);
|
|
205
|
+
// Reverse-chronological pages of up to 1000 bars; the next page ends just before the oldest
|
|
206
|
+
// bar returned. Done when a page is empty or reaches `start` — never on the cursor alone.
|
|
207
|
+
const byOpen = new Map();
|
|
208
|
+
let end = endTime;
|
|
209
|
+
let oldestSeen = Infinity;
|
|
210
|
+
let exhausted = false;
|
|
211
|
+
for (let page = 0; page < GRVT_KLINE_READ_PAGES; page++) {
|
|
212
|
+
const res = await this.client.post("kline", {
|
|
213
|
+
instrument: m.instrument, interval: ci, type: "TRADE", start_time: msToNs(start), end_time: msToNs(end), limit: GRVT_KLINE_PAGE,
|
|
214
|
+
});
|
|
215
|
+
if (res === null || typeof res !== "object" || !Array.isArray(res.result))
|
|
216
|
+
throw grvtShapeError("kline result is not an array");
|
|
217
|
+
// A fixed-interval bar whose first minute had no trade is labelled at its first trade (live
|
|
218
|
+
// 2026-10-19: the 09:30–10:00 30m bar opened "09:31"); its open is the bucket boundary, and a
|
|
219
|
+
// page asked to end before that label answers the same bar again — it is not an older bar.
|
|
220
|
+
const inPage = res.result
|
|
221
|
+
.map(c => ({ c, t: fixed === undefined ? nsToMs(c.open_time, "kline open_time") : Math.floor(nsToMs(c.open_time, "kline open_time") / fixed) * fixed }))
|
|
222
|
+
.filter(x => page === 0 || x.t < oldestSeen);
|
|
223
|
+
// No bar older than the last page (an empty page, or only bars already read): history starts here.
|
|
224
|
+
if (inPage.length === 0) {
|
|
225
|
+
exhausted = true;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
let oldest = Infinity;
|
|
229
|
+
for (const { c, t } of inPage) {
|
|
230
|
+
byOpen.set(String(t), { ...c, open_time: `${t}000000` });
|
|
231
|
+
if (t < oldest)
|
|
232
|
+
oldest = t;
|
|
233
|
+
}
|
|
234
|
+
oldestSeen = oldest;
|
|
235
|
+
if (oldest <= start)
|
|
236
|
+
break;
|
|
237
|
+
end = oldest - 1;
|
|
238
|
+
}
|
|
239
|
+
// Ran out of the read budget before reaching `start`: the oldest bars would be missing — never
|
|
240
|
+
// returned as the whole range.
|
|
241
|
+
if (!exhausted && oldestSeen > start) {
|
|
242
|
+
throw new PerpError("EXCHANGE_ERROR", `GRVT: ${interval} candles reached only ${new Date(oldestSeen).toISOString()} after ${GRVT_KLINE_READ_PAGES} pages (requested from ${new Date(start).toISOString()})`, {
|
|
243
|
+
exchange: "grvt", reachedMs: oldestSeen, requestedFromMs: start,
|
|
244
|
+
remediation: `Narrow --start / --end (or use a longer interval); fetch older bars with --end ${oldestSeen - 1}`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const rows = [...byOpen.values()];
|
|
248
|
+
const bars = rows.map(c => {
|
|
249
|
+
const x = c;
|
|
250
|
+
return {
|
|
251
|
+
time: nsToMs(x.open_time, "kline open_time"),
|
|
252
|
+
open: dec(x.open, "kline open"), high: dec(x.high, "kline high"), low: dec(x.low, "kline low"), close: dec(x.close, "kline close"),
|
|
253
|
+
volume: dec(x.volume_b, "kline volume_b"),
|
|
254
|
+
quoteVolume: typeof x.volume_q === "string" && DEC.test(x.volume_q) ? x.volume_q : null,
|
|
255
|
+
trades: typeof x.trades === "number" ? x.trades : null,
|
|
256
|
+
closeMs: nsToMs(x.close_time, "kline close_time"),
|
|
257
|
+
};
|
|
258
|
+
}).sort((a, b) => a.time - b.time);
|
|
259
|
+
// Calendar intervals (3d … 4w) are not floored: a first bar shorter than the interval is a
|
|
260
|
+
// truncated bar (live: the first 1w bar covered 4 days) and is left out. The newest bar may be
|
|
261
|
+
// in progress and is kept.
|
|
262
|
+
const nominal = GRVT_KLINE_NOMINAL_MS[interval];
|
|
263
|
+
const full = fixed === undefined && bars.length > 1 && bars[0].closeMs - bars[0].time + 1 < nominal * 0.99 ? bars.slice(1) : bars;
|
|
264
|
+
return full.map(({ closeMs: _c, ...k }) => k);
|
|
265
|
+
}
|
|
266
|
+
// ── Account / trading: refused (read-only in this version) ──
|
|
267
|
+
refuse(feature) {
|
|
268
|
+
throw roleUnsupported(this.venue, feature);
|
|
269
|
+
}
|
|
270
|
+
async getBalance() { return this.refuse("account"); }
|
|
271
|
+
async getPositions() { return this.refuse("account"); }
|
|
272
|
+
async getOpenOrders() { return this.refuse("account"); }
|
|
273
|
+
async getOrderHistory() { return this.refuse("account"); }
|
|
274
|
+
async getTradeHistory() { return this.refuse("account"); }
|
|
275
|
+
async getFundingPayments() { return this.refuse("account"); }
|
|
276
|
+
async marketOrder() { return this.refuse("trading"); }
|
|
277
|
+
async limitOrder() { return this.refuse("trading"); }
|
|
278
|
+
async editOrder() { return this.refuse("trading"); }
|
|
279
|
+
async cancelOrder() { return this.refuse("trading"); }
|
|
280
|
+
async cancelAllOrders() { return this.refuse("trading"); }
|
|
281
|
+
async setLeverage() { return this.refuse("trading"); }
|
|
282
|
+
async stopOrder() { return this.refuse("trading"); }
|
|
283
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRVT Market Data REST transport (official API, api-docs.grvt.io/market_data_api/).
|
|
3
|
+
*
|
|
4
|
+
* - `POST <rest>/full/v1/<endpoint>` with a JSON body; success is `{result: …}` (`margin_rules`
|
|
5
|
+
* answers its object unwrapped), an error is `{code, message, status}` with the HTTP status
|
|
6
|
+
* (1003/400 malformed — also what an unknown instrument gets —, 1004/404 not found, 1006/429
|
|
7
|
+
* rate limit, 1002/500 internal).
|
|
8
|
+
* - Rate limits (help.grvt.io): Ticker / MiniTicker / GetInstrument 1500 per minute, everything
|
|
9
|
+
* else 500 per minute — two token buckets per network (venues/grvt.ts).
|
|
10
|
+
* - No retries, no fallback: every failure is a PerpError.
|
|
11
|
+
*/
|
|
12
|
+
import { PerpError } from "../../errors.js";
|
|
13
|
+
import type { Network } from "../venue-types.js";
|
|
14
|
+
export declare const GRVT_HTTP_TIMEOUT_MS = 15000;
|
|
15
|
+
/** Response of an unexpected shape: an explicit EXCHANGE_ERROR, never a guessed value. */
|
|
16
|
+
export declare function grvtShapeError(what: string): PerpError;
|
|
17
|
+
export declare class GrvtClient {
|
|
18
|
+
private readonly restUrl;
|
|
19
|
+
private readonly network;
|
|
20
|
+
constructor(restUrl: string, network: Network);
|
|
21
|
+
/** POST /full/v1/<endpoint>. Returns the parsed body; every error path is a PerpError. */
|
|
22
|
+
post<T = unknown>(endpoint: string, body: Record<string, unknown>): Promise<T>;
|
|
23
|
+
/** POST and unwrap `{result}` (every market-data endpoint except margin_rules). */
|
|
24
|
+
result<T = unknown>(endpoint: string, body: Record<string, unknown>): Promise<T>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRVT Market Data REST transport (official API, api-docs.grvt.io/market_data_api/).
|
|
3
|
+
*
|
|
4
|
+
* - `POST <rest>/full/v1/<endpoint>` with a JSON body; success is `{result: …}` (`margin_rules`
|
|
5
|
+
* answers its object unwrapped), an error is `{code, message, status}` with the HTTP status
|
|
6
|
+
* (1003/400 malformed — also what an unknown instrument gets —, 1004/404 not found, 1006/429
|
|
7
|
+
* rate limit, 1002/500 internal).
|
|
8
|
+
* - Rate limits (help.grvt.io): Ticker / MiniTicker / GetInstrument 1500 per minute, everything
|
|
9
|
+
* else 500 per minute — two token buckets per network (venues/grvt.ts).
|
|
10
|
+
* - No retries, no fallback: every failure is a PerpError.
|
|
11
|
+
*/
|
|
12
|
+
import { PerpError, extractErrorMessage } from "../../errors.js";
|
|
13
|
+
import { acquireRate, rateLimitedError } from "../rate-limiter.js";
|
|
14
|
+
import { GRVT_OTHER_RATE_LIMIT, GRVT_TICKER_RATE_LIMIT } from "../venues/grvt.js";
|
|
15
|
+
export const GRVT_HTTP_TIMEOUT_MS = 15_000;
|
|
16
|
+
/** Endpoints in the 1500 / min bucket. */
|
|
17
|
+
const TICKER_BUCKET = new Set(["ticker", "mini", "instrument"]);
|
|
18
|
+
/** Response of an unexpected shape: an explicit EXCHANGE_ERROR, never a guessed value. */
|
|
19
|
+
export function grvtShapeError(what) {
|
|
20
|
+
return new PerpError("EXCHANGE_ERROR", `GRVT market data: unexpected response — ${what}`, {
|
|
21
|
+
exchange: "grvt",
|
|
22
|
+
remediation: "GRVT's market-data response did not match its documented schema (api-docs.grvt.io); perp-cli needs an update to read it",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function errorBodyOf(parsed) {
|
|
26
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
27
|
+
return null;
|
|
28
|
+
const p = parsed;
|
|
29
|
+
if (typeof p.message !== "string")
|
|
30
|
+
return null;
|
|
31
|
+
return { code: typeof p.code === "number" ? p.code : null, message: p.message };
|
|
32
|
+
}
|
|
33
|
+
export class GrvtClient {
|
|
34
|
+
restUrl;
|
|
35
|
+
network;
|
|
36
|
+
constructor(restUrl, network) {
|
|
37
|
+
this.restUrl = restUrl;
|
|
38
|
+
this.network = network;
|
|
39
|
+
}
|
|
40
|
+
/** POST /full/v1/<endpoint>. Returns the parsed body; every error path is a PerpError. */
|
|
41
|
+
async post(endpoint, body) {
|
|
42
|
+
const ticker = TICKER_BUCKET.has(endpoint);
|
|
43
|
+
await acquireRate(`grvt:${ticker ? "ticker" : "other"}:${this.network}`, ticker ? GRVT_TICKER_RATE_LIMIT : GRVT_OTHER_RATE_LIMIT, endpoint);
|
|
44
|
+
let res;
|
|
45
|
+
try {
|
|
46
|
+
res = await fetch(`${this.restUrl}/full/v1/${endpoint}`, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: { "content-type": "application/json" },
|
|
49
|
+
body: JSON.stringify(body),
|
|
50
|
+
signal: AbortSignal.timeout(GRVT_HTTP_TIMEOUT_MS),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
throw new PerpError("EXCHANGE_UNREACHABLE", `GRVT ${endpoint}: ${extractErrorMessage(e)}`, { exchange: "grvt" });
|
|
55
|
+
}
|
|
56
|
+
const text = await res.text().catch((e) => {
|
|
57
|
+
throw new PerpError("EXCHANGE_UNREACHABLE", `GRVT ${endpoint}: response body unreadable (${extractErrorMessage(e)})`, { exchange: "grvt" });
|
|
58
|
+
});
|
|
59
|
+
if (res.status === 429)
|
|
60
|
+
throw rateLimitedError("grvt", res.headers.get("retry-after"), text);
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(text);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
parsed = undefined;
|
|
67
|
+
}
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
const err = errorBodyOf(parsed);
|
|
70
|
+
const msg = err !== null ? `${err.code ?? "?"} ${err.message}` : text.slice(0, 200);
|
|
71
|
+
throw new PerpError(res.status >= 500 ? "EXCHANGE_UNREACHABLE" : "EXCHANGE_ERROR", `GRVT ${endpoint}: HTTP ${res.status} ${msg}`, {
|
|
72
|
+
exchange: "grvt", status: res.status,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (parsed === undefined)
|
|
76
|
+
throw grvtShapeError(`${endpoint} returned a non-JSON body`);
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
/** POST and unwrap `{result}` (every market-data endpoint except margin_rules). */
|
|
80
|
+
async result(endpoint, body) {
|
|
81
|
+
const res = await this.post(endpoint, body);
|
|
82
|
+
if (res === null || typeof res !== "object" || !("result" in res))
|
|
83
|
+
throw grvtShapeError(`${endpoint} has no result`);
|
|
84
|
+
return res.result;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRVT public market data — official Market Data REST API (api-docs.grvt.io/market_data_api/).
|
|
3
|
+
*
|
|
4
|
+
* Sources (all `POST https://market-data.grvt.io/full/v1/…`, no key):
|
|
5
|
+
* instruments {kind:["PERPETUAL"], is_active:true, limit} directory: instrument "BTC_USDT_Perp",
|
|
6
|
+
* base, quote, kind, settlement_period, tick_size, min_size, min_notional,
|
|
7
|
+
* funding_interval_hours (4 or 8, PER INSTRUMENT), asset_class (CRYPTO / EQUITY / COMMODITY),
|
|
8
|
+
* status. No leverage and no lot step (base_decimals is the signing multiplier, not a lot).
|
|
9
|
+
* ticker {instrument} one instrument per call (no bulk ticker exists): mark / index / last /
|
|
10
|
+
* best bid / best ask prices, open_interest (base), buy/sell_volume_24h_q (quote = USDT),
|
|
11
|
+
* funding_rate, next_funding_time (ns).
|
|
12
|
+
* margin_rules {instrument} risk_brackets → max_leverage of tier 1 (= maxLeverage) and the tiers.
|
|
13
|
+
*
|
|
14
|
+
* Mapping:
|
|
15
|
+
* - symbol = `base` ("BTC", "KPEPE"); venueSymbol = instrument ("BTC_USDT_Perp"); canonical via
|
|
16
|
+
* canonical-symbols.ts (crypto by ticker + denomination aliases, equity / etf by ticker,
|
|
17
|
+
* commodity explicit only). Pre-IPO markets (listed by GRVT as EQUITY) are preIpo → no canonical.
|
|
18
|
+
* - funding: `funding_rate` is the venue's INDICATIVE rate for the active interval (the interval
|
|
19
|
+
* ending at next_funding_time) — kind "predicted", a moving estimate. Its unit is PERCENT per the
|
|
20
|
+
* market's funding interval: settled history (/full/v1/funding, "percentage points") prints the
|
|
21
|
+
* same magnitude (BTC 0.01 = HL-style 0.01 %/8 h baseline; ticker 0.0063). The ticker schema says
|
|
22
|
+
* "centibeeps", which would make every rate 100× smaller than its own settled prints — not used.
|
|
23
|
+
* Converted to a fraction per interval by an exact × 0.01; basis perHours = funding_interval_hours.
|
|
24
|
+
* - volume24h = buy + sell 24 h quote volume (the docs' "24 hour volume"), exact decimal sum.
|
|
25
|
+
* - unknown / absent values are null; a ticker without a mark price, or whose read fails, drops
|
|
26
|
+
* that market (warned by name).
|
|
27
|
+
* - request budget: the registry path (fetchMarkets / fetchFundingRates) is price-only
|
|
28
|
+
* (instruments + one ticker per market, both reused in-process for 15 s); margin_rules (leverage)
|
|
29
|
+
* only for `market list` / `info`, file-cached 6 h per network.
|
|
30
|
+
*/
|
|
31
|
+
import { type VenuePublicData } from "../venue.js";
|
|
32
|
+
import type { AssetClass, FundingRateBasis, Network, VenueFundingRate, VenueMarket } from "../venue-types.js";
|
|
33
|
+
import { GrvtClient } from "./client.js";
|
|
34
|
+
/** Requests in flight at once for the per-instrument fan-out (ticker / margin_rules). */
|
|
35
|
+
export declare const GRVT_FANOUT_CONCURRENCY = 8;
|
|
36
|
+
/** In-process reuse of the directory and of each ticker (repeated MCP / dashboard reads). */
|
|
37
|
+
export declare const GRVT_SHORT_TTL_MS = 15000;
|
|
38
|
+
/** margin_rules brackets change rarely: file cache (src/cache.ts), per network. */
|
|
39
|
+
export declare const GRVT_MARGIN_TTL_MS: number;
|
|
40
|
+
export interface GrvtMarketMeta {
|
|
41
|
+
instrument: string;
|
|
42
|
+
symbol: string;
|
|
43
|
+
quote: string;
|
|
44
|
+
tickSize: string;
|
|
45
|
+
minSize: string;
|
|
46
|
+
minNotional: string | null;
|
|
47
|
+
/** funding_interval_hours of this instrument (required by the schema; drift is EXCHANGE_ERROR). */
|
|
48
|
+
fundingIntervalHours: number;
|
|
49
|
+
assetClass: AssetClass;
|
|
50
|
+
canonical: string | null;
|
|
51
|
+
tradable: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Cross-venue key: crypto / commodity / preIpo through canonical-symbols.ts; an equity / etf only
|
|
55
|
+
* when whitelisted (MATCHABLE_EQUITY / ETF), else null — warned once when not reviewed at all.
|
|
56
|
+
*/
|
|
57
|
+
export declare function grvtCanonicalOf(symbol: string, assetClass: AssetClass, warn: (msg: string) => void): string | null;
|
|
58
|
+
/**
|
|
59
|
+
* Parse one instruments row. Returns null for a row that is not a listed perpetual (another kind,
|
|
60
|
+
* an expiring settlement period); shape drift on a perpetual row is EXCHANGE_ERROR.
|
|
61
|
+
*/
|
|
62
|
+
export declare function toGrvtMarketMeta(raw: unknown, warn: (msg: string) => void): GrvtMarketMeta | null;
|
|
63
|
+
export declare function fetchGrvtDirectory(client: GrvtClient, warn: (msg: string) => void): Promise<GrvtMarketMeta[]>;
|
|
64
|
+
/** Resolve a CLI symbol ("btc", "BTC-PERP", "BTC_USDT_Perp", "BTC-USDT") against the directory. */
|
|
65
|
+
export declare function resolveGrvtMarket(dir: readonly GrvtMarketMeta[], input: string): GrvtMarketMeta;
|
|
66
|
+
export interface GrvtTicker {
|
|
67
|
+
markPrice: string | null;
|
|
68
|
+
indexPrice: string | null;
|
|
69
|
+
lastPrice: string | null;
|
|
70
|
+
bestBid: string | null;
|
|
71
|
+
bestAsk: string | null;
|
|
72
|
+
openInterest: string | null;
|
|
73
|
+
volume24hQuote: string | null;
|
|
74
|
+
/** Percent per the market's funding interval, as published. */
|
|
75
|
+
fundingPct: string | null;
|
|
76
|
+
/** ms; null when absent. */
|
|
77
|
+
nextFundingTime: number | null;
|
|
78
|
+
}
|
|
79
|
+
export declare function toGrvtTicker(raw: unknown): GrvtTicker;
|
|
80
|
+
/** Percent → fraction, exactly ("0.0063" → "0.000063"); null stays null (unknown, never 0). */
|
|
81
|
+
export declare function grvtPercentToFraction(pct: string | null): string | null;
|
|
82
|
+
export declare function grvtFundingBasis(m: Pick<GrvtMarketMeta, "fundingIntervalHours">): FundingRateBasis;
|
|
83
|
+
export interface GrvtLeverage {
|
|
84
|
+
maxLeverage: number;
|
|
85
|
+
tiers: Array<{
|
|
86
|
+
lowerBound: number;
|
|
87
|
+
maxLeverage: number;
|
|
88
|
+
}>;
|
|
89
|
+
}
|
|
90
|
+
/** risk_brackets → tier-1 max leverage and the notional tiers (ascending); null when unpublished. */
|
|
91
|
+
export declare function toGrvtLeverage(raw: unknown): GrvtLeverage | null;
|
|
92
|
+
type Settled<R> = {
|
|
93
|
+
ok: true;
|
|
94
|
+
value: R;
|
|
95
|
+
} | {
|
|
96
|
+
ok: false;
|
|
97
|
+
error: unknown;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Run `fn` over `items` with `limit` in flight; each item settles on its own. RATE_LIMITED stops
|
|
101
|
+
* every worker (nothing new is sent) and is re-thrown — the venue told us to back off.
|
|
102
|
+
*/
|
|
103
|
+
export declare function settleEach<T, R>(items: readonly T[], limit: number, fn: (item: T) => Promise<R>): Promise<Settled<R>[]>;
|
|
104
|
+
export declare function fetchGrvtTicker(client: GrvtClient, instrument: string): Promise<GrvtTicker>;
|
|
105
|
+
export declare function fetchGrvtLeverage(client: GrvtClient, instrument: string): Promise<GrvtLeverage | null>;
|
|
106
|
+
/** Drop every GRVT cache (in-process directory / tickers and the margin-rules file cache). */
|
|
107
|
+
export declare function clearGrvtCaches(): void;
|
|
108
|
+
/** The directory, reused for GRVT_SHORT_TTL_MS; a failed read is not kept. */
|
|
109
|
+
export declare function grvtDirectory(client: GrvtClient, net: Network, warn: (msg: string) => void): Promise<GrvtMarketMeta[]>;
|
|
110
|
+
/** One ticker, reused for GRVT_SHORT_TTL_MS. */
|
|
111
|
+
export declare function grvtTicker(client: GrvtClient, net: Network, instrument: string): Promise<GrvtTicker>;
|
|
112
|
+
/** One margin-rules cache entry: when it was read (ms) and what it said (null = no brackets published). */
|
|
113
|
+
export interface GrvtLeverageEntry {
|
|
114
|
+
fetchedAt: number;
|
|
115
|
+
leverage: GrvtLeverage | null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Leverage per instrument from the margin-rules file cache (JSON: instrument → {fetchedAt, leverage}).
|
|
119
|
+
* Each entry expires on its own GRVT_MARGIN_TTL_MS after ITS read (rewriting the file never extends
|
|
120
|
+
* an old entry); expired, malformed and missing instruments are read again. A failed read leaves
|
|
121
|
+
* that market's leverage unknown (null entry, warned, not cached).
|
|
122
|
+
*/
|
|
123
|
+
export declare function grvtLeverageFor(client: GrvtClient, net: Network, instruments: readonly string[], warn: (msg: string) => void): Promise<Record<string, GrvtLeverageEntry | null>>;
|
|
124
|
+
export type GrvtVenueMarket = VenueMarket & {
|
|
125
|
+
tickSize: string;
|
|
126
|
+
/** When maxLeverage / marginTiers were read from GRVT (ISO; they may come from the 6 h cache); null = not read. */
|
|
127
|
+
leverageAsOf: string | null;
|
|
128
|
+
lastPrice: string | null;
|
|
129
|
+
bestBid: string | null;
|
|
130
|
+
bestAsk: string | null;
|
|
131
|
+
nextFundingTime: number | null;
|
|
132
|
+
};
|
|
133
|
+
export declare function toGrvtVenueMarket(m: GrvtMarketMeta, t: GrvtTicker & {
|
|
134
|
+
markPrice: string;
|
|
135
|
+
}, entry: GrvtLeverageEntry | null): GrvtVenueMarket;
|
|
136
|
+
export interface GrvtPublicDeps {
|
|
137
|
+
warn: (msg: string) => void;
|
|
138
|
+
}
|
|
139
|
+
export declare function grvtClient(net: Network): GrvtClient;
|
|
140
|
+
export interface GrvtMarketsOptions {
|
|
141
|
+
/** true: also max leverage / margin tiers (margin_rules, file-cached); false: price-only (instruments + tickers). */
|
|
142
|
+
leverage: boolean;
|
|
143
|
+
}
|
|
144
|
+
export declare function fetchGrvtMarkets(net: Network, opts: GrvtMarketsOptions, deps?: GrvtPublicDeps): Promise<GrvtVenueMarket[]>;
|
|
145
|
+
/** One market: directory (cached) + its ticker + its margin rules — no fan-out. */
|
|
146
|
+
export declare function fetchGrvtMarket(net: Network, symbol: string, deps?: GrvtPublicDeps): Promise<GrvtVenueMarket>;
|
|
147
|
+
/** Funding rows (tickers only — no margin_rules): fraction per the market's own interval. */
|
|
148
|
+
export declare function fetchGrvtFundingRates(net: Network, deps?: GrvtPublicDeps): Promise<VenueFundingRate[]>;
|
|
149
|
+
/** The registry's public data path (MCP get_prices / get_markets, market://prices, arb, dashboards): price-only. */
|
|
150
|
+
export declare const grvtPublicData: VenuePublicData;
|
|
151
|
+
export {};
|