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
package/dist/commands/arb.js
CHANGED
|
@@ -1,99 +1,13 @@
|
|
|
1
1
|
import { makeTable, formatUsd, formatPercent, formatPnl, printJson, jsonOk, withJsonErrors, logSettledRejections } from "../utils.js";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { toHourlyRate, annualizeHourlyRate, estimateHourlyFundingFromHourly } from "../funding.js";
|
|
4
|
-
import { fetchAllFundingRates, fetchSymbolFundingRates, TOP_SYMBOLS, } from "../funding-rates.js";
|
|
4
|
+
import { fetchAllFundingRates, fetchCanonicalPriceGroups, dropPriceOutliers, fetchSymbolFundingRates, isRankedComparison, TOP_SYMBOLS, } from "../funding-rates.js";
|
|
5
5
|
import { saveFundingSnapshot, getHistoricalRates, getCompoundedAnnualReturn, getExchangeCompoundingHours, } from "../funding-history.js";
|
|
6
6
|
import { getVenue, listVenuesWithRole, tryResolveVenueId } from "../exchanges/venue.js";
|
|
7
7
|
import "../exchanges/venues/index.js";
|
|
8
|
-
import {
|
|
8
|
+
import { MAX_PRICE_DEVIATION_PCT } from "../exchanges/index.js";
|
|
9
|
+
import { fetchPacificaPricesRaw, parsePacificaRaw, fetchHyperliquidAllMidsRaw, fetchLighterOrderBookDetailsRaw, } from "../shared-api.js";
|
|
9
10
|
import { scanDexArb } from "../dex-asset-map.js";
|
|
10
|
-
async function fetchPacificaRates() {
|
|
11
|
-
const assets = await fetchPacificaPrices();
|
|
12
|
-
return assets.map(p => ({
|
|
13
|
-
exchange: "pacifica",
|
|
14
|
-
symbol: p.symbol,
|
|
15
|
-
fundingRate: p.funding,
|
|
16
|
-
markPrice: p.mark,
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
19
|
-
async function fetchHyperliquidRates() {
|
|
20
|
-
const assets = await fetchHyperliquidMeta();
|
|
21
|
-
return assets.map(a => ({
|
|
22
|
-
exchange: "hyperliquid",
|
|
23
|
-
symbol: a.symbol,
|
|
24
|
-
fundingRate: a.funding,
|
|
25
|
-
markPrice: a.markPx,
|
|
26
|
-
}));
|
|
27
|
-
}
|
|
28
|
-
async function fetchAsterRates() {
|
|
29
|
-
// SSOT rule #2: do NOT silently coerce missing markPrice / lastFundingRate
|
|
30
|
-
// to 0 — that would corrupt comparisons against other DEXs. Skip symbols
|
|
31
|
-
// whose Aster row is malformed instead. An endpoint failure PROPAGATES: the
|
|
32
|
-
// caller (handleBasisScan) runs every DEX under Promise.allSettled and reports
|
|
33
|
-
// the rejection on stderr — Aster is never silently absent from the scan.
|
|
34
|
-
const res = await fetch("https://fapi.asterdex.com/fapi/v1/premiumIndex");
|
|
35
|
-
if (!res.ok)
|
|
36
|
-
throw new Error(`Aster premiumIndex HTTP ${res.status}`);
|
|
37
|
-
const premiums = await res.json();
|
|
38
|
-
if (!Array.isArray(premiums))
|
|
39
|
-
throw new Error("Aster premiumIndex returned a non-array response");
|
|
40
|
-
const out = [];
|
|
41
|
-
for (const p of premiums) {
|
|
42
|
-
const rawSym = String(p.symbol ?? "");
|
|
43
|
-
if (!rawSym.endsWith("USDT"))
|
|
44
|
-
continue;
|
|
45
|
-
const fundingRate = Number(p.lastFundingRate);
|
|
46
|
-
const markPrice = Number(p.markPrice);
|
|
47
|
-
if (!Number.isFinite(fundingRate) || !Number.isFinite(markPrice) || markPrice <= 0) {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
out.push({
|
|
51
|
-
exchange: "aster",
|
|
52
|
-
symbol: rawSym.replace(/USDT$/, ""),
|
|
53
|
-
fundingRate,
|
|
54
|
-
markPrice,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
async function fetchLighterRates() {
|
|
60
|
-
// SSOT rule #2: error must propagate. Caller (handleBasisScan) uses
|
|
61
|
-
// Promise.allSettled to keep multi-DEX comparison alive when one DEX is
|
|
62
|
-
// down, with explicit stderr logging.
|
|
63
|
-
const [details, funding] = await Promise.all([
|
|
64
|
-
fetchLighterOrderBookDetails(),
|
|
65
|
-
fetchLighterFundingRates(),
|
|
66
|
-
]);
|
|
67
|
-
const priceMap = new Map(details.map(d => [d.marketId, d.lastTradePrice]));
|
|
68
|
-
const symMap = new Map(details.map(d => [d.marketId, d.symbol]));
|
|
69
|
-
const rates = [];
|
|
70
|
-
for (const fr of funding) {
|
|
71
|
-
const symbol = fr.symbol || symMap.get(fr.marketId);
|
|
72
|
-
if (!symbol)
|
|
73
|
-
continue;
|
|
74
|
-
// Prefer fr.markPrice; fall back to orderBook last-trade as documented
|
|
75
|
-
// price-source preference (NOT error fallback). Skip if neither resolves
|
|
76
|
-
// — never publish a 0 markPrice downstream.
|
|
77
|
-
const directMark = fr.markPrice;
|
|
78
|
-
const fallbackMark = priceMap.get(fr.marketId);
|
|
79
|
-
let markPrice;
|
|
80
|
-
if (directMark !== null && Number.isFinite(directMark) && directMark > 0) {
|
|
81
|
-
markPrice = directMark;
|
|
82
|
-
}
|
|
83
|
-
else if (fallbackMark !== undefined && Number.isFinite(fallbackMark) && fallbackMark > 0) {
|
|
84
|
-
markPrice = fallbackMark;
|
|
85
|
-
}
|
|
86
|
-
if (markPrice === undefined || !Number.isFinite(fr.rate))
|
|
87
|
-
continue;
|
|
88
|
-
rates.push({
|
|
89
|
-
exchange: "lighter",
|
|
90
|
-
symbol,
|
|
91
|
-
fundingRate: fr.rate,
|
|
92
|
-
markPrice,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
return rates;
|
|
96
|
-
}
|
|
97
11
|
// annualize moved to ../funding.ts — use annualizeRate() / computeAnnualSpread()
|
|
98
12
|
export function registerArbCommands(program, isJson, getAdapterForExchange) {
|
|
99
13
|
const arb = program.command("arb").description("Funding rate arbitrage & basis trading");
|
|
@@ -132,23 +46,34 @@ export async function fetchAllPrices() {
|
|
|
132
46
|
ltPrices.set(sym, price);
|
|
133
47
|
}
|
|
134
48
|
}
|
|
49
|
+
return buildGapSnapshots(pacPrices, hlPrices, ltPrices);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Cross-venue price gaps by venue symbol. The same ticker on two venues is not proof of the same
|
|
53
|
+
* instrument (Pacifica SAMSUNG 165.8 USD vs Lighter SAMSUNG 220,597 — a KRW-quoted listing): each
|
|
54
|
+
* symbol's prices go through the funding scan's median price-outlier guard (dropPriceOutliers,
|
|
55
|
+
* MAX_PRICE_DEVIATION_PCT, one warning per symbol / venue) before a gap is computed, so a gap is
|
|
56
|
+
* only reported between prices that agree within the guard.
|
|
57
|
+
*/
|
|
58
|
+
export function buildGapSnapshots(pacPrices, hlPrices, ltPrices) {
|
|
135
59
|
const allSymbols = new Set([...pacPrices.keys(), ...hlPrices.keys(), ...ltPrices.keys()]);
|
|
136
60
|
const snapshots = [];
|
|
61
|
+
const LABEL = { pacifica: "PAC", hyperliquid: "HL", lighter: "LT" };
|
|
137
62
|
for (const sym of allSymbols) {
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (available.length < 2)
|
|
63
|
+
const listed = [];
|
|
64
|
+
for (const [exchange, map] of [["pacifica", pacPrices], ["hyperliquid", hlPrices], ["lighter", ltPrices]]) {
|
|
65
|
+
const p = map.get(sym);
|
|
66
|
+
if (p !== undefined)
|
|
67
|
+
listed.push({ exchange, markPrice: p });
|
|
68
|
+
}
|
|
69
|
+
if (listed.length < 2)
|
|
70
|
+
continue;
|
|
71
|
+
const kept = dropPriceOutliers(sym, listed, "gaps");
|
|
72
|
+
// Need at least 2 exchanges whose prices agree
|
|
73
|
+
if (kept.length < 2)
|
|
150
74
|
continue;
|
|
151
|
-
|
|
75
|
+
const priceOf = (ex) => kept.find(k => k.exchange === ex)?.markPrice ?? null;
|
|
76
|
+
const available = kept.map(k => ({ ex: LABEL[k.exchange], price: k.markPrice })).sort((a, b) => a.price - b.price);
|
|
152
77
|
const cheapest = available[0];
|
|
153
78
|
const expensive = available[available.length - 1];
|
|
154
79
|
const maxGap = expensive.price - cheapest.price;
|
|
@@ -156,9 +81,9 @@ export async function fetchAllPrices() {
|
|
|
156
81
|
const maxGapPct = mid > 0 ? (maxGap / mid) * 100 : 0;
|
|
157
82
|
snapshots.push({
|
|
158
83
|
symbol: sym,
|
|
159
|
-
pacPrice:
|
|
160
|
-
hlPrice:
|
|
161
|
-
ltPrice:
|
|
84
|
+
pacPrice: priceOf("pacifica"),
|
|
85
|
+
hlPrice: priceOf("hyperliquid"),
|
|
86
|
+
ltPrice: priceOf("lighter"),
|
|
162
87
|
maxGap,
|
|
163
88
|
maxGapPct,
|
|
164
89
|
cheapest: cheapest.ex,
|
|
@@ -280,7 +205,8 @@ function printSnapshotTable(snapshot) {
|
|
|
280
205
|
}
|
|
281
206
|
const rows = snapshot.symbols.map(s => {
|
|
282
207
|
const rateOf = (id) => s.rates.find(r => r.exchange === id);
|
|
283
|
-
const
|
|
208
|
+
const spread = s.maxSpreadAnnual;
|
|
209
|
+
const spreadColor = spread === null ? chalk.gray : spread >= 30 ? chalk.green : spread >= 10 ? chalk.yellow : chalk.white;
|
|
284
210
|
// Historical-average source preference: HL, PAC, LT, AST, then whichever venue quoted.
|
|
285
211
|
const bestRate = rateOf("hyperliquid") ?? rateOf("pacifica") ?? rateOf("lighter") ?? rateOf("aster") ?? s.rates[0];
|
|
286
212
|
const trend = trendMap.get(s.symbol) ?? chalk.gray("-");
|
|
@@ -290,13 +216,18 @@ function printSnapshotTable(snapshot) {
|
|
|
290
216
|
const r = rateOf(v.id);
|
|
291
217
|
return r ? formatPercent(r.fundingRate) : chalk.gray("-");
|
|
292
218
|
}),
|
|
293
|
-
|
|
294
|
-
|
|
219
|
+
// No ranked pair (< 2 verified rows): unverified rates are shown, never ranked.
|
|
220
|
+
spread === null ? chalk.gray("n/a") : spreadColor(`${spread.toFixed(1)}%`), trend,
|
|
221
|
+
spread === null || s.longExchange === null || s.shortExchange === null ? chalk.gray("unranked (unverified)")
|
|
222
|
+
: spread >= 5 ? `${exAbbr(s.shortExchange)}>${exAbbr(s.longExchange)}${s.executable ? "" : chalk.gray(s.readOnlyLegs.length > 0 ? " (read-only)" : " (unverified)")}` : chalk.gray("-"),
|
|
295
223
|
formatAvgRate(bestRate?.historicalAvg?.avg8h), formatAvgRate(bestRate?.historicalAvg?.avg24h), formatAvgRate(bestRate?.historicalAvg?.avg7d),
|
|
296
224
|
];
|
|
297
225
|
});
|
|
298
|
-
console.log(makeTable(["Symbol", ...columns.map(v => v.displayName), "Spread", "24h", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
226
|
+
console.log(makeTable(["Symbol", ...columns.map(v => v.apiStatus.kind === "unofficial" ? `${v.displayName}*` : v.displayName), "Spread", "24h", "Direction", "Avg 8h", "Avg 24h", "Avg 7d"], rows));
|
|
299
227
|
console.log(chalk.gray(`\n ${snapshot.symbols.length} symbols compared across exchanges.\n`));
|
|
228
|
+
const unofficial = columns.filter(v => v.apiStatus.kind === "unofficial");
|
|
229
|
+
if (unofficial.length > 0)
|
|
230
|
+
console.log(chalk.yellow(` * ${unofficial.map(v => v.displayName).join(", ")}: UNOFFICIAL API (undocumented, read-only; may change or break). Unverified funding — shown only with --include-unverified.\n`));
|
|
300
231
|
}
|
|
301
232
|
function printFundingExchangeStatus(snapshot) {
|
|
302
233
|
const statuses = Object.entries(snapshot.exchangeStatus).map(([ex, status]) => {
|
|
@@ -315,9 +246,16 @@ function printDetailedComparison(comparison) {
|
|
|
315
246
|
const color = r.fundingRate > 0 ? chalk.red : r.fundingRate < 0 ? chalk.green : chalk.white;
|
|
316
247
|
console.log(` ${chalk.white.bold(exAbbr(r.exchange).padEnd(4))} Raw: ${color(formatPercent(r.fundingRate).padEnd(14))} Hourly: ${(r.hourlyRate * 100).toFixed(6)}% Annual: ${r.annualizedPct.toFixed(2)}% APY: ${(compoundedReturn * 100).toFixed(2)}%`);
|
|
317
248
|
}
|
|
249
|
+
if (!isRankedComparison(comparison)) {
|
|
250
|
+
console.log(chalk.yellow(`\n No ranked pair: fewer than two venues with verified funding (${comparison.unverifiedLegs.join(", ")} unverified — display only, never a long / short leg).\n`));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
318
253
|
const spreadColor = comparison.maxSpreadAnnual >= 30 ? chalk.green.bold : comparison.maxSpreadAnnual >= 10 ? chalk.yellow : chalk.white;
|
|
319
254
|
console.log(`\n Max Spread: ${spreadColor(`${comparison.maxSpreadAnnual.toFixed(1)}%`)} annual`);
|
|
320
|
-
console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}`
|
|
255
|
+
console.log(` Direction: Long ${exAbbr(comparison.longExchange)} / Short ${exAbbr(comparison.shortExchange)}${comparison.executable ? "" : chalk.yellow(` — not executable: ${[
|
|
256
|
+
...comparison.readOnlyLegs.map(v => `${v} is read-only in perp-cli`),
|
|
257
|
+
...comparison.unverifiedLegs.map(v => `${v} funding is unverified (display only)`),
|
|
258
|
+
].join("; ")}`)}`);
|
|
321
259
|
console.log(` Est. Income: $${comparison.estHourlyIncomeUsd.toFixed(4)}/hr per $1K notional\n`);
|
|
322
260
|
}
|
|
323
261
|
// registerFundingSubcommands removed — use 'arb scan --rates/--compare/--history/--positions' instead
|
|
@@ -325,9 +263,13 @@ function printDetailedComparison(comparison) {
|
|
|
325
263
|
/** One-line stderr notice when rows of unverified-funding venues are shown (--include-unverified). */
|
|
326
264
|
function warnUnverifiedFunding(rates) {
|
|
327
265
|
const venues = [...new Set(rates.filter(r => !r.fundingVerified).map(r => r.exchange))];
|
|
328
|
-
if (venues.length
|
|
329
|
-
|
|
330
|
-
|
|
266
|
+
if (venues.length > 0) {
|
|
267
|
+
process.stderr.write(`[warn] ${venues.join(", ")}: funding rates are UNVERIFIED (unit not confirmed against funding payments; annualized values overstate venues whose funding accrues only during market hours). Shown on request — not for trading decisions.\n`);
|
|
268
|
+
}
|
|
269
|
+
const unofficial = [...new Set(rates.filter(r => r.apiStatus === "unofficial").map(r => r.exchange))];
|
|
270
|
+
if (unofficial.length > 0) {
|
|
271
|
+
process.stderr.write(`[warn] ${unofficial.join(", ")}: read through an UNOFFICIAL API (undocumented; may change or be blocked without notice). Rows carry apiStatus "unofficial".\n`);
|
|
272
|
+
}
|
|
331
273
|
}
|
|
332
274
|
export async function handleRates(isJson, opts) {
|
|
333
275
|
const minSpread = parseFloat(opts.minSpread);
|
|
@@ -366,66 +308,64 @@ export async function handleRates(isJson, opts) {
|
|
|
366
308
|
const history = getHistoricalRates(s.symbol, bestEx, h24ago, now);
|
|
367
309
|
return { ...s, rateHistory: history.map(h => ({ ts: h.ts, hourlyRate: h.hourlyRate })) };
|
|
368
310
|
});
|
|
311
|
+
// --min never hides rows silently: the threshold and the hidden count are in meta (and data).
|
|
312
|
+
const minMeta = { minSpread: snapshot.minSpread, belowMinSpread: snapshot.belowMinSpread };
|
|
369
313
|
if (process.argv.includes("--ndjson") || process.argv.includes("--fields"))
|
|
370
|
-
return printJson(jsonOk(enriched));
|
|
371
|
-
return printJson(jsonOk({ ...snapshot, symbols: enriched }));
|
|
314
|
+
return printJson(jsonOk(enriched, minMeta));
|
|
315
|
+
return printJson(jsonOk({ ...snapshot, symbols: enriched }, minMeta));
|
|
372
316
|
}
|
|
373
317
|
printSnapshotTable(snapshot);
|
|
374
318
|
printFundingExchangeStatus(snapshot);
|
|
319
|
+
if (snapshot.belowMinSpread > 0) {
|
|
320
|
+
console.log(chalk.gray(` ${snapshot.belowMinSpread} pair${snapshot.belowMinSpread === 1 ? "" : "s"} below --min ${snapshot.minSpread}% hidden (show them: --min 0)\n`));
|
|
321
|
+
}
|
|
375
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Cross-venue price basis — a PRICE comparison, independent of funding.
|
|
325
|
+
*
|
|
326
|
+
* Venue scope: every venue with the "trading" role (both legs must be executable), whether or not
|
|
327
|
+
* its funding rate is verified; market-data-only venues (TxFlow, unofficial API) are out. Prices
|
|
328
|
+
* come from each venue's market directory and are grouped by `canonical` symbol with the funding
|
|
329
|
+
* scan's guards (fetchCanonicalPriceGroups): unclassified / ambiguous tickers have no canonical and
|
|
330
|
+
* never pair, and a venue whose mark deviates more than MAX_PRICE_DEVIATION_PCT from the group
|
|
331
|
+
* median is dropped. The chosen cheap / rich pair must itself agree within MAX_PRICE_DEVIATION_PCT:
|
|
332
|
+
* a wider gap is a different asset under the same ticker (BB on Lighter 8.50 vs Aster 0.0091).
|
|
333
|
+
* A venue that fails is reported on stderr and is absent from the scan, never silently empty.
|
|
334
|
+
*/
|
|
376
335
|
export async function handleBasisScan(isJson, opts) {
|
|
377
336
|
if (!isJson())
|
|
378
337
|
console.log(chalk.cyan(" Fetching prices for basis calculation...\n"));
|
|
379
|
-
// SSOT rule #2: each DEX is independent. allSettled keeps the comparison
|
|
380
|
-
// alive when one DEX errors out, while rejection reasons get logged to
|
|
381
|
-
// stderr (explicit failure, never silent [] downgrade).
|
|
382
|
-
const settled = await Promise.allSettled([
|
|
383
|
-
fetchPacificaRates(),
|
|
384
|
-
fetchHyperliquidRates(),
|
|
385
|
-
fetchLighterRates(),
|
|
386
|
-
fetchAsterRates(),
|
|
387
|
-
]);
|
|
388
|
-
logSettledRejections(settled, ["pacifica", "hyperliquid", "lighter", "aster"], "basis-scan");
|
|
389
|
-
const pacRates = settled[0].status === "fulfilled" ? settled[0].value : [];
|
|
390
|
-
const hlRates = settled[1].status === "fulfilled" ? settled[1].value : [];
|
|
391
|
-
const ltRates = settled[2].status === "fulfilled" ? settled[2].value : [];
|
|
392
|
-
const astRates = settled[3].status === "fulfilled" ? settled[3].value : [];
|
|
393
|
-
const exchangePrices = new Map();
|
|
394
338
|
const filterSymbol = opts.symbol?.toUpperCase();
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (!exchangePrices.has(r.symbol))
|
|
401
|
-
exchangePrices.set(r.symbol, new Map());
|
|
402
|
-
exchangePrices.get(r.symbol).set(r.exchange, r.markPrice);
|
|
403
|
-
}
|
|
339
|
+
const snapshot = await fetchCanonicalPriceGroups(listVenuesWithRole("trading"), {
|
|
340
|
+
symbols: filterSymbol !== undefined ? [filterSymbol] : undefined, tag: "basis-scan",
|
|
341
|
+
});
|
|
342
|
+
for (const [venue, msg] of Object.entries(snapshot.exchangeErrors))
|
|
343
|
+
console.error(`[basis-scan] ${venue} fetch failed: ${msg}`);
|
|
404
344
|
const opps = [];
|
|
405
|
-
for (const [symbol,
|
|
406
|
-
if (
|
|
345
|
+
for (const [symbol, priced] of snapshot.groups) {
|
|
346
|
+
if (priced.length < 2)
|
|
407
347
|
continue;
|
|
408
|
-
let
|
|
409
|
-
for (const
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (price > maxPrice) {
|
|
415
|
-
maxPrice = price;
|
|
416
|
-
maxEx = ex;
|
|
417
|
-
}
|
|
348
|
+
let min = priced[0], max = priced[0];
|
|
349
|
+
for (const r of priced) {
|
|
350
|
+
if (r.markPrice < min.markPrice)
|
|
351
|
+
min = r;
|
|
352
|
+
if (r.markPrice > max.markPrice)
|
|
353
|
+
max = r;
|
|
418
354
|
}
|
|
419
|
-
if (
|
|
355
|
+
if (min.exchange === max.exchange)
|
|
420
356
|
continue;
|
|
421
|
-
const diff =
|
|
422
|
-
const avgPrice = (
|
|
357
|
+
const diff = max.markPrice - min.markPrice;
|
|
358
|
+
const avgPrice = (max.markPrice + min.markPrice) / 2;
|
|
423
359
|
const pctDiff = (diff / avgPrice) * 100;
|
|
360
|
+
if (!(pctDiff <= MAX_PRICE_DEVIATION_PCT)) {
|
|
361
|
+
console.error(`[basis-scan] excluded ${symbol} ${min.exchange}↔${max.exchange}: mark gap ${pctDiff.toFixed(1)}% > ${MAX_PRICE_DEVIATION_PCT}% (likely a different asset with the same ticker)`);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
424
364
|
if (pctDiff * 365 >= parseFloat(opts.minBasis)) {
|
|
425
365
|
const priceObj = {};
|
|
426
|
-
for (const
|
|
427
|
-
priceObj[
|
|
428
|
-
opps.push({ symbol, prices: priceObj, priceDiff: diff, pctDiff, buyOn:
|
|
366
|
+
for (const r of priced)
|
|
367
|
+
priceObj[r.exchange] = r.markPrice;
|
|
368
|
+
opps.push({ symbol, prices: priceObj, priceDiff: diff, pctDiff, buyOn: min.exchange, sellOn: max.exchange });
|
|
429
369
|
}
|
|
430
370
|
}
|
|
431
371
|
opps.sort((a, b) => b.pctDiff - a.pctDiff);
|
|
@@ -439,12 +379,12 @@ export async function handleBasisScan(isJson, opts) {
|
|
|
439
379
|
console.log(chalk.gray(" Strategy: Buy cheap, sell expensive, profit from convergence.\n"));
|
|
440
380
|
const rows = opps.map((o) => [
|
|
441
381
|
chalk.white.bold(o.symbol),
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
o.prices.lighter ? `$${formatUsd(o.prices.lighter)}` : chalk.gray("-"),
|
|
382
|
+
`$${formatUsd(o.prices[o.buyOn])}`,
|
|
383
|
+
`$${formatUsd(o.prices[o.sellOn])}`,
|
|
445
384
|
`$${formatUsd(o.priceDiff)}`, `${o.pctDiff.toFixed(4)}%`, chalk.green(o.buyOn), chalk.red(o.sellOn),
|
|
385
|
+
chalk.gray(String(Object.keys(o.prices).length)),
|
|
446
386
|
]);
|
|
447
|
-
console.log(makeTable(["Symbol", "
|
|
387
|
+
console.log(makeTable(["Symbol", "Buy Price", "Sell Price", "Diff", "Basis %", "Buy On", "Sell On", "Venues"], rows));
|
|
448
388
|
}
|
|
449
389
|
export async function handleDexScan(isJson, opts) {
|
|
450
390
|
if (!isJson())
|
|
@@ -497,9 +437,13 @@ export async function handleCompare(isJson, symbol, includeUnverified = false) {
|
|
|
497
437
|
if (comparison)
|
|
498
438
|
warnUnverifiedFunding(comparison.rates);
|
|
499
439
|
if (!comparison) {
|
|
440
|
+
// A venue ticker whose canonical symbol differs (Variational US500-PERP = SPY): say where to look.
|
|
441
|
+
const hint = (await import("../exchanges/variational/public.js")).canonicalHintFor(symbol);
|
|
500
442
|
if (isJson())
|
|
501
|
-
return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false }));
|
|
443
|
+
return printJson(jsonOk({ symbol: symbol.toUpperCase(), available: false, ...(hint !== null ? { hint } : {}) }));
|
|
502
444
|
console.log(chalk.gray(` ${symbol.toUpperCase()} not found on at least 2 exchanges.\n`));
|
|
445
|
+
if (hint !== null)
|
|
446
|
+
console.log(chalk.yellow(` Hint: ${hint}\n`));
|
|
503
447
|
return;
|
|
504
448
|
}
|
|
505
449
|
if (isJson())
|
|
@@ -594,7 +538,7 @@ export async function handleFundingPositions(isJson, getAdapterForExchange, opts
|
|
|
594
538
|
}
|
|
595
539
|
const sz = Math.abs(Number(pos.size));
|
|
596
540
|
const nu = sz * mk;
|
|
597
|
-
const fh = fd ? fd.fundingHours : null; /* market basis first
|
|
541
|
+
const fh = fd ? fd.fundingHours : null; /* market basis first */
|
|
598
542
|
const hr = fh !== null ? fr / fh : toHourlyRate(fr, exchange);
|
|
599
543
|
const ap = annualizeHourlyRate(hr);
|
|
600
544
|
const hp = estimateHourlyFundingFromHourly(hr, nu, pos.side);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { printJson, jsonOk, jsonError, makeTable, formatUsd } from "../utils.js";
|
|
2
2
|
import { getHistoricalRates } from "../funding-history.js";
|
|
3
|
+
import { HyperliquidAdapter, hlEarliestCandleStart } from "../exchanges/hyperliquid.js";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
export function registerBacktestCommands(program, isJson) {
|
|
5
6
|
const backtest = program.command("backtest").description("Backtest trading strategies on historical data");
|
|
@@ -32,10 +33,13 @@ export function registerBacktestCommands(program, isJson) {
|
|
|
32
33
|
const ratesA = getHistoricalRates(sym, exchA, startTime, endTime);
|
|
33
34
|
const ratesB = getHistoricalRates(sym, exchB, startTime, endTime);
|
|
34
35
|
if (ratesA.length === 0 && ratesB.length === 0) {
|
|
36
|
+
// The funding history is written by `arb scan --rates` (saveFundingSnapshot; verified venues
|
|
37
|
+
// only). There is no `perp funding snapshot` command (0.15.0 named one).
|
|
38
|
+
const collect = `perp arb scan --rates --symbol ${sym}`;
|
|
35
39
|
if (isJson())
|
|
36
|
-
return printJson(jsonError("NO_DATA", `No historical funding data for ${sym}. Run '
|
|
40
|
+
return printJson(jsonError("NO_DATA", `No historical funding data for ${sym}. Run '${collect}' periodically to collect data first.`));
|
|
37
41
|
console.log(chalk.yellow(`\n No historical funding data for ${sym} on ${exchA}/${exchB}.`));
|
|
38
|
-
console.log(chalk.yellow(` Run '
|
|
42
|
+
console.log(chalk.yellow(` Run '${collect}' periodically to collect data first.\n`));
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
// Build time-aligned rate pairs
|
|
@@ -180,31 +184,25 @@ export function registerBacktestCommands(program, isJson) {
|
|
|
180
184
|
console.error(chalk.red("Error: Need at least 2 grid lines"));
|
|
181
185
|
process.exit(1);
|
|
182
186
|
}
|
|
187
|
+
// Hyperliquid serves only the most recent 5000 candles per interval (docs "Candle snapshot");
|
|
188
|
+
// with 1h candles a --days above ~208 cannot be read. The start is clamped to the available
|
|
189
|
+
// window (one hour inside it) and the result says so — never presented as the full period.
|
|
190
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
183
191
|
const endTime = Date.now();
|
|
184
|
-
const
|
|
185
|
-
|
|
192
|
+
const requestedStart = endTime - days * DAY_MS;
|
|
193
|
+
const earliest = hlEarliestCandleStart("1h", endTime) + 60 * 60 * 1000;
|
|
194
|
+
const truncated = requestedStart < earliest;
|
|
195
|
+
const startTime = truncated ? earliest : requestedStart;
|
|
196
|
+
const dataFrom = startTime;
|
|
197
|
+
const coveredDays = (endTime - startTime) / DAY_MS;
|
|
198
|
+
// Fetch historical klines from Hyperliquid (adapter: symbol resolution, interval / window checks)
|
|
186
199
|
if (!isJson()) {
|
|
187
200
|
console.log(chalk.gray(`\n Fetching ${days}d of 1h klines for ${sym}...`));
|
|
188
201
|
}
|
|
189
202
|
let klines;
|
|
190
203
|
try {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
headers: { "Content-Type": "application/json" },
|
|
194
|
-
body: JSON.stringify({
|
|
195
|
-
type: "candleSnapshot",
|
|
196
|
-
req: {
|
|
197
|
-
coin: sym,
|
|
198
|
-
interval: "1h",
|
|
199
|
-
startTime,
|
|
200
|
-
endTime,
|
|
201
|
-
},
|
|
202
|
-
}),
|
|
203
|
-
});
|
|
204
|
-
if (!resp.ok)
|
|
205
|
-
throw new Error(`HTTP ${resp.status}`);
|
|
206
|
-
const data = await resp.json();
|
|
207
|
-
klines = data.map(k => ({ t: k.t, o: k.o, h: k.h, l: k.l, c: k.c }));
|
|
204
|
+
const rows = await new HyperliquidAdapter(undefined, false).getKlines(sym, "1h", startTime, endTime);
|
|
205
|
+
klines = rows.map(k => ({ t: k.time, o: k.open, h: k.high, l: k.low, c: k.close }));
|
|
208
206
|
}
|
|
209
207
|
catch (err) {
|
|
210
208
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -291,6 +289,8 @@ export function registerBacktestCommands(program, isJson) {
|
|
|
291
289
|
symbol: sym,
|
|
292
290
|
period: `${days} days`,
|
|
293
291
|
klines: klines.length,
|
|
292
|
+
dataFrom: new Date(dataFrom).toISOString(),
|
|
293
|
+
truncated,
|
|
294
294
|
priceRange,
|
|
295
295
|
gridRange: `$${formatUsd(String(lowerPrice))} - $${formatUsd(String(upperPrice))}`,
|
|
296
296
|
grids,
|
|
@@ -307,6 +307,8 @@ export function registerBacktestCommands(program, isJson) {
|
|
|
307
307
|
return printJson(jsonOk(summary));
|
|
308
308
|
console.log(chalk.cyan.bold(`\n Grid Backtest — ${sym}\n`));
|
|
309
309
|
console.log(` Period: ${days} days (${klines.length} candles)`);
|
|
310
|
+
if (truncated)
|
|
311
|
+
console.log(chalk.yellow(` Truncated: Hyperliquid serves only the most recent 5000 1h candles — start clamped to ${new Date(dataFrom).toISOString()} (${coveredDays.toFixed(1)} of ${days} days)`));
|
|
310
312
|
console.log(` Price range: ${priceRange}`);
|
|
311
313
|
console.log(` Grid range: $${formatUsd(String(lowerPrice))} - $${formatUsd(String(upperPrice))}`);
|
|
312
314
|
console.log(` Grid lines: ${grids} (step: $${step.toFixed(2)})`);
|
package/dist/commands/bot.js
CHANGED
|
@@ -419,7 +419,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
|
|
|
419
419
|
}
|
|
420
420
|
console.log();
|
|
421
421
|
});
|
|
422
|
-
// ── quarantine
|
|
422
|
+
// ── quarantine: positions the bot refuses to touch until a human clears them ──
|
|
423
423
|
bot
|
|
424
424
|
.command("quarantine")
|
|
425
425
|
.description("List quarantined symbols (unknown / unverified order outcome or unhedged leg) — blocked for every bot strategy, arb exec and arb auto (~/.perp/bot-quarantine.json)")
|
package/dist/commands/builder.js
CHANGED
|
@@ -283,7 +283,7 @@ export function registerWalletBuilderCommands(wallet, isJson, getAdapterForExcha
|
|
|
283
283
|
.action(async (venueArg, _opts, cmd) => {
|
|
284
284
|
await withJsonErrors(isJson(), async () => {
|
|
285
285
|
const network = networkOf(cmd);
|
|
286
|
-
const venues = venueArg !== undefined ? [getVenue(venueArg).id] : [...builderFeeVenues(), ...["lighter", "lighter-rh", "arcus"]];
|
|
286
|
+
const venues = venueArg !== undefined ? [getVenue(venueArg).id] : [...builderFeeVenues(), ...["lighter", "lighter-rh", "arcus", "risex", "txflow", "variational", "grvt", "extended"]];
|
|
287
287
|
const rows = [];
|
|
288
288
|
for (const v of venues) {
|
|
289
289
|
const configured = builderTargetOf(loadSettings().builder, v) !== null;
|