perp-cli 0.12.17 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -27
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +70 -92
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +156 -684
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +7 -2
- package/dist/landing.js +19 -3
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +142 -55
- package/skills/perp-cli/references/agent-operations.md +110 -56
- package/skills/perp-cli/references/commands.md +266 -71
- package/skills/perp-cli/references/strategies.md +34 -31
- package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
- package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +31 -15
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decimal-string precision helpers for order entry (design §10.10).
|
|
3
|
+
*
|
|
4
|
+
* All math is done on scaled BigInt to avoid float drift. Rules:
|
|
5
|
+
* - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
|
|
6
|
+
* - slippage-bounded protection prices are aligned toward crossing
|
|
7
|
+
* (buy → up, sell → down);
|
|
8
|
+
* - sizes are floored to the step; a result of zero is SIZE_TOO_SMALL.
|
|
9
|
+
*/
|
|
10
|
+
import type { ExchangeAdapter } from "./interface.js";
|
|
11
|
+
/** Number of ticks (integer) for a value exactly on the grid, or null when off-grid. */
|
|
12
|
+
export declare function exactUnits(value: string, step: string): bigint | null;
|
|
13
|
+
/** Value → integer units; throws INVALID_TICK when off-grid. */
|
|
14
|
+
export declare function toUnitsExact(value: string, step: string, what: string): bigint;
|
|
15
|
+
/** Integer units → decimal string on the step grid. */
|
|
16
|
+
export declare function unitsToString(units: bigint, step: string): string;
|
|
17
|
+
/** Floor a size to the step. Throws SIZE_TOO_SMALL if the result is zero. */
|
|
18
|
+
export declare function floorSize(size: string, stepSize: string): string;
|
|
19
|
+
/** Align a slippage-bounded protection price toward crossing: buy → ceil, sell → floor. */
|
|
20
|
+
export declare function alignProtectionPrice(price: string, tickSize: string, side: "buy" | "sell"): string;
|
|
21
|
+
/** Throws INVALID_PARAMS unless `value` is a plain decimal strictly greater than zero. */
|
|
22
|
+
export declare function requirePositive(value: string, what: string): void;
|
|
23
|
+
/** Validate a limit price is positive and on tick; returns it unchanged (normalized). Throws INVALID_TICK. */
|
|
24
|
+
export declare function requireOnTick(price: string, tickSize: string): string;
|
|
25
|
+
/** Exact (a + b) / 2 of two plain decimal strings (no float arithmetic: 0.1 and 0.2 → "0.15"). */
|
|
26
|
+
export declare function midDecimal(a: string, b: string): string;
|
|
27
|
+
/** Exact decimal a − b (plain decimal strings). */
|
|
28
|
+
export declare function subDecimal(a: string, b: string): string;
|
|
29
|
+
/** Exact decimal a × b (plain decimal strings). */
|
|
30
|
+
export declare function mulDecimal(a: string, b: string): string;
|
|
31
|
+
/** mark × (1 ± bps/10000), as a decimal string (not yet tick-aligned). */
|
|
32
|
+
export declare function slippagePrice(mark: string, side: "buy" | "sell", slippageBps: number): string;
|
|
33
|
+
/**
|
|
34
|
+
* A COMPUTED price (an average, a mid, notional / size) as a plain decimal string without binary
|
|
35
|
+
* float noise ("2727.2000000000003" → "2727.2"): rounded to the market tick's decimals when
|
|
36
|
+
* `tickSize` is given, else to 8 significant digits; trailing zeros dropped. A price the venue
|
|
37
|
+
* reports as a string is passed through unchanged elsewhere — never run through this.
|
|
38
|
+
* A non-finite value throws (Rule #2: no "0" / "NaN" price).
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatComputedPrice(value: number, tickSize?: string | null): string;
|
|
41
|
+
/** Floor a non-negative measured amount (e.g. a verified fill) onto the lot grid; "0…" when below one lot. */
|
|
42
|
+
export declare function floorToStep(value: number, step: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* The market's lot step from the venue market info (stepSize, else 10^-sizeDecimals).
|
|
45
|
+
* Unknown market or unknown lot → throws (Rule #2: never size orders off the typed decimals).
|
|
46
|
+
*/
|
|
47
|
+
export declare function lotStepOf(adapter: ExchangeAdapter, symbol: string): Promise<string>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decimal-string precision helpers for order entry (design §10.10).
|
|
3
|
+
*
|
|
4
|
+
* All math is done on scaled BigInt to avoid float drift. Rules:
|
|
5
|
+
* - limit prices that are not on tick are REJECTED (INVALID_TICK), never rounded;
|
|
6
|
+
* - slippage-bounded protection prices are aligned toward crossing
|
|
7
|
+
* (buy → up, sell → down);
|
|
8
|
+
* - sizes are floored to the step; a result of zero is SIZE_TOO_SMALL.
|
|
9
|
+
*/
|
|
10
|
+
import { PerpError } from "../errors.js";
|
|
11
|
+
/**
|
|
12
|
+
* Strict plain-decimal parse ("123", "-0.5", "1.250"). Hex, exponent
|
|
13
|
+
* ("1e-8"), Infinity/NaN, empty and whitespace-padded forms are rejected —
|
|
14
|
+
* never coerced through Number().
|
|
15
|
+
*/
|
|
16
|
+
function parseDecimal(s) {
|
|
17
|
+
if (!/^-?\d+(\.\d+)?$/.test(s)) {
|
|
18
|
+
throw new PerpError("INVALID_PARAMS", `Not a plain decimal number: "${s}"`, {
|
|
19
|
+
remediation: "Use a plain decimal such as 123.45 (no exponent, hex or spaces)",
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const neg = s.startsWith("-");
|
|
23
|
+
const body = neg ? s.slice(1) : s;
|
|
24
|
+
const [ip, fp = ""] = body.split(".");
|
|
25
|
+
const int = BigInt(ip + fp) * (neg ? -1n : 1n);
|
|
26
|
+
return { int, scale: fp.length };
|
|
27
|
+
}
|
|
28
|
+
function rescale(v, scale) {
|
|
29
|
+
if (scale < v.scale)
|
|
30
|
+
throw new Error("rescale: cannot reduce scale");
|
|
31
|
+
return v.int * 10n ** BigInt(scale - v.scale);
|
|
32
|
+
}
|
|
33
|
+
function format(int, scale) {
|
|
34
|
+
const neg = int < 0n;
|
|
35
|
+
const abs = neg ? -int : int;
|
|
36
|
+
let s = abs.toString().padStart(scale + 1, "0");
|
|
37
|
+
if (scale > 0)
|
|
38
|
+
s = `${s.slice(0, s.length - scale)}.${s.slice(s.length - scale)}`.replace(/\.?0+$/, "");
|
|
39
|
+
return (neg ? "-" : "") + s;
|
|
40
|
+
}
|
|
41
|
+
/** Number of ticks (integer) for a value exactly on the grid, or null when off-grid. */
|
|
42
|
+
export function exactUnits(value, step) {
|
|
43
|
+
const v = parseDecimal(value);
|
|
44
|
+
const st = parseDecimal(step);
|
|
45
|
+
if (st.int <= 0n)
|
|
46
|
+
throw new PerpError("INVALID_PARAMS", `step must be positive (got ${step})`);
|
|
47
|
+
const scale = Math.max(v.scale, st.scale);
|
|
48
|
+
const vi = rescale(v, scale);
|
|
49
|
+
const si = rescale(st, scale);
|
|
50
|
+
if (vi % si !== 0n)
|
|
51
|
+
return null;
|
|
52
|
+
return vi / si;
|
|
53
|
+
}
|
|
54
|
+
/** Value → integer units; throws INVALID_TICK when off-grid. */
|
|
55
|
+
export function toUnitsExact(value, step, what) {
|
|
56
|
+
const u = exactUnits(value, step);
|
|
57
|
+
if (u === null) {
|
|
58
|
+
throw new PerpError("INVALID_TICK", `${what} ${value} is not a multiple of ${step}`, {
|
|
59
|
+
remediation: `Use a ${what} that is a multiple of ${step}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return u;
|
|
63
|
+
}
|
|
64
|
+
function alignUnits(value, step, mode) {
|
|
65
|
+
const v = parseDecimal(value);
|
|
66
|
+
const st = parseDecimal(step);
|
|
67
|
+
if (st.int <= 0n)
|
|
68
|
+
throw new PerpError("INVALID_PARAMS", `step must be positive (got ${step})`);
|
|
69
|
+
const scale = Math.max(v.scale, st.scale);
|
|
70
|
+
const vi = rescale(v, scale);
|
|
71
|
+
const si = rescale(st, scale);
|
|
72
|
+
let q = vi / si;
|
|
73
|
+
const r = vi % si;
|
|
74
|
+
if (r !== 0n) {
|
|
75
|
+
if (mode === "floor" && vi < 0n)
|
|
76
|
+
q -= 1n;
|
|
77
|
+
if (mode === "ceil" && vi > 0n)
|
|
78
|
+
q += 1n;
|
|
79
|
+
}
|
|
80
|
+
return q;
|
|
81
|
+
}
|
|
82
|
+
/** Integer units → decimal string on the step grid. */
|
|
83
|
+
export function unitsToString(units, step) {
|
|
84
|
+
const st = parseDecimal(step);
|
|
85
|
+
return format(units * st.int, st.scale);
|
|
86
|
+
}
|
|
87
|
+
/** Floor a size to the step. Throws SIZE_TOO_SMALL if the result is zero. */
|
|
88
|
+
export function floorSize(size, stepSize) {
|
|
89
|
+
const u = alignUnits(size, stepSize, "floor");
|
|
90
|
+
if (u <= 0n) {
|
|
91
|
+
throw new PerpError("SIZE_TOO_SMALL", `Size ${size} is below the minimum step ${stepSize}`, {
|
|
92
|
+
remediation: `Use a size of at least ${stepSize}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return unitsToString(u, stepSize);
|
|
96
|
+
}
|
|
97
|
+
/** Align a slippage-bounded protection price toward crossing: buy → ceil, sell → floor. */
|
|
98
|
+
export function alignProtectionPrice(price, tickSize, side) {
|
|
99
|
+
const u = alignUnits(price, tickSize, side === "buy" ? "ceil" : "floor");
|
|
100
|
+
if (u <= 0n)
|
|
101
|
+
throw new PerpError("INVALID_PARAMS", `Protection price ${price} aligns to zero`);
|
|
102
|
+
return unitsToString(u, tickSize);
|
|
103
|
+
}
|
|
104
|
+
/** Throws INVALID_PARAMS unless `value` is a plain decimal strictly greater than zero. */
|
|
105
|
+
export function requirePositive(value, what) {
|
|
106
|
+
if (parseDecimal(value).int <= 0n) {
|
|
107
|
+
throw new PerpError("INVALID_PARAMS", `${what} must be positive (got ${value})`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Validate a limit price is positive and on tick; returns it unchanged (normalized). Throws INVALID_TICK. */
|
|
111
|
+
export function requireOnTick(price, tickSize) {
|
|
112
|
+
requirePositive(price, "price");
|
|
113
|
+
return unitsToString(toUnitsExact(price, tickSize, "price"), tickSize);
|
|
114
|
+
}
|
|
115
|
+
/** Exact (a + b) / 2 of two plain decimal strings (no float arithmetic: 0.1 and 0.2 → "0.15"). */
|
|
116
|
+
export function midDecimal(a, b) {
|
|
117
|
+
const x = parseDecimal(a);
|
|
118
|
+
const y = parseDecimal(b);
|
|
119
|
+
const scale = Math.max(x.scale, y.scale) + 1; // one more digit holds the possible ".5"
|
|
120
|
+
return format((rescale(x, scale) + rescale(y, scale)) / 2n, scale);
|
|
121
|
+
}
|
|
122
|
+
/** Exact decimal a − b (plain decimal strings). */
|
|
123
|
+
export function subDecimal(a, b) {
|
|
124
|
+
const x = parseDecimal(a);
|
|
125
|
+
const y = parseDecimal(b);
|
|
126
|
+
const scale = Math.max(x.scale, y.scale);
|
|
127
|
+
return format(rescale(x, scale) - rescale(y, scale), scale);
|
|
128
|
+
}
|
|
129
|
+
/** Exact decimal a × b (plain decimal strings). */
|
|
130
|
+
export function mulDecimal(a, b) {
|
|
131
|
+
const x = parseDecimal(a);
|
|
132
|
+
const y = parseDecimal(b);
|
|
133
|
+
return format(x.int * y.int, x.scale + y.scale);
|
|
134
|
+
}
|
|
135
|
+
/** mark × (1 ± bps/10000), as a decimal string (not yet tick-aligned). */
|
|
136
|
+
export function slippagePrice(mark, side, slippageBps) {
|
|
137
|
+
const m = Number(mark);
|
|
138
|
+
if (!Number.isFinite(m) || m <= 0)
|
|
139
|
+
throw new PerpError("PRICE_STALE", `Invalid mark price "${mark}"`);
|
|
140
|
+
const f = side === "buy" ? 1 + slippageBps / 10_000 : 1 - slippageBps / 10_000;
|
|
141
|
+
if (f <= 0)
|
|
142
|
+
throw new PerpError("INVALID_PARAMS", `slippageBps ${slippageBps} is too large for a sell`);
|
|
143
|
+
return (m * f).toFixed(12);
|
|
144
|
+
}
|
|
145
|
+
/** Significant digits kept for a computed price when the market tick is not at hand. */
|
|
146
|
+
const COMPUTED_PRICE_SIG_DIGITS = 8;
|
|
147
|
+
/**
|
|
148
|
+
* A COMPUTED price (an average, a mid, notional / size) as a plain decimal string without binary
|
|
149
|
+
* float noise ("2727.2000000000003" → "2727.2"): rounded to the market tick's decimals when
|
|
150
|
+
* `tickSize` is given, else to 8 significant digits; trailing zeros dropped. A price the venue
|
|
151
|
+
* reports as a string is passed through unchanged elsewhere — never run through this.
|
|
152
|
+
* A non-finite value throws (Rule #2: no "0" / "NaN" price).
|
|
153
|
+
*/
|
|
154
|
+
export function formatComputedPrice(value, tickSize = null) {
|
|
155
|
+
if (!Number.isFinite(value)) {
|
|
156
|
+
throw new PerpError("EXCHANGE_ERROR", `computed price is not a finite number (${value})`);
|
|
157
|
+
}
|
|
158
|
+
const abs = Math.abs(value);
|
|
159
|
+
const decimals = tickSize !== null
|
|
160
|
+
? parseDecimal(tickSize).scale
|
|
161
|
+
: abs === 0 ? 0 : Math.min(20, Math.max(0, COMPUTED_PRICE_SIG_DIGITS - 1 - Math.floor(Math.log10(abs))));
|
|
162
|
+
const fixed = value.toFixed(decimals);
|
|
163
|
+
return fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
|
|
164
|
+
}
|
|
165
|
+
/** Floor a non-negative measured amount (e.g. a verified fill) onto the lot grid; "0…" when below one lot. */
|
|
166
|
+
export function floorToStep(value, step) {
|
|
167
|
+
const dec = (step.split(".")[1] ?? "").length;
|
|
168
|
+
const f = 10 ** dec;
|
|
169
|
+
const plain = (Math.floor(Math.max(0, value) * f + 1e-9) / f).toFixed(dec);
|
|
170
|
+
return unitsToString(alignUnits(plain, step, "floor"), step);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The market's lot step from the venue market info (stepSize, else 10^-sizeDecimals).
|
|
174
|
+
* Unknown market or unknown lot → throws (Rule #2: never size orders off the typed decimals).
|
|
175
|
+
*/
|
|
176
|
+
export async function lotStepOf(adapter, symbol) {
|
|
177
|
+
// The adapter's own lot step from market metadata it already holds (Lighter: size decimals of
|
|
178
|
+
// its loaded market list) — no full market-list read per order.
|
|
179
|
+
const own = adapter.lotStepOf;
|
|
180
|
+
if (typeof own === "function")
|
|
181
|
+
return own.call(adapter, symbol);
|
|
182
|
+
const strip = (s) => s.toUpperCase().replace(/-PERP$/, "");
|
|
183
|
+
// The adapter's own symbol resolution when it has one (HL "GOOGL" → "km:GOOGL" on a HIP-3 dex).
|
|
184
|
+
const resolve = adapter.resolveSymbol;
|
|
185
|
+
const wanted = new Set([strip(symbol), ...(typeof resolve === "function" ? [strip(resolve.call(adapter, symbol))] : [])]);
|
|
186
|
+
const markets = await adapter.getMarkets();
|
|
187
|
+
let m = markets.find(x => wanted.has(strip(x.symbol)));
|
|
188
|
+
if (m === undefined) {
|
|
189
|
+
// HIP-3: the dex adapter lists "xyz:XYZ100" while callers (and its asset map) also use the
|
|
190
|
+
// bare "XYZ100". Only an unambiguous dex-prefixed match counts.
|
|
191
|
+
const bare = markets.filter(x => x.symbol.includes(":") && wanted.has(strip(x.symbol.split(":").pop())));
|
|
192
|
+
if (bare.length > 1) {
|
|
193
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: ${symbol} is ambiguous (${bare.map(x => x.symbol).join(", ")}) — lot step unknown`, { exchange: adapter.name });
|
|
194
|
+
}
|
|
195
|
+
m = bare[0];
|
|
196
|
+
}
|
|
197
|
+
if (m === undefined) {
|
|
198
|
+
throw new PerpError("SYMBOL_NOT_FOUND", `${adapter.name}: market ${symbol} not found — lot step unknown`, { exchange: adapter.name });
|
|
199
|
+
}
|
|
200
|
+
if (m.stepSize !== null)
|
|
201
|
+
return m.stepSize;
|
|
202
|
+
if (m.sizeDecimals !== null && Number.isInteger(m.sizeDecimals) && m.sizeDecimals >= 0) {
|
|
203
|
+
return m.sizeDecimals === 0 ? "1" : `0.${"0".repeat(m.sizeDecimals - 1)}1`;
|
|
204
|
+
}
|
|
205
|
+
throw new PerpError("EXCHANGE_ERROR", `${adapter.name} ${symbol}: the venue market info has no lot step (stepSize / sizeDecimals) — cannot size the order to the lot`, {
|
|
206
|
+
exchange: adapter.name,
|
|
207
|
+
remediation: `Check the market: perp -e ${adapter.name} market info ${symbol}`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QFEX adapter — native TradingV2 + legacy ExchangeAdapter shim
|
|
3
|
+
* (design §14.3–§14.6 as amended by §14.12; §10.7, §10.10, §13).
|
|
4
|
+
*
|
|
5
|
+
* Signer: imported HMAC API key only (QfexAgentMeta → ~/.perp/qfex-agents/<publicKey>.json).
|
|
6
|
+
* Orders go over the Trade WebSocket (ws-transport.ts); account reads, leverage and transfers
|
|
7
|
+
* over REST; deposit address / withdraw over the banker host.
|
|
8
|
+
*
|
|
9
|
+
* Order entry:
|
|
10
|
+
* - market orders: IOC LIMIT at alignProtectionPrice(slippagePrice(mark, side, bps), tick,
|
|
11
|
+
* side) — the venue's native MARKET has no user bound. The reference is the MDS
|
|
12
|
+
* market_stats mark of THAT symbol, refused (PRICE_STALE) when its own `time` is older than
|
|
13
|
+
* QFEX_MARK_MAX_AGE_MS; nothing else is substituted.
|
|
14
|
+
* - limit: GTC | IOC | FOK as LIMIT; ALO as order_type ALO + GTC.
|
|
15
|
+
* - precision, bands, status, allowed order types / TIFs: /refdata only. Off-tick prices and
|
|
16
|
+
* out-of-band prices are refused, never rounded or clamped; sizes are floored to the lot.
|
|
17
|
+
* - client order ids are generated (UUID); a GTC / ALO order is returned as acknowledged
|
|
18
|
+
* (pending) after its ACK — "open" comes only from getOrderStatus (a get_user_orders row).
|
|
19
|
+
* An ALO order the venue cancels right after the ACK (it would cross) is ORDER_REJECTED
|
|
20
|
+
* { reason: "postOnlyWouldCross" }.
|
|
21
|
+
* - IOC / FOK / market: final only on a venue terminal frame; a terminal frame with zero
|
|
22
|
+
* fill is ORDER_REJECTED { reason: "iocNoFill" }; no terminal frame in time → pending.
|
|
23
|
+
*
|
|
24
|
+
* - reduce-only (close / reduce / flatten, reduce-only IOC limits): `reduce_only: true`, and —
|
|
25
|
+
* because the venue accepts a reduce-only order larger than the position (Q2, signing.ts) —
|
|
26
|
+
* the adapter's own rule before every send (requireReducible) and an echo check after it.
|
|
27
|
+
*
|
|
28
|
+
* Refused for now (uniform CAPABILITY_UNSUPPORTED, see signing.ts constants):
|
|
29
|
+
* - a RESTING reduce-only order (GTC / ALO; capability reduceOnlyTif, and so `trade scale-tp`)
|
|
30
|
+
* until QFEX_RESTING_REDUCE_ONLY_VERIFIED;
|
|
31
|
+
* - unattended automation — bots, plans, TWAP, DCA, grid, funding-arb, trailing stop,
|
|
32
|
+
* background jobs (automationGate) — until QFEX_AUTOMATION_VERIFIED;
|
|
33
|
+
* - stop-limit and stops that open (the venue offers neither); reduce-only stop-MARKET orders
|
|
34
|
+
* (TAKE_PROFIT / STOP_LOSS, Q21 passed — placeStop) are placed;
|
|
35
|
+
* - amend of a reduce-only order (a plain LIMIT / ALO is amended — Q5 passed);
|
|
36
|
+
* - attached TP/SL (Q7);
|
|
37
|
+
* - markets not quoted in USD (sizing / risk / notional code assumes USD);
|
|
38
|
+
* - user-supplied client order ids (not unique on the venue).
|
|
39
|
+
*
|
|
40
|
+
* Funding: unverified (Q4) → getVenueMarkets()/getMarkets() report fundingRate null.
|
|
41
|
+
*/
|
|
42
|
+
import { PerpError } from "../../errors.js";
|
|
43
|
+
import type { WithdrawGate } from "../../settings.js";
|
|
44
|
+
import { type BuilderFeeGate } from "../builder-fee.js";
|
|
45
|
+
import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade } from "../interface.js";
|
|
46
|
+
import type { IocOutcome } from "../ioc-outcome.js";
|
|
47
|
+
import type { ResolvedSlippage } from "../slippage.js";
|
|
48
|
+
import type { TradingV2 } from "../trading-v2.js";
|
|
49
|
+
import type { VenueCreateContext, VenueDefinition } from "../venue.js";
|
|
50
|
+
import type { AmendRequest, CancelResult, LeverageResult, Network, OrderRef, OrderRequest, OrderResult, VenueMarket } from "../venue-types.js";
|
|
51
|
+
import { type QfexReduceOnlyWire } from "./signing.js";
|
|
52
|
+
import { qfexPendingRemediation, type QfexOrderResponse } from "./ws-transport.js";
|
|
53
|
+
/** A mark price entry older than this (its own venue `time`) is not used as an order reference. */
|
|
54
|
+
export declare const QFEX_MARK_MAX_AGE_MS = 10000;
|
|
55
|
+
/** Client order id sent to the venue: a fresh UUID (the format the venue itself generates). */
|
|
56
|
+
export declare function qfexClientOrderId(): string;
|
|
57
|
+
/**
|
|
58
|
+
* JSON number → plain decimal string (never exponent notation). The venue sends decimals as
|
|
59
|
+
* JSON numbers, i.e. binary doubles, and some carry the venue's own float noise (live
|
|
60
|
+
* 2026-10-05 `/user/positions` average_price 4142.099999999999 for a 4142.1 entry). A double
|
|
61
|
+
* represents every decimal of up to 15 significant digits exactly-once (IEEE 754 binary64
|
|
62
|
+
* DBL_DIG = 15), so the value is read at 15 significant digits: noise in the 16th/17th digit
|
|
63
|
+
* is dropped and no real venue decimal is altered. Fields with a known grid (order price /
|
|
64
|
+
* quantity) are quantized to the instrument tick / lot instead (fromWireNumber).
|
|
65
|
+
*/
|
|
66
|
+
export declare function plainNumber(n: unknown, what: string): string;
|
|
67
|
+
export interface QfexAddOrderWire {
|
|
68
|
+
symbol: string;
|
|
69
|
+
side: "BUY" | "SELL";
|
|
70
|
+
order_type: "LIMIT" | "ALO";
|
|
71
|
+
order_time_in_force: "GTC" | "IOC" | "FOK";
|
|
72
|
+
quantity: number;
|
|
73
|
+
price: number;
|
|
74
|
+
client_order_id: string;
|
|
75
|
+
reduce_only?: boolean | number;
|
|
76
|
+
[k: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The frame of a reduce-only order that fails the echo check, or null when the order is confirmed
|
|
80
|
+
* as reduce-only. The rule, from production frames (2026-10-05, signing.ts Q2):
|
|
81
|
+
* - the ACK and every other NON-FILL status frame (CANCELLED, IOC_CANCELLED, MODIFIED, a
|
|
82
|
+
* post-ACK rejection, …) describes the ORDER and must carry `reduce_only: true`;
|
|
83
|
+
* - a fill-carrying frame (status FILLED) describes the TRADE and is NOT judged: the venue sends
|
|
84
|
+
* `reduce_only: false` on it for an order whose ACK and CANCELLED frames both carry `true`;
|
|
85
|
+
* - an order with no non-fill frame at all has nothing that confirms the flag, so its frames are
|
|
86
|
+
* judged as they are (a missing confirmation is never read as confirmed).
|
|
87
|
+
*/
|
|
88
|
+
export declare function reduceOnlyUnconfirmedFrame(responses: readonly QfexOrderResponse[]): QfexOrderResponse | null;
|
|
89
|
+
/**
|
|
90
|
+
* add_order params. `take_profit` / `stop_loss` are never sent (no attached TP/SL). `reduce_only`
|
|
91
|
+
* is sent only for a reduce-only order, in the verified wire format — with the format unset a
|
|
92
|
+
* reduce-only order is refused here.
|
|
93
|
+
*/
|
|
94
|
+
export declare function buildAddOrderParams(o: {
|
|
95
|
+
venueSymbol: string;
|
|
96
|
+
side: "buy" | "sell";
|
|
97
|
+
orderType: "LIMIT" | "ALO";
|
|
98
|
+
tif: "GTC" | "IOC" | "FOK";
|
|
99
|
+
quantity: string;
|
|
100
|
+
price: string;
|
|
101
|
+
clientOrderId: string;
|
|
102
|
+
reduceOnly: boolean;
|
|
103
|
+
}, reduceOnlyWire: QfexReduceOnlyWire | null): QfexAddOrderWire;
|
|
104
|
+
export declare class QfexAdapter implements ExchangeAdapter, TradingV2 {
|
|
105
|
+
readonly name: "qfex";
|
|
106
|
+
readonly aliases: readonly ["qf"];
|
|
107
|
+
readonly venue: VenueDefinition;
|
|
108
|
+
readonly network: Network;
|
|
109
|
+
private readonly caps;
|
|
110
|
+
private readonly client;
|
|
111
|
+
private readonly meta;
|
|
112
|
+
private readonly credentials;
|
|
113
|
+
private instruments;
|
|
114
|
+
/**
|
|
115
|
+
* Builder-code gate (settings builder.qfex; no approval step); null = not configured. Set by
|
|
116
|
+
* the venue definition's create — the adapter never reads settings.
|
|
117
|
+
*/
|
|
118
|
+
builderFee: BuilderFeeGate | null;
|
|
119
|
+
/**
|
|
120
|
+
* settings.withdraw.qfex, set by the venue definition's create; null = not configured →
|
|
121
|
+
* withdraw() refuses. Enforced inside withdraw() so no caller can skip it.
|
|
122
|
+
*/
|
|
123
|
+
withdrawGate: WithdrawGate | null;
|
|
124
|
+
constructor(def: VenueDefinition, ctx: VenueCreateContext);
|
|
125
|
+
/** The account UUID every request of this adapter acts as. */
|
|
126
|
+
get accountId(): string;
|
|
127
|
+
private requireMeta;
|
|
128
|
+
/** The builder decision for the connection (the code rides on the session's auth, never per order). */
|
|
129
|
+
private builderDecision;
|
|
130
|
+
private session;
|
|
131
|
+
private instrumentMap;
|
|
132
|
+
private instrumentOf;
|
|
133
|
+
/** Refuse a market perp-cli must not send orders to, and an order type / TIF the symbol does not list. */
|
|
134
|
+
private requireOrderable;
|
|
135
|
+
/** Mark of ONE symbol from a single-symbol MDS subscription; PRICE_STALE when absent or older than QFEX_MARK_MAX_AGE_MS. */
|
|
136
|
+
private referenceMark;
|
|
137
|
+
private checkBand;
|
|
138
|
+
/**
|
|
139
|
+
* `minimum: "deferred"` — a reduce-only order: its minimum is checked in requireReducible, once
|
|
140
|
+
* the position is known (a full close of a sub-minimum residual is not refused here).
|
|
141
|
+
*/
|
|
142
|
+
private checkSize;
|
|
143
|
+
/** The uniform refusal of every reduce-only order while the wire format is unset; null now that Q2 set it. */
|
|
144
|
+
reduceOnlyGate(): PerpError | null;
|
|
145
|
+
/**
|
|
146
|
+
* The uniform refusal of every UNATTENDED flow (bots, plans, TWAP, DCA, grid, funding-arb,
|
|
147
|
+
* trailing stop, background jobs) while stops are refused: an automated strategy would have no
|
|
148
|
+
* protective exit on the venue. Manual commands never ask. Null once QFEX_AUTOMATION_VERIFIED.
|
|
149
|
+
*/
|
|
150
|
+
automationGate(): PerpError | null;
|
|
151
|
+
/**
|
|
152
|
+
* The uniform refusal of every stop order while Q21 is unset — and so of `--tp/--sl` and
|
|
153
|
+
* `trade tpsl`, whose legs are stops. Callers ask BEFORE an entry is sent (stopOrderGateOf).
|
|
154
|
+
* Null now that Q21 passed (QFEX_STOP_CLIENT_ID_ECHO).
|
|
155
|
+
*/
|
|
156
|
+
stopOrderGate(): PerpError | null;
|
|
157
|
+
/**
|
|
158
|
+
* One reduce-only stop-market order (add_order with order_type TAKE_PROFIT / STOP_LOSS).
|
|
159
|
+
* Docs stop_add_order: "The direction given must be the eventual direction of the triggered
|
|
160
|
+
* order", `price` = "Price for when the order is triggered", quantity "Must respect lot size
|
|
161
|
+
* but not min/max limits", order_time_in_force "Required for legacy reasons, the value will
|
|
162
|
+
* not be read", and "TAKE_PROFIT and STOP_LOSS orders cannot exceed your current position,
|
|
163
|
+
* attempting to do this could cause your order to be rejected or other stop orders to be
|
|
164
|
+
* cancelled". So, before sending (every refusal definite, nothing sent):
|
|
165
|
+
* - a non-zero position, the order side opposite to it;
|
|
166
|
+
* - size + Σ quantity of the other open stops of the SAME type on the symbol ≤ |position|
|
|
167
|
+
* (never makes the venue cancel another stop);
|
|
168
|
+
* - kind given (TP/SL legs) or, for a plain stop, from the trigger vs the MDS mark price
|
|
169
|
+
* (closing a long: below mark = STOP_LOSS, above = TAKE_PROFIT; a short: the mirror).
|
|
170
|
+
* After the send: the ACK must carry our client id and reduce_only true (production 2026-10-12);
|
|
171
|
+
* the row is then read back from get_user_orders (final "open" only with that row). The venue's
|
|
172
|
+
* repeating `stop_order_response` frames are a heartbeat and never read as state.
|
|
173
|
+
*/
|
|
174
|
+
private placeStop;
|
|
175
|
+
/** A TP / SL leg (separateOrders: legacy-trading-v2 placeTpSlLeg dispatches here). Stop-market only. */
|
|
176
|
+
placeTpSlLeg(leg: {
|
|
177
|
+
symbol: string;
|
|
178
|
+
closeSide: "buy" | "sell";
|
|
179
|
+
size: string;
|
|
180
|
+
trigger: string;
|
|
181
|
+
kind: "tp" | "sl";
|
|
182
|
+
limitPrice: string | null;
|
|
183
|
+
}): Promise<OrderResult>;
|
|
184
|
+
/**
|
|
185
|
+
* Orders left on symbols where the account holds NO position: resting reduce-only LIMIT / ALO
|
|
186
|
+
* orders and TAKE_PROFIT / STOP_LOSS stops. Production 2026-10-12 (Q2): the venue does NOT
|
|
187
|
+
* cancel a resting reduce-only order when the position closes; whether one the market reaches
|
|
188
|
+
* while flat would open a position is unproven. `symbol` null = every symbol.
|
|
189
|
+
*/
|
|
190
|
+
orphanedReduceOnlyOrders(symbol: string | null): Promise<Array<{
|
|
191
|
+
symbol: string;
|
|
192
|
+
orderId: string;
|
|
193
|
+
type: string;
|
|
194
|
+
side: string;
|
|
195
|
+
price: string;
|
|
196
|
+
quantity: string;
|
|
197
|
+
}>>;
|
|
198
|
+
/**
|
|
199
|
+
* The rule before EVERY reduce-only send. The venue accepts a reduce-only order larger than
|
|
200
|
+
* the position (Q2, signing.ts), so the size is enforced here, on live reads:
|
|
201
|
+
* (a) GET /user/positions has a non-zero position on the symbol — none → POSITION_NOT_FOUND;
|
|
202
|
+
* (b) the order side is opposite to it (long → sell, short → buy);
|
|
203
|
+
* (c) size ≤ |position| − Σ quantity_remaining of ALL this account's other open LIMIT / ALO
|
|
204
|
+
* orders on that symbol and side (get_user_orders rows) — a plain SELL consumes a long
|
|
205
|
+
* exactly like a reduce-only one, so both are counted;
|
|
206
|
+
* (d) size ≥ min_quantity, unless the order closes the whole (floored) position.
|
|
207
|
+
* A same-side open order whose row does not report `reduce_only` as a boolean is not in the
|
|
208
|
+
* verified row format (Q2) → refused, with the cancel command. Stop rows (TAKE_PROFIT / STOP_LOSS) are not counted: the
|
|
209
|
+
* venue's stops "act only to decrease position at the time of execution" (docs
|
|
210
|
+
* /qfex/order-types); they are reported as an execution note instead.
|
|
211
|
+
* Every refusal is definite — nothing was sent. The position is floored to the lot.
|
|
212
|
+
* Returns the execution notes of the check.
|
|
213
|
+
*/
|
|
214
|
+
private requireReducible;
|
|
215
|
+
/**
|
|
216
|
+
* A reduce-only order the venue did not confirm as reduce-only (`reduce_only` false or absent
|
|
217
|
+
* on an order frame — reduceOnlyUnconfirmedFrame — or on a read-back row of THIS order). The order may be live as a normal order, so the outcome is
|
|
218
|
+
* unknown (ORDER_PENDING, design §14.12 #4e: never re-sent). It is NOT cancelled here: a cancel
|
|
219
|
+
* is a second mutating command with its own unknown outcome and cannot undo a fill.
|
|
220
|
+
*/
|
|
221
|
+
private reduceOnlyEchoError;
|
|
222
|
+
getVenueMarkets(): Promise<VenueMarket[]>;
|
|
223
|
+
placeOrder(input: OrderRequest): Promise<OrderResult>;
|
|
224
|
+
/** Map a correlated add_order outcome to an OrderResult — fills from venue frames only, decimal math. */
|
|
225
|
+
private orderResultOf;
|
|
226
|
+
/**
|
|
227
|
+
* Final filled size of an order placed through the legacy shim — non-null ONLY when a venue
|
|
228
|
+
* terminal frame was received (design §14.12 #3). A pending result is never an outcome.
|
|
229
|
+
*/
|
|
230
|
+
iocOutcomeOf(raw: unknown): IocOutcome | null;
|
|
231
|
+
amendOrder(req: AmendRequest): Promise<OrderResult>;
|
|
232
|
+
/** The open-order row for a ref; a client id shared by several open orders is ambiguous and refused. */
|
|
233
|
+
private pick;
|
|
234
|
+
private openOrderResult;
|
|
235
|
+
private historicResult;
|
|
236
|
+
/** Open orders first (get_user_orders), then the historic store; null when neither has it. `source` says which one answered. */
|
|
237
|
+
private findOrder;
|
|
238
|
+
getOrderStatus(symbol: string, ref: OrderRef): Promise<OrderResult>;
|
|
239
|
+
cancelByRef(symbol: string, ref: OrderRef): Promise<CancelResult>;
|
|
240
|
+
cancelAllV2(symbol: string | null): Promise<CancelResult>;
|
|
241
|
+
/**
|
|
242
|
+
* How an order that disappeared from the open orders ended, from its /user/historic-orders row
|
|
243
|
+
* (Q6: the row may lag): "cancelled" / "filled" when the row proves it, else "not indexed" /
|
|
244
|
+
* the unproven status after HISTORY_SETTLE_TRIES reads HISTORY_SETTLE_DELAY_MS apart.
|
|
245
|
+
*/
|
|
246
|
+
/** After an unknown stop-cancel outcome: "cancelled"/"filled" only when the stop is no longer listed open
|
|
247
|
+
* AND its historic-orders row settles it; otherwise a reason string (the cancel stays pending). */
|
|
248
|
+
private settleUnknownStopCancel;
|
|
249
|
+
private settledByHistory;
|
|
250
|
+
applyLeverage(symbol: string, leverage: number, marginMode: "cross" | "isolated" | null): Promise<LeverageResult>;
|
|
251
|
+
getMarkets(): Promise<ExchangeMarketInfo[]>;
|
|
252
|
+
getOrderbook(symbol: string): Promise<{
|
|
253
|
+
bids: [string, string][];
|
|
254
|
+
asks: [string, string][];
|
|
255
|
+
}>;
|
|
256
|
+
getRecentTrades(): Promise<ExchangeTrade[]>;
|
|
257
|
+
/** Hourly funding windows (GET /funding/{symbol}, intervalMinutes 60). `limit` = hours back (default 24). */
|
|
258
|
+
getFundingHistory(symbol: string, limit?: number): Promise<{
|
|
259
|
+
time: number;
|
|
260
|
+
rate: string;
|
|
261
|
+
price: string | null;
|
|
262
|
+
}[]>;
|
|
263
|
+
getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
|
|
264
|
+
private userPositions;
|
|
265
|
+
/** Equity is the venue's own figure (GET /user/subaccounts/equity) — never recomputed from balance fields. */
|
|
266
|
+
getBalance(): Promise<ExchangeBalance>;
|
|
267
|
+
/**
|
|
268
|
+
* `position` is a signed quantity: positive = long, negative = short (the response has no
|
|
269
|
+
* side field; Q10). requireReducible reads the same sign for reduce-only orders. The mark
|
|
270
|
+
* comes from the MDS snapshot; the venue reports no liquidation price.
|
|
271
|
+
*/
|
|
272
|
+
getPositions(): Promise<ExchangePosition[]>;
|
|
273
|
+
getOpenOrders(): Promise<ExchangeOrder[]>;
|
|
274
|
+
getOrderHistory(limit?: number): Promise<ExchangeOrder[]>;
|
|
275
|
+
/** `order_timestamp` is unix seconds with a fraction (the same unit as order_response.update_time). */
|
|
276
|
+
getTradeHistory(limit?: number): Promise<ExchangeTrade[]>;
|
|
277
|
+
getFundingPayments(): Promise<ExchangeFundingPayment[]>;
|
|
278
|
+
marketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
|
|
279
|
+
reduceOnly: boolean;
|
|
280
|
+
slippage: ResolvedSlippage;
|
|
281
|
+
}): Promise<OrderResult>;
|
|
282
|
+
limitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
|
|
283
|
+
reduceOnly?: boolean;
|
|
284
|
+
tif?: string;
|
|
285
|
+
}): Promise<OrderResult>;
|
|
286
|
+
editOrder(symbol: string, orderId: string, price: string, size: string): Promise<OrderResult>;
|
|
287
|
+
cancelOrder(symbol: string, orderId: string): Promise<CancelResult>;
|
|
288
|
+
cancelAllOrders(symbol?: string): Promise<CancelResult>;
|
|
289
|
+
setLeverage(symbol: string, leverage: number, marginMode?: "cross" | "isolated"): Promise<LeverageResult>;
|
|
290
|
+
stopOrder(symbol: string, side: "buy" | "sell", size: string, triggerPrice: string, opts?: {
|
|
291
|
+
limitPrice?: string;
|
|
292
|
+
reduceOnly?: boolean;
|
|
293
|
+
triggerType?: "mark" | "last" | "mid";
|
|
294
|
+
}): Promise<OrderResult>;
|
|
295
|
+
/**
|
|
296
|
+
* Deposit address for USDC on Arbitrum One (banker GET /address). The address is returned
|
|
297
|
+
* only when the venue confirms exactly that network and asset — anything else is refused,
|
|
298
|
+
* because funds sent on another network / asset are lost.
|
|
299
|
+
*/
|
|
300
|
+
depositAddress(): Promise<{
|
|
301
|
+
address: string;
|
|
302
|
+
network: string;
|
|
303
|
+
asset: string;
|
|
304
|
+
}>;
|
|
305
|
+
/**
|
|
306
|
+
* Withdraw USDC to an Arbitrum One address (banker POST /withdraw — NOT idempotent).
|
|
307
|
+
* Authority is the API key alone, so the local withdraw gate (settings.withdraw.qfex:
|
|
308
|
+
* allowlist, maxPerTx, maxPerDay) is enforced HERE; absent → refused. The venue may still
|
|
309
|
+
* refuse: QFEX documents that withdrawals need 2FA on the web app.
|
|
310
|
+
* No balance pre-check: the venue's Withdrawable Balance is not exposed by the API and
|
|
311
|
+
* `available_balance` is a different quantity — the venue's own 400 is the answer.
|
|
312
|
+
*/
|
|
313
|
+
withdraw(amount: string, destination: string, opts?: {
|
|
314
|
+
assetId?: number;
|
|
315
|
+
routeType?: number;
|
|
316
|
+
}): Promise<{
|
|
317
|
+
accepted: true;
|
|
318
|
+
amount: string;
|
|
319
|
+
address: string;
|
|
320
|
+
network: string;
|
|
321
|
+
asset: "USDC";
|
|
322
|
+
fee: null;
|
|
323
|
+
note: string;
|
|
324
|
+
}>;
|
|
325
|
+
/** The user's accounts (GET /user/subaccounts/balance): id, primary flag, available balance. */
|
|
326
|
+
listAccounts(): Promise<Array<{
|
|
327
|
+
accountId: string;
|
|
328
|
+
isPrimary: boolean;
|
|
329
|
+
available: string;
|
|
330
|
+
}>>;
|
|
331
|
+
/**
|
|
332
|
+
* Move USDC between two of the user's accounts (POST /user/transfer → 204). Idempotency and
|
|
333
|
+
* the needed permission are unverified (Q16): an unknown outcome is ORDER_PENDING, never re-sent.
|
|
334
|
+
*/
|
|
335
|
+
transferBetweenAccounts(amount: string, fromAccountId: string, toAccountId: string): Promise<{
|
|
336
|
+
amount: string;
|
|
337
|
+
from: string;
|
|
338
|
+
to: string;
|
|
339
|
+
}>;
|
|
340
|
+
}
|
|
341
|
+
export { qfexPendingRemediation };
|