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
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX public market data
|
|
2
|
+
* QFEX public market data.
|
|
3
3
|
*
|
|
4
4
|
* Sources (all public):
|
|
5
5
|
* GET /refdata instruments: tick, lot, min/max quantity, price band, status,
|
|
6
6
|
* quote / margin asset, allowed order types + TIFs, market hours
|
|
7
7
|
* MDS market_stats snapshot mark price (each entry with its own `time`), open interest,
|
|
8
8
|
* 24h notional volume
|
|
9
|
-
* GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads
|
|
9
|
+
* GET /md/contracts funding_rate / next_funding_rate_timestamp (funding reads);
|
|
10
|
+
* index_price (market rows, best effort)
|
|
10
11
|
*
|
|
11
|
-
* Coupling
|
|
12
|
-
* Markets need /refdata + the MDS snapshot
|
|
13
|
-
*
|
|
12
|
+
* Coupling: order-entry precision comes from /refdata ALONE (fetchQfexInstruments).
|
|
13
|
+
* Markets need /refdata + the MDS snapshot; /md/contracts only adds indexPrice — its failure
|
|
14
|
+
* leaves indexPrice null (unknown) with a warning, never fails market listing or order entry.
|
|
15
|
+
* Funding reads need /md/contracts.
|
|
16
|
+
*
|
|
17
|
+
* indexPrice = /md/contracts `index_price` (docs contract-specifications: the Index Price is "the
|
|
18
|
+
* reference spot price for the asset"; the OpenAPI field has no description). Live 2026-10-19 it
|
|
19
|
+
* tracked the MDS `underlier` channel (AAPL 342.47 = 342.47, GOLD 4170.22 vs 4170.05, SAMSUNG-KRW
|
|
20
|
+
* 220092 vs 220082 KRW), moved on 122 of 222 markets within 45 s, and sat a median 0.015 % from
|
|
21
|
+
* the mark (max 5 %, CVNA pre-market). It is in the quote currency (`index_currency`), like the mark.
|
|
22
|
+
* /md/contracts lists ACTIVE markets only; a non-positive / missing value is null.
|
|
14
23
|
*
|
|
15
24
|
* Funding is UNVERIFIED (venue flag fundingVerified: false, Q4): the rate's unit is not
|
|
16
25
|
* confirmed against settled payments and `funding_rate` vs `next_funding_rate` is undecided.
|
|
17
26
|
* Markets therefore carry fundingRate null; fetchFundingRates returns the raw `funding_rate`
|
|
18
|
-
* for the explicit, flagged reads only (`
|
|
27
|
+
* for the explicit, flagged reads only (`arb scan --rates|--compare --include-unverified`).
|
|
19
28
|
*
|
|
20
29
|
* Symbols: "AAPL-USD" → CLI symbol "AAPL". Non-USD quotes keep the suffix ("SKHYNIX-KRW"),
|
|
21
30
|
* have no canonical symbol and are NOT tradable through perp-cli (USD-assuming sizing / risk).
|
|
@@ -103,26 +112,24 @@ export function toQfexInstrument(raw) {
|
|
|
103
112
|
marketHours: hours !== null && typeof hours === "object" && !Array.isArray(hours) ? hours : null,
|
|
104
113
|
};
|
|
105
114
|
}
|
|
106
|
-
/** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE
|
|
115
|
+
/** Is the market one perp-cli may send orders to? USD-quoted and ACTIVE. */
|
|
107
116
|
export function qfexTradable(i) {
|
|
108
117
|
return i.status === "ACTIVE" && i.quoteAsset === "USD";
|
|
109
118
|
}
|
|
110
119
|
// ── Market hours → session ──
|
|
111
120
|
const WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
112
121
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* This is the underlying's regular session only — it is NOT used to infer whether funding
|
|
116
|
-
* accrues (the schedule and the documented funding hours do not map 1:1, Q9).
|
|
122
|
+
* Session names of the instrument's market_hours that contain `ms` (weekday + local time in its
|
|
123
|
+
* `tz`); [] = no window listed at that time. null = the venue published no usable schedule.
|
|
117
124
|
*/
|
|
118
|
-
|
|
125
|
+
function qfexSessionsAt(marketHours, ms) {
|
|
119
126
|
if (marketHours === null || typeof marketHours.tz !== "string")
|
|
120
127
|
return null;
|
|
121
128
|
let parts;
|
|
122
129
|
try {
|
|
123
130
|
parts = new Intl.DateTimeFormat("en-US", {
|
|
124
131
|
timeZone: marketHours.tz, weekday: "long", hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23",
|
|
125
|
-
}).formatToParts(new Date(
|
|
132
|
+
}).formatToParts(new Date(ms));
|
|
126
133
|
}
|
|
127
134
|
catch {
|
|
128
135
|
return null; // an unknown IANA zone name is "no usable schedule", not a closed market
|
|
@@ -135,19 +142,70 @@ export function qfexUnderlyingOpen(marketHours, nowMs) {
|
|
|
135
142
|
const now = `${h}:${m}:${s}`;
|
|
136
143
|
const day = marketHours[weekday];
|
|
137
144
|
if (day === undefined)
|
|
138
|
-
return
|
|
145
|
+
return []; // no window listed for this weekday
|
|
139
146
|
if (!Array.isArray(day))
|
|
140
147
|
return null;
|
|
148
|
+
const out = [];
|
|
141
149
|
for (const w of day) {
|
|
142
150
|
if (w === null || typeof w !== "object")
|
|
143
151
|
return null;
|
|
144
152
|
const win = w;
|
|
145
153
|
if (typeof win.session !== "string" || typeof win.open !== "string" || typeof win.close !== "string")
|
|
146
154
|
return null;
|
|
147
|
-
if (
|
|
148
|
-
|
|
155
|
+
if (now >= win.open && now <= win.close)
|
|
156
|
+
out.push(win.session);
|
|
149
157
|
}
|
|
150
|
-
return
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Whether `nowMs` falls inside a `session: "open"` window of the instrument's market_hours.
|
|
162
|
+
* null = the venue published no usable schedule. This is the underlying's regular session only;
|
|
163
|
+
* funding hours are qfexFundingWindowClosed's question.
|
|
164
|
+
*/
|
|
165
|
+
export function qfexUnderlyingOpen(marketHours, nowMs) {
|
|
166
|
+
const sessions = qfexSessionsAt(marketHours, nowMs);
|
|
167
|
+
return sessions === null ? null : sessions.includes("open");
|
|
168
|
+
}
|
|
169
|
+
/** market_hours session names during which no funding accrues. */
|
|
170
|
+
const QFEX_NON_FUNDING_SESSIONS = new Set(["weekend", "close"]);
|
|
171
|
+
/**
|
|
172
|
+
* Was the funding window labelled `windowStartMs` outside the market's funding hours? Docs
|
|
173
|
+
* (docs.qfex.com/qfex/funding): "Funding is only active during a symbol's configured
|
|
174
|
+
* market_hours"; outside them "funding rate is 0 and no funding payment is exchanged".
|
|
175
|
+
*
|
|
176
|
+
* The window labelled T covers the hour BEFORE T (live 2026-10-19, GET /funding, 9 symbols over
|
|
177
|
+
* 10 days: AAPL-USD prints its last non-zero rate at Sat 00:00Z = Fri 20:00 ET and its first at
|
|
178
|
+
* Mon 01:00Z = Sun 21:00 ET, i.e. funding Sun 20:00–Fri 20:00 ET as documented, only when each
|
|
179
|
+
* label is read as the end of its hour).
|
|
180
|
+
*
|
|
181
|
+
* market_hours alone does not say whether an unlisted gap funds (US equities fund overnight
|
|
182
|
+
* between post_market and pre_market; Korean equities do not fund outside "open"), so a window is
|
|
183
|
+
* closed when the venue printed exactly 0 AND no part of the hour was in a funding session
|
|
184
|
+
* (anything but "weekend" / "close"). A non-zero rate is never marked closed, and a 0 inside a
|
|
185
|
+
* session stays a real 0. No usable schedule → never closed.
|
|
186
|
+
*
|
|
187
|
+
* Known limits (review B2-4 / B2-5):
|
|
188
|
+
* - market holidays are not in market_hours: a holiday inside a configured weekday session
|
|
189
|
+
* (SAMSUNG-KRW on Hangul Day) prints the venue's 0 as a real-looking "0", marketClosed false;
|
|
190
|
+
* - US equities fund in the weekday overnight gap (post_market → pre_market, docs "Sunday 20:00 –
|
|
191
|
+
* Friday 20:00 ET"), so a gap is never closed by schedule alone — only an exact 0 there is, and
|
|
192
|
+
* a genuine 0 rate in such a gap is (rarely) reported as closed.
|
|
193
|
+
*/
|
|
194
|
+
export function qfexFundingWindowClosed(marketHours, windowStartMs, rate) {
|
|
195
|
+
if (rate !== 0)
|
|
196
|
+
return false;
|
|
197
|
+
const from = windowStartMs - 3_600_000;
|
|
198
|
+
// Sampled inside the hour, never on its edges: market_hours closes are inclusive and often on
|
|
199
|
+
// the hour (GOLD-USD Fri close "17:00:00"), so a sample AT `from` would put the hour after the
|
|
200
|
+
// close inside the session (GOLD printed 0 for Fri 17:00–18:00 ET on 2026-10-09 and 10-16).
|
|
201
|
+
for (const t of [from + 1_000, from + 900_000, from + 1_800_000, from + 2_700_000, windowStartMs - 1_000]) {
|
|
202
|
+
const sessions = qfexSessionsAt(marketHours, t);
|
|
203
|
+
if (sessions === null)
|
|
204
|
+
return false;
|
|
205
|
+
if (sessions.some(x => !QFEX_NON_FUNDING_SESSIONS.has(x)))
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
151
209
|
}
|
|
152
210
|
function sessionOf(i, nowMs) {
|
|
153
211
|
const open = qfexUnderlyingOpen(i.marketHours, nowMs);
|
|
@@ -173,10 +231,24 @@ function canonicalOf(i) {
|
|
|
173
231
|
return null;
|
|
174
232
|
return canonicalFor("qfex", i.symbol, i.assetClass);
|
|
175
233
|
}
|
|
176
|
-
|
|
234
|
+
/** venue symbol → /md/contracts `index_price` (positive finite only). */
|
|
235
|
+
export function qfexIndexPrices(contracts) {
|
|
236
|
+
const out = new Map();
|
|
237
|
+
for (const c of contracts) {
|
|
238
|
+
const v = c.index_price;
|
|
239
|
+
if (typeof c.ticker_id === "string" && typeof v === "string" && Number.isFinite(Number(v)) && Number(v) > 0)
|
|
240
|
+
out.set(c.ticker_id, v);
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
}
|
|
244
|
+
export function buildQfexMarkets(instruments, snap, nowMs, indexPrices = new Map()) {
|
|
177
245
|
const markets = [];
|
|
178
246
|
const activeWithoutMark = [];
|
|
179
247
|
for (const i of instruments) {
|
|
248
|
+
// Listing policy: a DELISTED / INACTIVE market is not listed (per-symbol reads refuse it); only
|
|
249
|
+
// ACTIVE markets are, with tradable false when perp-cli cannot trade them (non-USD quote).
|
|
250
|
+
if (i.status !== "ACTIVE")
|
|
251
|
+
continue;
|
|
180
252
|
const mark = snap.marks.get(i.venueSymbol);
|
|
181
253
|
if (mark === undefined) {
|
|
182
254
|
if (i.status === "ACTIVE")
|
|
@@ -194,12 +266,16 @@ export function buildQfexMarkets(instruments, snap, nowMs) {
|
|
|
194
266
|
settle: i.marginAsset,
|
|
195
267
|
quoteAsset: i.quoteAsset,
|
|
196
268
|
markPrice: mark.price,
|
|
197
|
-
indexPrice: null,
|
|
269
|
+
indexPrice: indexPrices.get(i.venueSymbol) ?? null,
|
|
198
270
|
fundingRate: null, // unverified (Q4) — see module header
|
|
199
271
|
fundingRateBasis: QFEX_FUNDING_BASIS,
|
|
200
272
|
settlementIntervalHours: QFEX_SETTLEMENT_HOURS,
|
|
201
273
|
openInterest: oi === undefined ? null : oi,
|
|
202
|
-
|
|
274
|
+
// daily_volumes_notional is in the QUOTE currency (live 2026-10-19: SAMSUNG-KRW 9,827,530 =
|
|
275
|
+
// /md/contracts target_volume 9,763,491 KRW ≈ base_volume 45.16 × ~216,000 KRW, and the
|
|
276
|
+
// 24 hourly candles' usdVolume sum to 9,821,767). VenueMarket.volume24h is USD, and the venue
|
|
277
|
+
// publishes no USD-KRW / JPY / CNY / SEK / HKD rate → null for a non-USD quote.
|
|
278
|
+
volume24h: vol === undefined || i.quoteAsset !== "USD" ? null : vol,
|
|
203
279
|
tickSize: i.tickSize,
|
|
204
280
|
stepSize: i.lotSize,
|
|
205
281
|
minSize: i.minQuantity,
|
|
@@ -212,14 +288,40 @@ export function buildQfexMarkets(instruments, snap, nowMs) {
|
|
|
212
288
|
}
|
|
213
289
|
return { markets, activeWithoutMark };
|
|
214
290
|
}
|
|
215
|
-
|
|
216
|
-
|
|
291
|
+
/**
|
|
292
|
+
* Priced markets, the /refdata instruments they were built from (status / quote asset per row) and
|
|
293
|
+
* the 24h notional per venue symbol in its QUOTE currency (KRW for SAMSUNG-KRW).
|
|
294
|
+
*/
|
|
295
|
+
export async function fetchQfexMarketsWithInstruments(net) {
|
|
296
|
+
const client = qfexPublicClient(net);
|
|
297
|
+
const instruments = await fetchQfexInstruments(client);
|
|
217
298
|
const snap = await fetchQfexMarketStats(QFEX_ENDPOINTS[net].mdsWs, "*", QFEX_MDS_TIMEOUT_MS);
|
|
218
|
-
|
|
299
|
+
let indexPrices = new Map();
|
|
300
|
+
try {
|
|
301
|
+
const body = await client.publicGet("/md/contracts", null);
|
|
302
|
+
if (!Array.isArray(body.data))
|
|
303
|
+
throw new Error("response has no data list");
|
|
304
|
+
indexPrices = qfexIndexPrices(body.data);
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
console.warn(`[qfex] index prices unavailable (/md/contracts: ${e instanceof Error ? e.message : String(e)}); indexPrice left null`);
|
|
308
|
+
}
|
|
309
|
+
const { markets, activeWithoutMark } = buildQfexMarkets(instruments, snap, Date.now(), indexPrices);
|
|
219
310
|
if (activeWithoutMark.length > 0) {
|
|
220
311
|
console.warn(`[qfex] excluded ${activeWithoutMark.length} active market(s) without a mark price: ${activeWithoutMark.join(", ")}`);
|
|
221
312
|
}
|
|
222
|
-
return markets;
|
|
313
|
+
return { markets, instruments, volumeNotional: snap.volumeNotional };
|
|
314
|
+
}
|
|
315
|
+
/** "active" = ACTIVE and USD-quoted; "nonUsdQuote" = ACTIVE, quoted in KRW / JPY / … (readable, never ordered). */
|
|
316
|
+
export function qfexListedStatus(i) {
|
|
317
|
+
if (i.status !== "ACTIVE")
|
|
318
|
+
throw new PerpError("EXCHANGE_ERROR", `QFEX ${i.venueSymbol} is ${i.status}; it is not listed`, { exchange: "qfex" });
|
|
319
|
+
return qfexTradable(i) ? "active" : "nonUsdQuote";
|
|
320
|
+
}
|
|
321
|
+
async function fetchMarkets(net) {
|
|
322
|
+
const { markets, instruments } = await fetchQfexMarketsWithInstruments(net);
|
|
323
|
+
const bySymbol = new Map(instruments.map(i => [i.venueSymbol, i]));
|
|
324
|
+
return markets.map(m => { const i = bySymbol.get(m.venueSymbol); return i === undefined ? m : { ...m, status: qfexListedStatus(i) }; });
|
|
223
325
|
}
|
|
224
326
|
export function buildQfexFundingRates(instruments, contracts, marks, nowMs) {
|
|
225
327
|
const byVenueSymbol = new Map(instruments.map(i => [i.venueSymbol, i]));
|
|
@@ -238,12 +340,16 @@ export function buildQfexFundingRates(instruments, contracts, marks, nowMs) {
|
|
|
238
340
|
}
|
|
239
341
|
const mark = marks.get(c.ticker_id);
|
|
240
342
|
const nextMs = typeof c.next_funding_rate_timestamp === "number" ? c.next_funding_rate_timestamp * 1000 : null;
|
|
343
|
+
// `funding_rate` is the implied rate of the window in progress, labelled by its END (the next
|
|
344
|
+
// top of the hour, = next_funding_rate_timestamp live) — the same window rule as history.
|
|
345
|
+
const marketClosed = qfexFundingWindowClosed(i.marketHours, Math.floor(nowMs / 3_600_000) * 3_600_000 + 3_600_000, Number(c.funding_rate));
|
|
241
346
|
rates.push({
|
|
242
347
|
venue: "qfex",
|
|
243
348
|
venueSymbol: c.ticker_id,
|
|
244
349
|
symbol: i.symbol,
|
|
245
350
|
canonical: canonicalOf(i),
|
|
246
|
-
rate: c.funding_rate,
|
|
351
|
+
rate: marketClosed ? null : c.funding_rate,
|
|
352
|
+
marketClosed,
|
|
247
353
|
basis: QFEX_FUNDING_BASIS,
|
|
248
354
|
markPrice: mark === undefined ? null : mark.price,
|
|
249
355
|
// A timestamp in the past is not a "next" funding time (seen on closed markets).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX request signing + wire-number conversion
|
|
2
|
+
* QFEX request signing + wire-number conversion.
|
|
3
3
|
*
|
|
4
4
|
* Auth (docs /websocket/channels/trade/authenticate, OpenAPI securitySchemes):
|
|
5
5
|
* signature = hex(HMAC-SHA256(secret, `${nonce}:${unix_ts}`))
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* fromWireNumber are the only places a decimal string becomes a float or the reverse.
|
|
13
13
|
*
|
|
14
14
|
* Wire facts that needed a live check are single constants here; while one is unset every code
|
|
15
|
-
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response
|
|
15
|
+
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response.
|
|
16
16
|
*/
|
|
17
17
|
import { PerpError } from "../../errors.js";
|
|
18
18
|
export interface QfexCredentials {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX request signing + wire-number conversion
|
|
2
|
+
* QFEX request signing + wire-number conversion.
|
|
3
3
|
*
|
|
4
4
|
* Auth (docs /websocket/channels/trade/authenticate, OpenAPI securitySchemes):
|
|
5
5
|
* signature = hex(HMAC-SHA256(secret, `${nonce}:${unix_ts}`))
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* fromWireNumber are the only places a decimal string becomes a float or the reverse.
|
|
13
13
|
*
|
|
14
14
|
* Wire facts that needed a live check are single constants here; while one is unset every code
|
|
15
|
-
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response
|
|
15
|
+
* path that needs it refuses with the uniform CAPABILITY_UNSUPPORTED response.
|
|
16
16
|
*/
|
|
17
17
|
import { createHmac, randomBytes } from "node:crypto";
|
|
18
18
|
import { PerpError } from "../../errors.js";
|
|
@@ -108,8 +108,7 @@ export const QFEX_MODIFY_KEEPS_CLIENT_ID = true;
|
|
|
108
108
|
* (it may be the user id). true → a mismatch is ORDER_PENDING.
|
|
109
109
|
*/
|
|
110
110
|
export const QFEX_USER_ID_IS_ACCOUNT = null;
|
|
111
|
-
const Q2_REMEDIATION = "Reduce / close the position on the QFEX web app. perp-cli sends reduce-only orders on QFEX only with the wire format of item Q2 "
|
|
112
|
-
+ "(docs/TEST-PLAN.md) set";
|
|
111
|
+
const Q2_REMEDIATION = "Reduce / close the position on the QFEX web app. perp-cli sends reduce-only orders on QFEX only with the wire format of item Q2 set";
|
|
113
112
|
/** The uniform refusal of every reduce-only path when the wire format is unset; null when it is set. */
|
|
114
113
|
export function qfexReduceOnlyGate(wire) {
|
|
115
114
|
if (wire !== null)
|
|
@@ -121,7 +120,7 @@ export function qfexAutomationGate(verified) {
|
|
|
121
120
|
if (verified === true)
|
|
122
121
|
return null;
|
|
123
122
|
return unsupported("qfex", "automation", "funding is unverified (Q4) and a resting reduce-only order the market reaches while flat is not proven harmless (Q2), and no automated flow places its own protective stop yet", "Trade QFEX with the manual commands (perp trade market / limit / stop / tpsl / close / reduce / close-all / flatten -e qfex) and watch the position yourself; "
|
|
124
|
-
+ "automation opens once
|
|
123
|
+
+ "automation opens once QFEX items Q2 / Q4 are settled");
|
|
125
124
|
}
|
|
126
125
|
/** `reduce_only` value for the wire in the verified format. */
|
|
127
126
|
export function reduceOnlyWireValue(wire, reduceOnly) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX Trade WebSocket transport
|
|
2
|
+
* QFEX Trade WebSocket transport.
|
|
3
3
|
*
|
|
4
4
|
* The venue has no request id. Correlation therefore rests on four rules:
|
|
5
5
|
* 1. ONE command at a time per connection (FIFO) — reads share the queue.
|
|
@@ -94,7 +94,7 @@ export interface QfexFillResponse {
|
|
|
94
94
|
export declare function orderResponseOf(frame: QfexFrame): QfexOrderResponse | null;
|
|
95
95
|
export declare function fillResponseOf(frame: QfexFrame): QfexFillResponse | null;
|
|
96
96
|
/**
|
|
97
|
-
* Error frame in any documented shape
|
|
97
|
+
* Error frame in any documented shape:
|
|
98
98
|
* { "err": { error_code, message, incoming_message } } /websocket/errors + AsyncAPI
|
|
99
99
|
* { "type": "Err", error_code, message, incoming_message } /websocket/rate
|
|
100
100
|
* { "type": "error", error_code, message } the docs' own client samples
|
|
@@ -277,7 +277,7 @@ export interface QfexMdsSnapshot {
|
|
|
277
277
|
}>;
|
|
278
278
|
/** Venue symbol → open interest (base units). */
|
|
279
279
|
openInterest: Map<string, string>;
|
|
280
|
-
/** Venue symbol → 24h volume,
|
|
280
|
+
/** Venue symbol → 24h volume, notional in the market's QUOTE currency (KRW for SAMSUNG-KRW — not USD). */
|
|
281
281
|
volumeNotional: Map<string, string>;
|
|
282
282
|
frame: QfexFrame;
|
|
283
283
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QFEX Trade WebSocket transport
|
|
2
|
+
* QFEX Trade WebSocket transport.
|
|
3
3
|
*
|
|
4
4
|
* The venue has no request id. Correlation therefore rests on four rules:
|
|
5
5
|
* 1. ONE command at a time per connection (FIFO) — reads share the queue.
|
|
@@ -66,7 +66,7 @@ export function fillResponseOf(frame) {
|
|
|
66
66
|
return r;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* Error frame in any documented shape
|
|
69
|
+
* Error frame in any documented shape:
|
|
70
70
|
* { "err": { error_code, message, incoming_message } } /websocket/errors + AsyncAPI
|
|
71
71
|
* { "type": "Err", error_code, message, incoming_message } /websocket/rate
|
|
72
72
|
* { "type": "error", error_code, message } the docs' own client samples
|
|
@@ -86,7 +86,7 @@ export function errFrameOf(frame) {
|
|
|
86
86
|
: null;
|
|
87
87
|
return { errorCode: body.error_code, message: typeof body.message === "string" ? body.message : null, echo };
|
|
88
88
|
}
|
|
89
|
-
/** Auth outcome frame: both documented success shapes are accepted
|
|
89
|
+
/** Auth outcome frame: both documented success shapes are accepted. */
|
|
90
90
|
function authVerdictOf(frame) {
|
|
91
91
|
if (typeof frame.authenticated === "boolean")
|
|
92
92
|
return frame.authenticated;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-process token-bucket rate limiter keyed by venue
|
|
2
|
+
* Per-process token-bucket rate limiter keyed by venue.
|
|
3
3
|
*
|
|
4
4
|
* Limitation (documented): this only coordinates requests inside one process.
|
|
5
5
|
* CLI + MCP server + bots running at once can still exceed a venue's limit.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-process token-bucket rate limiter keyed by venue
|
|
2
|
+
* Per-process token-bucket rate limiter keyed by venue.
|
|
3
3
|
*
|
|
4
4
|
* Limitation (documented): this only coordinates requests inside one process.
|
|
5
5
|
* CLI + MCP server + bots running at once can still exceed a venue's limit.
|
|
@@ -19,7 +19,10 @@ class TokenBucket {
|
|
|
19
19
|
}
|
|
20
20
|
refill() {
|
|
21
21
|
const now = Date.now();
|
|
22
|
-
|
|
22
|
+
// Clamped: a wall clock stepping backwards (NTP, VM resume, fake timers) must not drive the bucket
|
|
23
|
+
// negative and stall every request until the clock catches up.
|
|
24
|
+
const elapsedMs = Math.max(0, now - this.last);
|
|
25
|
+
this.tokens = Math.min(this.policy.capacity, this.tokens + (elapsedMs / 1000) * this.policy.refillPerSec);
|
|
23
26
|
this.last = now;
|
|
24
27
|
}
|
|
25
28
|
acquire(weight) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapter registry — legacy name/alias API, now derived from the venue
|
|
3
|
-
* definitions (src/exchanges/venues
|
|
3
|
+
* definitions (src/exchanges/venues/). New venues are added by
|
|
4
4
|
* registering a VenueDefinition, not here.
|
|
5
5
|
*/
|
|
6
6
|
import "./venues/index.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Adapter registry — legacy name/alias API, now derived from the venue
|
|
3
|
-
* definitions (src/exchanges/venues
|
|
3
|
+
* definitions (src/exchanges/venues/). New venues are added by
|
|
4
4
|
* registering a VenueDefinition, not here.
|
|
5
5
|
*/
|
|
6
6
|
import { getVenue, listVenues, tryResolveVenueId } from "./venue.js";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RISEx adapter — market data (public), account reads (public by address) and trading through the
|
|
3
|
+
* registered, trading-only session signer (EIP-712 VerifyWitness permits; the server submits the
|
|
4
|
+
* on-chain transaction and pays gas).
|
|
5
|
+
*
|
|
6
|
+
* Account address: the signer's account (agent meta), else the master wallet's address (reads
|
|
7
|
+
* only, nothing is signed with it here). Writes need the signer.
|
|
8
|
+
*
|
|
9
|
+
* Order safety:
|
|
10
|
+
* - every refusal (market flags, maintenance, precision, pins) happens before a nonce is
|
|
11
|
+
* allocated or anything is signed;
|
|
12
|
+
* - every permit consumes a fresh (anchor, bit) pair that is persisted BEFORE the send, so a
|
|
13
|
+
* pair is never reused, even when the outcome is unknown;
|
|
14
|
+
* - a client order id is always attached (generated uint64 when the caller gives none);
|
|
15
|
+
* - an unknown send outcome → one read-only reconciliation (permit nonce consumed on-chain? the
|
|
16
|
+
* client order id in open orders / order history?) → a proven result carries notes
|
|
17
|
+
* ["reconciled"]; otherwise ORDER_PENDING ("do NOT resubmit") and the symbol is quarantined.
|
|
18
|
+
* The permit deadline is short (RISEX_PERMIT_TTL_S), so an unproven permit expires quickly.
|
|
19
|
+
*/
|
|
20
|
+
import { PerpError } from "../../errors.js";
|
|
21
|
+
import type { RisexAgentMeta } from "../../settings.js";
|
|
22
|
+
import type { EvmSigner } from "../../signer/interface.js";
|
|
23
|
+
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade, FundingHistoryRow } from "../interface.js";
|
|
24
|
+
import type { ResolvedSlippage } from "../slippage.js";
|
|
25
|
+
import { type TradingV2 } from "../trading-v2.js";
|
|
26
|
+
import type { VenueCreateContext, VenueDefinition } from "../venue.js";
|
|
27
|
+
import { type AmendRequest, type CancelResult, type LeverageResult, type Network, type OrderRef, type OrderRequest, type OrderResult, type VenueMarket } from "../venue-types.js";
|
|
28
|
+
import { type RisexChainReader } from "./session.js";
|
|
29
|
+
/** Permit lifetime: long enough for the server to submit, short enough that an unproven permit expires soon. */
|
|
30
|
+
export declare const RISEX_PERMIT_TTL_S = 120;
|
|
31
|
+
/** How long a successful on-chain trading-only proof is trusted before the next permit re-checks it. */
|
|
32
|
+
export declare const RISEX_TRADING_ONLY_TTL_MS = 60000;
|
|
33
|
+
export interface RisexAdapterDeps {
|
|
34
|
+
/** Account the reads and permits are for; null = public market data only. */
|
|
35
|
+
account: string | null;
|
|
36
|
+
/** Trading-only session signer; null = no trading. */
|
|
37
|
+
signer: EvmSigner | null;
|
|
38
|
+
/** Agent meta the signer came from (for messages); null when none. */
|
|
39
|
+
meta: RisexAgentMeta | null;
|
|
40
|
+
/** Why trading is unavailable when `signer` is null (the uniform refusal). */
|
|
41
|
+
noSignerReason: PerpError | null;
|
|
42
|
+
nonceDir?: string;
|
|
43
|
+
chain?: RisexChainReader;
|
|
44
|
+
sleep?: (ms: number) => Promise<void>;
|
|
45
|
+
/** Quarantine hook for unknown outcomes (default: bot/quarantine.ts). */
|
|
46
|
+
quarantine?: (symbol: string, reason: string, remediation: string) => void;
|
|
47
|
+
}
|
|
48
|
+
export declare class RisexAdapter implements ExchangeAdapter, TradingV2 {
|
|
49
|
+
readonly name: "risex";
|
|
50
|
+
readonly aliases: readonly ["rise"];
|
|
51
|
+
readonly venue: VenueDefinition;
|
|
52
|
+
readonly network: Network;
|
|
53
|
+
private readonly client;
|
|
54
|
+
private readonly deps;
|
|
55
|
+
private markets;
|
|
56
|
+
constructor(def: VenueDefinition, network: Network, deps: RisexAdapterDeps);
|
|
57
|
+
/** Resolve the account and the trading-only signer from the create context (OWS only). */
|
|
58
|
+
static create(def: VenueDefinition, ctx: VenueCreateContext): Promise<RisexAdapter>;
|
|
59
|
+
/**
|
|
60
|
+
* Trading capabilities — also the adapter-level release gate: while the trading role is gated
|
|
61
|
+
* (risex-release.ts) this throws CAPABILITY_UNSUPPORTED before any validation, nonce or
|
|
62
|
+
* signature. Read paths never touch it, so the adapter still serves market data / account reads.
|
|
63
|
+
*/
|
|
64
|
+
private get caps();
|
|
65
|
+
private requireAccount;
|
|
66
|
+
private requireSigner;
|
|
67
|
+
private sleep;
|
|
68
|
+
private rawMarkets;
|
|
69
|
+
/** Active market by symbol ("BTC", "BTC/USDC", "BTC-PERP") or market id. */
|
|
70
|
+
private market;
|
|
71
|
+
private symbolOfId;
|
|
72
|
+
/**
|
|
73
|
+
* The on-chain trading-only proof, re-checked at most every RISEX_TRADING_ONLY_TTL_MS before a
|
|
74
|
+
* permit (long-lived adapters — perp-mcp, bots — must notice a contract upgrade). A failed proof
|
|
75
|
+
* (refusal or RPC error) is never cached.
|
|
76
|
+
*/
|
|
77
|
+
private tradingOnlyProof;
|
|
78
|
+
private permit;
|
|
79
|
+
private chainRead;
|
|
80
|
+
private quarantine;
|
|
81
|
+
/** The account reads / permits are for (signer's account, else the master address). */
|
|
82
|
+
accountAddress(): string;
|
|
83
|
+
/** Testnet faucet: 1000 test USDC, once per wallet (also registers the account). Refused on mainnet. */
|
|
84
|
+
testnetFaucet(): Promise<{
|
|
85
|
+
txHash: string | null;
|
|
86
|
+
}>;
|
|
87
|
+
getVenueMarkets(): Promise<VenueMarket[]>;
|
|
88
|
+
/** Legacy rows plus the venue's market-level order restrictions. */
|
|
89
|
+
getMarkets(): Promise<Array<ExchangeMarketInfo & {
|
|
90
|
+
marketId: string;
|
|
91
|
+
reduceOnlyMarket: boolean;
|
|
92
|
+
postOnlyMarket: boolean;
|
|
93
|
+
tradable: boolean;
|
|
94
|
+
}>>;
|
|
95
|
+
/** `depth` levels per side (API max 250, developer.rise.trade; was always 50). */
|
|
96
|
+
getOrderbook(symbol: string, depth?: number): Promise<{
|
|
97
|
+
bids: [string, string][];
|
|
98
|
+
asks: [string, string][];
|
|
99
|
+
}>;
|
|
100
|
+
getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Settled hourly prints, newest first. The API defaults `start_time` to now − 7 d (168 rows, any
|
|
103
|
+
* `limit`), so the window is sent explicitly (`limit` hours back, one spare) and pages are
|
|
104
|
+
* followed with `page` / `has_next_page` (max 1000 rows per page; developer.rise.trade
|
|
105
|
+
* getfundingratehistory; verified live 2026-10-19).
|
|
106
|
+
*/
|
|
107
|
+
getFundingHistory(symbol: string, limit?: number): Promise<FundingHistoryRow[]>;
|
|
108
|
+
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
|
|
109
|
+
private portfolio;
|
|
110
|
+
getBalance(): Promise<ExchangeBalance>;
|
|
111
|
+
getPositions(): Promise<ExchangePosition[]>;
|
|
112
|
+
private openOrdersRaw;
|
|
113
|
+
private historyOrders;
|
|
114
|
+
private toExchangeOrder;
|
|
115
|
+
/** On-chain open orders, enriched with the indexer's decimals / fills (one retry for indexer lag). */
|
|
116
|
+
getOpenOrders(): Promise<ExchangeOrder[]>;
|
|
117
|
+
getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
|
|
118
|
+
getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
|
|
119
|
+
/** `payment` is positive when RECEIVED (RISEx `funding_payment` is positive when paid — negated here). */
|
|
120
|
+
getFundingPayments(limit?: number): Promise<ExchangeFundingPayment[]>;
|
|
121
|
+
placeOrder(req: OrderRequest): Promise<OrderResult>;
|
|
122
|
+
/** One read-only reconciliation of an unknown place outcome. */
|
|
123
|
+
private reconcilePlace;
|
|
124
|
+
amendOrder(_req: AmendRequest): Promise<OrderResult>;
|
|
125
|
+
cancelByRef(symbol: string, ref: OrderRef): Promise<CancelResult>;
|
|
126
|
+
cancelAllV2(symbol: string | null): Promise<CancelResult>;
|
|
127
|
+
applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
|
|
128
|
+
private sendAccountAction;
|
|
129
|
+
getOrderStatus(symbol: string, ref: OrderRef): Promise<OrderResult>;
|
|
130
|
+
marketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
|
|
131
|
+
reduceOnly: boolean;
|
|
132
|
+
slippage: ResolvedSlippage;
|
|
133
|
+
clientOrderId?: string;
|
|
134
|
+
}): Promise<unknown>;
|
|
135
|
+
limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
|
|
136
|
+
reduceOnly?: boolean;
|
|
137
|
+
tif?: string;
|
|
138
|
+
clientOrderId?: string;
|
|
139
|
+
}): Promise<unknown>;
|
|
140
|
+
editOrder(): Promise<unknown>;
|
|
141
|
+
cancelOrder(symbol: string, orderId: string): Promise<unknown>;
|
|
142
|
+
cancelAllOrders(symbol?: string): Promise<unknown>;
|
|
143
|
+
setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<unknown>;
|
|
144
|
+
stopOrder(): Promise<unknown>;
|
|
145
|
+
/** TP/SL entries are refused before the entry is sent (not implemented yet). */
|
|
146
|
+
stopOrderGate(): PerpError;
|
|
147
|
+
/** Bots / TWAP / grid / funding-arb legs stay off until RISEx trading is verified on mainnet. */
|
|
148
|
+
automationGate(): PerpError;
|
|
149
|
+
}
|
|
150
|
+
/** Candle intervals → ms (the venue takes the interval in ns). */
|
|
151
|
+
/**
|
|
152
|
+
* Candle intervals verified live (2026-10-19, market 1): each returns rows labelled with that
|
|
153
|
+
* interval. 3m / 30m / 2h / 8h / 12h / 3d are answered with 1m bars (no error), so they are not
|
|
154
|
+
* listed — getKlines refuses them with INVALID_PARAMS instead of returning 1m candles.
|
|
155
|
+
*/
|
|
156
|
+
export declare const RISEX_BOOK_MAX = 250;
|
|
157
|
+
export declare const RISEX_TRADES_CAP = 1000;
|
|
158
|
+
/** funding-rate-history: max rows per page (docs) and pages followed before giving up. */
|
|
159
|
+
export declare const RISEX_FUNDING_PAGE_MAX = 1000;
|
|
160
|
+
export declare const KLINE_INTERVAL_MS: Readonly<Record<string, number>>;
|