perp-cli 0.12.15 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -31
- package/dist/_stderr-filter.js +0 -1
- package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
- package/dist/agent-wallet/lighter-keystore.js +92 -10
- package/dist/agent-wallet/passphrase.d.ts +1 -1
- package/dist/agent-wallet/passphrase.js +16 -3
- package/dist/agent-wallet/store.d.ts +37 -4
- package/dist/agent-wallet/store.js +132 -0
- package/dist/agent-wallet/venue-keystore.d.ts +14 -0
- package/dist/agent-wallet/venue-keystore.js +121 -0
- package/dist/api/public/hyperliquid.js +4 -4
- package/dist/api/public/lighter.js +5 -5
- package/dist/api/public/pacifica.d.ts +0 -1
- package/dist/api/public/pacifica.js +4 -13
- package/dist/api/public/urls.d.ts +9 -1
- package/dist/api/public/urls.js +35 -13
- package/dist/arb/file-lock.d.ts +11 -0
- package/dist/arb/file-lock.js +74 -0
- package/dist/arb/index.d.ts +2 -2
- package/dist/arb/index.js +2 -2
- package/dist/arb/pending-entry.d.ts +32 -0
- package/dist/arb/pending-entry.js +77 -0
- package/dist/arb/sizing.d.ts +6 -13
- package/dist/arb/sizing.js +12 -51
- package/dist/arb/state.d.ts +45 -12
- package/dist/arb/state.js +214 -42
- package/dist/arb/utils.d.ts +20 -6
- package/dist/arb/utils.js +43 -39
- package/dist/bot/conditions.d.ts +4 -4
- package/dist/bot/conditions.js +11 -6
- package/dist/bot/config.d.ts +17 -1
- package/dist/bot/config.js +48 -12
- package/dist/bot/engine.d.ts +61 -2
- package/dist/bot/engine.js +610 -144
- package/dist/bot/fill-size.d.ts +7 -0
- package/dist/bot/fill-size.js +18 -0
- package/dist/bot/index.d.ts +1 -1
- package/dist/bot/index.js +1 -1
- package/dist/bot/perp-order.d.ts +99 -0
- package/dist/bot/perp-order.js +299 -0
- package/dist/bot/presets.d.ts +7 -0
- package/dist/bot/presets.js +17 -0
- package/dist/bot/quarantine.d.ts +75 -0
- package/dist/bot/quarantine.js +190 -0
- package/dist/bot/quote-rules.d.ts +23 -0
- package/dist/bot/quote-rules.js +69 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
- package/dist/bot/strategies/avellaneda-mm.js +72 -70
- package/dist/bot/strategies/basis-arb.js +2 -0
- package/dist/bot/strategies/dca-strategy.d.ts +2 -0
- package/dist/bot/strategies/dca-strategy.js +5 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
- package/dist/bot/strategies/funding-arb-strategy.js +571 -380
- package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
- package/dist/bot/strategies/funding-arb-utils.js +159 -195
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +258 -47
- package/dist/bot/strategies/funding-auto.js +215 -194
- package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
- package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
- package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
- package/dist/bot/strategies/spot-perp-executor.js +319 -120
- package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
- package/dist/bot/strategies/spot-perp-state.js +70 -0
- package/dist/bot/strategy-types.d.ts +6 -0
- package/dist/bot/tui/BotDashboard.d.ts +7 -2
- package/dist/bot/tui/BotDashboard.js +6 -3
- package/dist/bridge-engine.d.ts +120 -19
- package/dist/bridge-engine.js +710 -422
- package/dist/bridge-signers.d.ts +54 -0
- package/dist/bridge-signers.js +161 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +11 -0
- package/dist/cli-flags.d.ts +50 -0
- package/dist/cli-flags.js +104 -0
- package/dist/cli-program.d.ts +35 -0
- package/dist/cli-program.js +286 -0
- package/dist/cli-spec.js +1 -1
- package/dist/client-id-tracker.js +11 -2
- package/dist/commands/account.d.ts +7 -1
- package/dist/commands/account.js +74 -84
- package/dist/commands/agent-import.d.ts +114 -0
- package/dist/commands/agent-import.js +794 -0
- package/dist/commands/agent.d.ts +232 -2
- package/dist/commands/agent.js +1858 -955
- package/dist/commands/alerts.d.ts +1 -0
- package/dist/commands/alerts.js +31 -28
- package/dist/commands/arb/index.d.ts +60 -1
- package/dist/commands/arb/index.js +381 -384
- package/dist/commands/arb-auto.d.ts +132 -4
- package/dist/commands/arb-auto.js +1474 -967
- package/dist/commands/arb.d.ts +3 -1
- package/dist/commands/arb.js +62 -30
- package/dist/commands/bot.js +127 -47
- package/dist/commands/bridge.d.ts +2 -1
- package/dist/commands/bridge.js +339 -192
- package/dist/commands/builder.d.ts +55 -0
- package/dist/commands/builder.js +387 -0
- package/dist/commands/funds-onchain.d.ts +32 -0
- package/dist/commands/funds-onchain.js +286 -0
- package/dist/commands/funds.d.ts +25 -0
- package/dist/commands/funds.js +939 -245
- package/dist/commands/history.js +72 -45
- package/dist/commands/init.d.ts +107 -15
- package/dist/commands/init.js +1116 -249
- package/dist/commands/jobs.d.ts +2 -1
- package/dist/commands/jobs.js +77 -15
- package/dist/commands/manage.js +136 -91
- package/dist/commands/market.d.ts +4 -1
- package/dist/commands/market.js +35 -32
- package/dist/commands/outcome.d.ts +10 -0
- package/dist/commands/outcome.js +396 -0
- package/dist/commands/plan.js +52 -12
- package/dist/commands/portfolio.d.ts +1 -0
- package/dist/commands/portfolio.js +60 -72
- package/dist/commands/rebalance.d.ts +2 -1
- package/dist/commands/rebalance.js +33 -43
- package/dist/commands/risk.js +46 -19
- package/dist/commands/settings.d.ts +1 -0
- package/dist/commands/settings.js +246 -51
- package/dist/commands/trade.d.ts +50 -0
- package/dist/commands/trade.js +1943 -695
- package/dist/commands/wallet.d.ts +30 -1
- package/dist/commands/wallet.js +316 -337
- package/dist/config.d.ts +60 -9
- package/dist/config.js +252 -82
- package/dist/constants.d.ts +29 -4
- package/dist/constants.js +62 -20
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.js +9 -6
- package/dist/dashboard/ui.js +6 -4
- package/dist/dashboard/ws-feeds.js +20 -8
- package/dist/dex-asset-map.js +5 -3
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +194 -0
- package/dist/event-stream.js +75 -19
- package/dist/evm-master-tx.d.ts +59 -0
- package/dist/evm-master-tx.js +154 -0
- package/dist/exchanges/adapter-network.d.ts +10 -0
- package/dist/exchanges/adapter-network.js +10 -0
- package/dist/exchanges/arcus/adapter.d.ts +196 -0
- package/dist/exchanges/arcus/adapter.js +1092 -0
- package/dist/exchanges/arcus/agent.d.ts +55 -0
- package/dist/exchanges/arcus/agent.js +188 -0
- package/dist/exchanges/arcus/client.d.ts +63 -0
- package/dist/exchanges/arcus/client.js +285 -0
- package/dist/exchanges/arcus/public.d.ts +65 -0
- package/dist/exchanges/arcus/public.js +138 -0
- package/dist/exchanges/arcus/signing.d.ts +176 -0
- package/dist/exchanges/arcus/signing.js +288 -0
- package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
- package/dist/exchanges/arcus/spot-chain.js +241 -0
- package/dist/exchanges/arcus/spot-router.d.ts +177 -0
- package/dist/exchanges/arcus/spot-router.js +183 -0
- package/dist/exchanges/arcus/spot.d.ts +109 -0
- package/dist/exchanges/arcus/spot.js +645 -0
- package/dist/exchanges/aster-funds.d.ts +127 -0
- package/dist/exchanges/aster-funds.js +252 -0
- package/dist/exchanges/aster-nonce.d.ts +2 -0
- package/dist/exchanges/aster-nonce.js +107 -0
- package/dist/exchanges/aster-spot.d.ts +117 -0
- package/dist/exchanges/aster-spot.js +686 -0
- package/dist/exchanges/aster-typed-data.d.ts +133 -7
- package/dist/exchanges/aster-typed-data.js +121 -7
- package/dist/exchanges/aster.d.ts +219 -6
- package/dist/exchanges/aster.js +1023 -153
- package/dist/exchanges/builder-fee.d.ts +254 -0
- package/dist/exchanges/builder-fee.js +421 -0
- package/dist/exchanges/canonical-symbols.d.ts +32 -0
- package/dist/exchanges/canonical-symbols.js +96 -0
- package/dist/exchanges/capabilities.d.ts +7 -1
- package/dist/exchanges/capabilities.js +7 -0
- package/dist/exchanges/hl-signing.d.ts +165 -0
- package/dist/exchanges/hl-signing.js +158 -0
- package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
- package/dist/exchanges/hyperliquid-outcome.js +646 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
- package/dist/exchanges/hyperliquid-spot.js +308 -117
- package/dist/exchanges/hyperliquid.d.ts +462 -57
- package/dist/exchanges/hyperliquid.js +1575 -449
- package/dist/exchanges/index.d.ts +6 -0
- package/dist/exchanges/index.js +4 -0
- package/dist/exchanges/interface.d.ts +63 -10
- package/dist/exchanges/interface.js +18 -3
- package/dist/exchanges/ioc-outcome.d.ts +44 -0
- package/dist/exchanges/ioc-outcome.js +57 -0
- package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
- package/dist/exchanges/legacy-trading-v2.js +940 -0
- package/dist/exchanges/lighter-public.d.ts +43 -0
- package/dist/exchanges/lighter-public.js +276 -0
- package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
- package/dist/exchanges/lighter-rh-deposit.js +185 -0
- package/dist/exchanges/lighter-signer.d.ts +123 -0
- package/dist/exchanges/lighter-signer.js +215 -0
- package/dist/exchanges/lighter-spot.d.ts +91 -13
- package/dist/exchanges/lighter-spot.js +595 -233
- package/dist/exchanges/lighter.d.ts +611 -39
- package/dist/exchanges/lighter.js +1890 -649
- package/dist/exchanges/ondo/adapter.d.ts +166 -0
- package/dist/exchanges/ondo/adapter.js +835 -0
- package/dist/exchanges/ondo/agent.d.ts +31 -0
- package/dist/exchanges/ondo/agent.js +101 -0
- package/dist/exchanges/ondo/client.d.ts +60 -0
- package/dist/exchanges/ondo/client.js +297 -0
- package/dist/exchanges/ondo/funds.d.ts +122 -0
- package/dist/exchanges/ondo/funds.js +290 -0
- package/dist/exchanges/ondo/public.d.ts +95 -0
- package/dist/exchanges/ondo/public.js +214 -0
- package/dist/exchanges/ondo/spot.d.ts +127 -0
- package/dist/exchanges/ondo/spot.js +392 -0
- package/dist/exchanges/outcome-interface.d.ts +140 -0
- package/dist/exchanges/outcome-interface.js +16 -0
- package/dist/exchanges/pacifica-spot.d.ts +83 -0
- package/dist/exchanges/pacifica-spot.js +324 -0
- package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
- package/dist/exchanges/pacifica-typed-data.js +57 -9
- package/dist/exchanges/pacifica.d.ts +288 -4
- package/dist/exchanges/pacifica.js +1013 -76
- package/dist/exchanges/precision.d.ts +47 -0
- package/dist/exchanges/precision.js +209 -0
- package/dist/exchanges/qfex/adapter.d.ts +341 -0
- package/dist/exchanges/qfex/adapter.js +1383 -0
- package/dist/exchanges/qfex/agent.d.ts +58 -0
- package/dist/exchanges/qfex/agent.js +188 -0
- package/dist/exchanges/qfex/client.d.ts +54 -0
- package/dist/exchanges/qfex/client.js +138 -0
- package/dist/exchanges/qfex/errors.d.ts +70 -0
- package/dist/exchanges/qfex/errors.js +236 -0
- package/dist/exchanges/qfex/public.d.ts +122 -0
- package/dist/exchanges/qfex/public.js +268 -0
- package/dist/exchanges/qfex/signing.d.ts +160 -0
- package/dist/exchanges/qfex/signing.js +283 -0
- package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
- package/dist/exchanges/qfex/ws-transport.js +798 -0
- package/dist/exchanges/rate-limiter.d.ts +12 -0
- package/dist/exchanges/rate-limiter.js +64 -0
- package/dist/exchanges/registry.d.ts +14 -2
- package/dist/exchanges/registry.js +20 -32
- package/dist/exchanges/slippage.d.ts +37 -0
- package/dist/exchanges/slippage.js +70 -0
- package/dist/exchanges/spot-factory.d.ts +166 -0
- package/dist/exchanges/spot-factory.js +431 -0
- package/dist/exchanges/spot-interface.d.ts +98 -4
- package/dist/exchanges/spot-interface.js +27 -1
- package/dist/exchanges/spot-result.d.ts +79 -0
- package/dist/exchanges/spot-result.js +273 -0
- package/dist/exchanges/trading-v2.d.ts +117 -0
- package/dist/exchanges/trading-v2.js +301 -0
- package/dist/exchanges/venue-types.d.ts +231 -0
- package/dist/exchanges/venue-types.js +87 -0
- package/dist/exchanges/venue.d.ts +211 -0
- package/dist/exchanges/venue.js +132 -0
- package/dist/exchanges/venues/arcus.d.ts +8 -0
- package/dist/exchanges/venues/arcus.js +109 -0
- package/dist/exchanges/venues/aster.d.ts +49 -0
- package/dist/exchanges/venues/aster.js +176 -0
- package/dist/exchanges/venues/create-helpers.d.ts +18 -0
- package/dist/exchanges/venues/create-helpers.js +199 -0
- package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
- package/dist/exchanges/venues/hyperliquid.js +126 -0
- package/dist/exchanges/venues/index.d.ts +16 -0
- package/dist/exchanges/venues/index.js +26 -0
- package/dist/exchanges/venues/lighter-family.d.ts +92 -0
- package/dist/exchanges/venues/lighter-family.js +166 -0
- package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
- package/dist/exchanges/venues/lighter-rh.js +58 -0
- package/dist/exchanges/venues/lighter.d.ts +6 -0
- package/dist/exchanges/venues/lighter.js +55 -0
- package/dist/exchanges/venues/ondo.d.ts +34 -0
- package/dist/exchanges/venues/ondo.js +140 -0
- package/dist/exchanges/venues/pacifica.d.ts +8 -0
- package/dist/exchanges/venues/pacifica.js +95 -0
- package/dist/exchanges/venues/qfex.d.ts +28 -0
- package/dist/exchanges/venues/qfex.js +132 -0
- package/dist/execution-log.js +10 -2
- package/dist/funding/legacy-public.d.ts +31 -0
- package/dist/funding/legacy-public.js +369 -0
- package/dist/funding/normalize.d.ts +28 -16
- package/dist/funding/normalize.js +61 -29
- package/dist/funding/rates.d.ts +46 -11
- package/dist/funding/rates.js +228 -277
- package/dist/guardrail/perp-guardrail.js +0 -0
- package/dist/index.js +157 -682
- package/dist/jobs.d.ts +84 -6
- package/dist/jobs.js +239 -41
- package/dist/landing.d.ts +14 -0
- package/dist/landing.js +36 -0
- package/dist/mcp-server.d.ts +14 -4
- package/dist/mcp-server.js +820 -402
- package/dist/mcp-trade-guard.d.ts +16 -0
- package/dist/mcp-trade-guard.js +59 -0
- package/dist/pacifica/client.d.ts +41 -10
- package/dist/pacifica/client.js +167 -63
- package/dist/pacifica/constants.d.ts +0 -1
- package/dist/pacifica/constants.js +0 -5
- package/dist/pacifica/deposit.d.ts +30 -3
- package/dist/pacifica/deposit.js +119 -2
- package/dist/pacifica/faucet.d.ts +29 -0
- package/dist/pacifica/faucet.js +100 -0
- package/dist/pacifica/sign-tx.d.ts +15 -0
- package/dist/pacifica/sign-tx.js +41 -0
- package/dist/pacifica/types/account.d.ts +28 -4
- package/dist/pacifica/types/lake.d.ts +20 -5
- package/dist/pacifica/types/lake.js +4 -0
- package/dist/pacifica/types/market.d.ts +5 -0
- package/dist/pacifica/types/order.d.ts +4 -1
- package/dist/pacifica/types/position.d.ts +9 -1
- package/dist/plan-executor.d.ts +10 -5
- package/dist/plan-executor.js +165 -54
- package/dist/position-history.js +13 -0
- package/dist/rebalance.d.ts +26 -2
- package/dist/rebalance.js +86 -7
- package/dist/relay-swap.d.ts +232 -0
- package/dist/relay-swap.js +630 -0
- package/dist/risk.js +5 -1
- package/dist/secret-env.d.ts +15 -0
- package/dist/secret-env.js +36 -0
- package/dist/settings.d.ts +169 -6
- package/dist/settings.js +219 -38
- package/dist/signer/ows-evm.d.ts +13 -0
- package/dist/signer/ows-evm.js +22 -0
- package/dist/smart-order.d.ts +9 -0
- package/dist/smart-order.js +53 -8
- package/dist/strategies/dca.d.ts +6 -2
- package/dist/strategies/dca.js +66 -26
- package/dist/strategies/funding-arb.d.ts +11 -1
- package/dist/strategies/funding-arb.js +202 -89
- package/dist/strategies/grid.d.ts +31 -0
- package/dist/strategies/grid.js +89 -11
- package/dist/strategies/pending-stop.d.ts +8 -0
- package/dist/strategies/pending-stop.js +12 -0
- package/dist/strategies/split-order.d.ts +45 -17
- package/dist/strategies/split-order.js +157 -91
- package/dist/strategies/trailing-stop.d.ts +2 -1
- package/dist/strategies/trailing-stop.js +52 -6
- package/dist/strategies/twap.d.ts +6 -1
- package/dist/strategies/twap.js +39 -11
- package/dist/trade-validator.d.ts +22 -0
- package/dist/trade-validator.js +119 -15
- package/dist/utils/numeric.d.ts +46 -0
- package/dist/utils/numeric.js +79 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +96 -19
- package/dist/withdraw-gate.d.ts +49 -0
- package/dist/withdraw-gate.js +131 -0
- package/package.json +15 -6
- package/skills/perp-cli/SKILL.md +338 -134
- package/skills/perp-cli/references/agent-operations.md +181 -92
- package/skills/perp-cli/references/commands.md +475 -132
- package/skills/perp-cli/references/strategies.md +44 -38
- package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
- package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
- package/skills/perp-cli/scripts/preflight.sh +21 -14
- package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
- package/skills/perp-cli/scripts/validate-arb.sh +33 -16
- package/vendor/lighter-signer/SHA256SUMS +2 -0
- package/vendor/lighter-signer/VERSION +8 -0
- package/vendor/lighter-signer/lighter-signer.wasm +0 -0
- package/vendor/lighter-signer/wasm_exec.js +575 -0
- package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
- package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { StrategyContext } from "../strategy-types.js";
|
|
6
6
|
import type { ExchangeAdapter } from "../../exchanges/index.js";
|
|
7
|
-
import type { SpotAdapter } from "../../exchanges/spot-interface.js";
|
|
7
|
+
import type { SpotAdapter, SpotMarketInfo } from "../../exchanges/spot-interface.js";
|
|
8
|
+
import type { VenueMarket } from "../../exchanges/venue-types.js";
|
|
9
|
+
import "../../exchanges/venues/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Funding strategies refuse a venue whose funding rate is unverified as a leg (design §14.12
|
|
12
|
+
* #6): every adapter of the run (primary + extraAdapters) is checked when the strategy starts.
|
|
13
|
+
* Adapter names that are not registered venues (test doubles) are not venues and are skipped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function assertFundingArbLegs(ctx: StrategyContext): void;
|
|
8
16
|
export type RateEntry = {
|
|
9
17
|
rate: number;
|
|
10
18
|
price: number;
|
|
@@ -15,12 +23,26 @@ export type RateEntry = {
|
|
|
15
23
|
export type RateMap = Map<string, Map<string, RateEntry>>;
|
|
16
24
|
/** Build adapter map from primary adapter + extraAdapters in context state. */
|
|
17
25
|
export declare function buildAdapterMap(ctx: StrategyContext): Map<string, ExchangeAdapter>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export declare function
|
|
26
|
+
/**
|
|
27
|
+
* Move the spot venue's quote asset (quoteAssets[0]) perp → spot. Returns false when
|
|
28
|
+
* spot shares perp margin (no-op — callers must not log "Transferred"); throws when
|
|
29
|
+
* the venue has no transfer.
|
|
30
|
+
*/
|
|
31
|
+
export declare function transferUsdcToSpot(spotAdapter: SpotAdapter, amount: number): Promise<boolean>;
|
|
32
|
+
/** Spot → perp counterpart of transferUsdcToSpot. */
|
|
33
|
+
export declare function transferUsdcToPerp(spotAdapter: SpotAdapter, amount: number): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Bot spot↔perp candidates: spot bases on `spotVenue` matched to each perp venue by
|
|
36
|
+
* canonical symbol + price-deviation guard (`matchSpotPerp`). Bare-ticker equality is
|
|
37
|
+
* not enough. Bot positions store one symbol and derive the perp leg with
|
|
38
|
+
* `getPerpSymbol(base)`, so a canonical pair whose perp symbol differs from the spot
|
|
39
|
+
* base (e.g. UBTC ↔ BTC) is reported and skipped rather than traded under the wrong
|
|
40
|
+
* perp symbol. Returns perp venue → matched spot bases (upper-case).
|
|
41
|
+
*
|
|
42
|
+
* `perpMarketsCache` lets one scan reuse each perp venue's public market fetch.
|
|
43
|
+
* A perp venue whose markets cannot be fetched is logged and gets no candidates.
|
|
44
|
+
*/
|
|
45
|
+
export declare function botSpotPerpMatches(spotVenue: string, spotMarkets: readonly SpotMarketInfo[], perpVenues: readonly string[], perpMarketsCache: Map<string, Promise<VenueMarket[]>>, log: (msg: string) => void): Promise<Map<string, Set<string>>>;
|
|
24
46
|
/** Resolve perp symbol for a given base symbol on an exchange. */
|
|
25
47
|
export declare function getPerpSymbol(baseSymbol: string, _exchangeName: string): string;
|
|
26
48
|
/** Look up rate from ratesByExchange, trying symbol, symbol-PERP, and symbol without -PERP. */
|
|
@@ -29,15 +51,6 @@ export declare function findRate(ratesByExchange: RateMap, exchange: string, sym
|
|
|
29
51
|
export declare function matchSymbol(s: string, target: string): boolean;
|
|
30
52
|
/** Get a price estimate for a symbol from the perp adapter. */
|
|
31
53
|
export declare function getPriceEstimate(perpAdapter: ExchangeAdapter, perpSymbol: string, fallbackSymbol: string): Promise<number>;
|
|
32
|
-
export interface RecoveredPosition {
|
|
33
|
-
symbol: string;
|
|
34
|
-
mode: "spot-perp" | "perp-perp";
|
|
35
|
-
longExchange: string;
|
|
36
|
-
shortExchange: string;
|
|
37
|
-
size: string;
|
|
38
|
-
}
|
|
39
|
-
/** Recover arb positions from exchange state (perp-perp + spot-perp, same + cross exchange). */
|
|
40
|
-
export declare function recoverArbPositions(adapters: Map<string, ExchangeAdapter>, log: (msg: string) => void): Promise<RecoveredPosition[]>;
|
|
41
54
|
export interface FundingScore {
|
|
42
55
|
symbol: string;
|
|
43
56
|
exchange: string;
|
|
@@ -46,18 +59,24 @@ export interface FundingScore {
|
|
|
46
59
|
annualized: number;
|
|
47
60
|
payments: number;
|
|
48
61
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
/**
|
|
63
|
+
* true when a reduce-only close was rejected because there is no position left to reduce.
|
|
64
|
+
* Prefers the typed signal: placePerpVerified's ORDER_REJECTED with details.reduceOnlyFlat
|
|
65
|
+
* (flat before and after the order — "(no X position to reduce)"). Venue texts that carry the
|
|
66
|
+
* same meaning are matched too: HL "Reduce only order would increase position", Aster -2022
|
|
67
|
+
* / "ReduceOnly Order is rejected". Only ever applied to a DEFINITE rejection (an
|
|
68
|
+
* ORDER_PENDING outcome is never "gone").
|
|
69
|
+
*/
|
|
70
|
+
export declare function isPositionGone(e: unknown): boolean;
|
|
55
71
|
/** Score a symbol's funding rate using history. Returns null if insufficient data. */
|
|
56
72
|
export declare function scoreFunding(adapter: ExchangeAdapter, symbol: string, exchangeName: string, periods: number, cache?: Map<string, {
|
|
57
73
|
score: FundingScore;
|
|
58
74
|
ts: number;
|
|
59
75
|
}>): Promise<FundingScore | null>;
|
|
60
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Fetch funding rates from an exchange adapter. Failures propagate — callers
|
|
78
|
+
* record them per venue (an empty list would read as "venue has no markets").
|
|
79
|
+
*/
|
|
61
80
|
export declare function fetchRates(adapter: ExchangeAdapter, exchangeName: string): Promise<{
|
|
62
81
|
symbol: string;
|
|
63
82
|
rate: number;
|
|
@@ -66,3 +85,9 @@ export declare function fetchRates(adapter: ExchangeAdapter, exchangeName: strin
|
|
|
66
85
|
maxLeverage?: number;
|
|
67
86
|
fundingHours?: number;
|
|
68
87
|
}[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Cancel the open orders of `symbols` (the bot's own) on every venue in `adapters` — never an
|
|
90
|
+
* account-wide cancel. Each failure is logged (never "cancelled"); nothing is logged as
|
|
91
|
+
* cancelled when the bot traded no symbol.
|
|
92
|
+
*/
|
|
93
|
+
export declare function cancelBotSymbols(adapters: Map<string, ExchangeAdapter>, symbols: ReadonlySet<string>, tag: string, log: (msg: string) => void): Promise<void>;
|
|
@@ -3,7 +3,29 @@
|
|
|
3
3
|
* Extracted to keep individual strategy files under size limits.
|
|
4
4
|
*/
|
|
5
5
|
import { getFundingHours } from "../../funding.js";
|
|
6
|
+
import { sharedApiNetwork } from "../../api/public/urls.js";
|
|
6
7
|
import { annualizeHourlyRate } from "../../funding/normalize.js";
|
|
8
|
+
import { fetchPerpMarketsForMatching, matchSpotPerp, transferQuoteToPerp, transferQuoteToSpot } from "../../exchanges/spot-factory.js";
|
|
9
|
+
import { PerpError, extractErrorMessage } from "../../errors.js";
|
|
10
|
+
import { assertFundingArbVenue, tryResolveVenueId } from "../../exchanges/venue.js";
|
|
11
|
+
import "../../exchanges/venues/index.js";
|
|
12
|
+
/**
|
|
13
|
+
* Funding strategies refuse a venue whose funding rate is unverified as a leg (design §14.12
|
|
14
|
+
* #6): every adapter of the run (primary + extraAdapters) is checked when the strategy starts.
|
|
15
|
+
* Adapter names that are not registered venues (test doubles) are not venues and are skipped.
|
|
16
|
+
*/
|
|
17
|
+
export function assertFundingArbLegs(ctx) {
|
|
18
|
+
const names = new Set([ctx.adapter.name]);
|
|
19
|
+
const extra = ctx.state.get("extraAdapters");
|
|
20
|
+
if (extra !== undefined)
|
|
21
|
+
for (const a of extra.values())
|
|
22
|
+
names.add(a.name);
|
|
23
|
+
for (const name of names) {
|
|
24
|
+
if (tryResolveVenueId(name.split(":")[0]) === null)
|
|
25
|
+
continue;
|
|
26
|
+
assertFundingArbVenue(name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
7
29
|
// ── Adapter helpers ──
|
|
8
30
|
/** Build adapter map from primary adapter + extraAdapters in context state. */
|
|
9
31
|
export function buildAdapterMap(ctx) {
|
|
@@ -16,73 +38,64 @@ export function buildAdapterMap(ctx) {
|
|
|
16
38
|
}
|
|
17
39
|
return adapters;
|
|
18
40
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const { HyperliquidSpotAdapter } = await import("../../exchanges/hyperliquid-spot.js");
|
|
29
|
-
const { HyperliquidAdapter } = await import("../../exchanges/hyperliquid.js");
|
|
30
|
-
if (adapter instanceof HyperliquidAdapter) {
|
|
31
|
-
const instance = new HyperliquidSpotAdapter(adapter);
|
|
32
|
-
await instance.init();
|
|
33
|
-
spot = instance;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
else if (name === "lighter") {
|
|
37
|
-
const { LighterSpotAdapter } = await import("../../exchanges/lighter-spot.js");
|
|
38
|
-
const { LighterAdapter } = await import("../../exchanges/lighter.js");
|
|
39
|
-
if (adapter instanceof LighterAdapter) {
|
|
40
|
-
const instance = new LighterSpotAdapter(adapter);
|
|
41
|
-
await instance.init();
|
|
42
|
-
spot = instance;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (spot)
|
|
46
|
-
spotAdapterCache.set(name, spot);
|
|
47
|
-
return spot;
|
|
48
|
-
}
|
|
49
|
-
catch { /* not supported */ }
|
|
50
|
-
return null;
|
|
41
|
+
// ── Transfer helpers (§12.4 #4) ──
|
|
42
|
+
// Spot adapters come from the factory (src/exchanges/spot-factory.ts, getBotSpotAdapter).
|
|
43
|
+
/**
|
|
44
|
+
* Move the spot venue's quote asset (quoteAssets[0]) perp → spot. Returns false when
|
|
45
|
+
* spot shares perp margin (no-op — callers must not log "Transferred"); throws when
|
|
46
|
+
* the venue has no transfer.
|
|
47
|
+
*/
|
|
48
|
+
export async function transferUsdcToSpot(spotAdapter, amount) {
|
|
49
|
+
return transferQuoteToSpot(spotAdapter, amount);
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
/** Spot → perp counterpart of transferUsdcToSpot. */
|
|
52
|
+
export async function transferUsdcToPerp(spotAdapter, amount) {
|
|
53
|
+
return transferQuoteToPerp(spotAdapter, amount);
|
|
54
|
+
}
|
|
55
|
+
// ── Spot ↔ perp matching (§12.4 #6) ──
|
|
56
|
+
/**
|
|
57
|
+
* Bot spot↔perp candidates: spot bases on `spotVenue` matched to each perp venue by
|
|
58
|
+
* canonical symbol + price-deviation guard (`matchSpotPerp`). Bare-ticker equality is
|
|
59
|
+
* not enough. Bot positions store one symbol and derive the perp leg with
|
|
60
|
+
* `getPerpSymbol(base)`, so a canonical pair whose perp symbol differs from the spot
|
|
61
|
+
* base (e.g. UBTC ↔ BTC) is reported and skipped rather than traded under the wrong
|
|
62
|
+
* perp symbol. Returns perp venue → matched spot bases (upper-case).
|
|
63
|
+
*
|
|
64
|
+
* `perpMarketsCache` lets one scan reuse each perp venue's public market fetch.
|
|
65
|
+
* A perp venue whose markets cannot be fetched is logged and gets no candidates.
|
|
66
|
+
*/
|
|
67
|
+
export async function botSpotPerpMatches(spotVenue, spotMarkets, perpVenues, perpMarketsCache, log) {
|
|
68
|
+
const out = new Map();
|
|
69
|
+
for (const perpVenue of perpVenues) {
|
|
70
|
+
let pending = perpMarketsCache.get(perpVenue);
|
|
71
|
+
if (pending === undefined) {
|
|
72
|
+
pending = fetchPerpMarketsForMatching(perpVenue);
|
|
73
|
+
perpMarketsCache.set(perpVenue, pending);
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (spotAdapter instanceof LighterSpotAdapter) {
|
|
65
|
-
await spotAdapter.transferUsdcToSpot(amount);
|
|
66
|
-
return;
|
|
75
|
+
let perpMarkets;
|
|
76
|
+
try {
|
|
77
|
+
perpMarkets = await pending;
|
|
67
78
|
}
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (exchangeName === "hyperliquid") {
|
|
73
|
-
const { HyperliquidSpotAdapter } = await import("../../exchanges/hyperliquid-spot.js");
|
|
74
|
-
if (spotAdapter instanceof HyperliquidSpotAdapter) {
|
|
75
|
-
await spotAdapter.transferUsdcToPerp(amount);
|
|
76
|
-
return;
|
|
79
|
+
catch (e) {
|
|
80
|
+
log(`spot↔perp matching: ${perpVenue} perp markets unavailable (${e instanceof Error ? e.message : String(e)}) — no ${perpVenue} candidates this scan`);
|
|
81
|
+
out.set(perpVenue, new Set());
|
|
82
|
+
continue;
|
|
77
83
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
const matches = matchSpotPerp(spotVenue, spotMarkets, perpVenue, perpMarkets, log, sharedApiNetwork());
|
|
85
|
+
const bases = new Set();
|
|
86
|
+
const aliased = [];
|
|
87
|
+
for (const m of matches.values()) {
|
|
88
|
+
if (getPerpSymbol(m.spotBase, perpVenue) === m.perpSymbol.toUpperCase())
|
|
89
|
+
bases.add(m.spotBase);
|
|
90
|
+
else
|
|
91
|
+
aliased.push(`${m.spotBase}↔${m.perpSymbol}`);
|
|
92
|
+
}
|
|
93
|
+
if (aliased.length > 0) {
|
|
94
|
+
log(`spot↔perp matching ${spotVenue}→${perpVenue}: ${aliased.length} canonical pair(s) with a different perp symbol skipped (bot positions use one symbol): ${aliased.join(", ")}`);
|
|
84
95
|
}
|
|
96
|
+
out.set(perpVenue, bases);
|
|
85
97
|
}
|
|
98
|
+
return out;
|
|
86
99
|
}
|
|
87
100
|
// ── Rate / symbol helpers ──
|
|
88
101
|
/** Resolve perp symbol for a given base symbol on an exchange. */
|
|
@@ -116,112 +129,41 @@ export async function getPriceEstimate(perpAdapter, perpSymbol, fallbackSymbol)
|
|
|
116
129
|
return 0;
|
|
117
130
|
}
|
|
118
131
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (exA === exB)
|
|
139
|
-
continue;
|
|
140
|
-
for (const pB of posB) {
|
|
141
|
-
const keyB = `${exB}:${pB.symbol}`;
|
|
142
|
-
if (used.has(keyB) || pA.symbol !== pB.symbol || pA.side === pB.side)
|
|
143
|
-
continue;
|
|
144
|
-
const longEx = pA.side === "long" ? exA : exB;
|
|
145
|
-
const shortEx = pA.side === "short" ? exA : exB;
|
|
146
|
-
recovered.push({ symbol: pA.symbol, mode: "perp-perp", longExchange: longEx, shortExchange: shortEx, size: pA.side === "long" ? pA.size : pB.size });
|
|
147
|
-
used.add(keyA);
|
|
148
|
-
used.add(keyB);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
if (used.has(keyA))
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Same-exchange spot-perp hedges
|
|
157
|
-
for (const [name, a] of adapters) {
|
|
158
|
-
try {
|
|
159
|
-
const spotAdapter = await getSpotAdapter(name, a);
|
|
160
|
-
if (!spotAdapter)
|
|
161
|
-
continue;
|
|
162
|
-
const bals = await spotAdapter.getSpotBalances();
|
|
163
|
-
const nonUsdc = bals.filter(b => Number(b.total) > 0 && !b.token.toUpperCase().startsWith("USDC"));
|
|
164
|
-
if (nonUsdc.length === 0)
|
|
165
|
-
continue;
|
|
166
|
-
for (const perp of positionsByExchange.get(name) ?? []) {
|
|
167
|
-
const perpKey = `${name}:${perp.symbol}`;
|
|
168
|
-
if (used.has(perpKey))
|
|
169
|
-
continue;
|
|
170
|
-
const base = perp.symbol.replace(/-PERP$/, "").toUpperCase();
|
|
171
|
-
const spotBal = nonUsdc.find(b => b.token.toUpperCase().replace(/-SPOT$/, "") === base);
|
|
172
|
-
if (spotBal && perp.side === "short") {
|
|
173
|
-
recovered.push({ symbol: base, mode: "spot-perp", longExchange: `${name}-spot`, shortExchange: name, size: perp.size });
|
|
174
|
-
used.add(perpKey);
|
|
175
|
-
log(` Recovered spot-perp: ${base} ${name}-spot<>${name}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
catch { /* no spot */ }
|
|
180
|
-
}
|
|
181
|
-
// Cross-exchange spot-perp
|
|
182
|
-
for (const [spotExName, spotExAdapter] of adapters) {
|
|
183
|
-
try {
|
|
184
|
-
const spotAdapter = await getSpotAdapter(spotExName, spotExAdapter);
|
|
185
|
-
if (!spotAdapter)
|
|
186
|
-
continue;
|
|
187
|
-
const bals = await spotAdapter.getSpotBalances();
|
|
188
|
-
const nonUsdc = bals.filter(b => Number(b.total) > 0 && !b.token.toUpperCase().startsWith("USDC"));
|
|
189
|
-
for (const bal of nonUsdc) {
|
|
190
|
-
const base = bal.token.toUpperCase().replace(/-SPOT$/, "");
|
|
191
|
-
for (const [perpExName, perpPositions] of positionsByExchange) {
|
|
192
|
-
if (perpExName === spotExName)
|
|
193
|
-
continue;
|
|
194
|
-
for (const perp of perpPositions) {
|
|
195
|
-
const perpKey = `${perpExName}:${perp.symbol}`;
|
|
196
|
-
if (used.has(perpKey))
|
|
197
|
-
continue;
|
|
198
|
-
if (perp.symbol.replace(/-PERP$/, "").toUpperCase() === base && perp.side === "short") {
|
|
199
|
-
recovered.push({ symbol: base, mode: "spot-perp", longExchange: `${spotExName}-spot`, shortExchange: perpExName, size: perp.size });
|
|
200
|
-
used.add(perpKey);
|
|
201
|
-
log(` Recovered cross spot-perp: ${base} ${spotExName}-spot<>${perpExName}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
catch { /* no spot */ }
|
|
208
|
-
}
|
|
209
|
-
return recovered;
|
|
132
|
+
/**
|
|
133
|
+
* true when a reduce-only close was rejected because there is no position left to reduce.
|
|
134
|
+
* Prefers the typed signal: placePerpVerified's ORDER_REJECTED with details.reduceOnlyFlat
|
|
135
|
+
* (flat before and after the order — "(no X position to reduce)"). Venue texts that carry the
|
|
136
|
+
* same meaning are matched too: HL "Reduce only order would increase position", Aster -2022
|
|
137
|
+
* / "ReduceOnly Order is rejected". Only ever applied to a DEFINITE rejection (an
|
|
138
|
+
* ORDER_PENDING outcome is never "gone").
|
|
139
|
+
*/
|
|
140
|
+
export function isPositionGone(e) {
|
|
141
|
+
if (e instanceof PerpError) {
|
|
142
|
+
if (e.structured.code === "ORDER_PENDING")
|
|
143
|
+
return false;
|
|
144
|
+
if (e.structured.code === "ORDER_REJECTED" && e.structured.details?.reduceOnlyFlat === true)
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
const msg = typeof e === "string" ? e : extractErrorMessage(e);
|
|
148
|
+
return /position to reduce\)/.test(msg)
|
|
149
|
+
|| /reduce only order would increase position/i.test(msg)
|
|
150
|
+
|| msg.includes("ReduceOnly") || msg.includes("reduceOnly") || msg.includes("-2022");
|
|
210
151
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return msg.includes("ReduceOnly") || msg.includes("reduceOnly") || msg.includes("-2022");
|
|
152
|
+
function hasPerSymbolFundingHours(adapter) {
|
|
153
|
+
return typeof adapter.getFundingHours === "function";
|
|
214
154
|
}
|
|
215
|
-
/**
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Funding basis (hours one quoted rate covers) for a market: the market's own
|
|
157
|
+
* value when the venue publishes one per symbol, else the venue definition.
|
|
158
|
+
* History timestamp spacing is settlement timing, never the basis (§10.13 #2).
|
|
159
|
+
* @returns null when the venue publishes per-symbol values and this symbol has none
|
|
160
|
+
*/
|
|
161
|
+
async function marketFundingHours(adapter, exchangeName, symbol) {
|
|
162
|
+
if (hasPerSymbolFundingHours(adapter)) {
|
|
163
|
+
const hours = await adapter.getFundingHours(symbol);
|
|
164
|
+
return hours === undefined ? null : hours;
|
|
165
|
+
}
|
|
166
|
+
return getFundingHours(exchangeName);
|
|
225
167
|
}
|
|
226
168
|
/** Score a symbol's funding rate using history. Returns null if insufficient data. */
|
|
227
169
|
export async function scoreFunding(adapter, symbol, exchangeName, periods, cache) {
|
|
@@ -229,12 +171,16 @@ export async function scoreFunding(adapter, symbol, exchangeName, periods, cache
|
|
|
229
171
|
const cached = cache?.get(key);
|
|
230
172
|
if (cached && Date.now() - cached.ts < 10 * 60 * 1000)
|
|
231
173
|
return cached.score;
|
|
174
|
+
// Outside the try: an unknown venue is a configuration error, not "no score".
|
|
175
|
+
const venueHours = hasPerSymbolFundingHours(adapter) ? null : getFundingHours(exchangeName);
|
|
232
176
|
try {
|
|
177
|
+
const fH = venueHours !== null ? venueHours : await marketFundingHours(adapter, exchangeName, symbol);
|
|
178
|
+
if (fH === null)
|
|
179
|
+
return null; // per-symbol basis unknown — the hourly rate would be a guess
|
|
233
180
|
const history = await adapter.getFundingHistory(symbol, periods);
|
|
234
181
|
if (history.length < 2)
|
|
235
182
|
return null;
|
|
236
183
|
const rates = history.map(h => parseFloat(h.rate));
|
|
237
|
-
const fH = computeFundingHoursFromHistory(history);
|
|
238
184
|
const avgRate = rates.reduce((s, r) => s + r, 0) / rates.length;
|
|
239
185
|
const consistency = rates.filter(r => r > 0).length / rates.length;
|
|
240
186
|
const annualized = annualizeHourlyRate(avgRate / fH);
|
|
@@ -246,42 +192,60 @@ export async function scoreFunding(adapter, symbol, exchangeName, periods, cache
|
|
|
246
192
|
return null;
|
|
247
193
|
}
|
|
248
194
|
}
|
|
249
|
-
/**
|
|
195
|
+
/**
|
|
196
|
+
* Fetch funding rates from an exchange adapter. Failures propagate — callers
|
|
197
|
+
* record them per venue (an empty list would read as "venue has no markets").
|
|
198
|
+
*/
|
|
250
199
|
export async function fetchRates(adapter, exchangeName) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return withRates
|
|
274
|
-
.filter(m => m.fundingHours != null) // skip aster symbols with unknown funding period
|
|
275
|
-
.map(m => ({
|
|
200
|
+
const markets = await adapter.getMarkets();
|
|
201
|
+
const withRates = markets.filter(m => m.fundingRate != null);
|
|
202
|
+
// fundingHours = basis (§10.13 #7): market value first, then the venue definition.
|
|
203
|
+
// Per-symbol venues (Aster) load their whole table in one call; symbols it
|
|
204
|
+
// does not list stay null and are skipped below.
|
|
205
|
+
for (const m of withRates) {
|
|
206
|
+
if (m.fundingHours !== null)
|
|
207
|
+
continue;
|
|
208
|
+
m.fundingHours = await marketFundingHours(adapter, exchangeName, m.symbol);
|
|
209
|
+
}
|
|
210
|
+
const result = [];
|
|
211
|
+
for (const m of withRates) {
|
|
212
|
+
// Skip aster symbols with unknown funding period — the hourly rate would be a guess.
|
|
213
|
+
if (m.fundingHours === null)
|
|
214
|
+
continue;
|
|
215
|
+
// Skip markets whose size precision the venue does not publish — a leg could
|
|
216
|
+
// not be sized to the venue's lot, and substituting a static guess mis-sizes it.
|
|
217
|
+
if (m.sizeDecimals === null)
|
|
218
|
+
continue;
|
|
219
|
+
result.push({
|
|
276
220
|
symbol: m.symbol,
|
|
277
221
|
rate: parseFloat(m.fundingRate),
|
|
278
222
|
price: parseFloat(m.markPrice),
|
|
279
223
|
sizeDecimals: m.sizeDecimals,
|
|
280
224
|
maxLeverage: m.maxLeverage,
|
|
281
225
|
fundingHours: m.fundingHours,
|
|
282
|
-
})
|
|
226
|
+
});
|
|
283
227
|
}
|
|
284
|
-
|
|
285
|
-
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Cancel the open orders of `symbols` (the bot's own) on every venue in `adapters` — never an
|
|
232
|
+
* account-wide cancel. Each failure is logged (never "cancelled"); nothing is logged as
|
|
233
|
+
* cancelled when the bot traded no symbol.
|
|
234
|
+
*/
|
|
235
|
+
export async function cancelBotSymbols(adapters, symbols, tag, log) {
|
|
236
|
+
if (symbols.size === 0) {
|
|
237
|
+
log(` ${tag} No bot symbols traded — no orders to cancel`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
for (const [name, a] of adapters) {
|
|
241
|
+
for (const sym of symbols) {
|
|
242
|
+
try {
|
|
243
|
+
await a.cancelAllOrders(sym);
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
log(` ${tag} WARNING: cancelling ${sym} orders on ${name} FAILED (${e instanceof Error ? e.message : String(e)}) — check: perp -e ${name} account orders`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
286
249
|
}
|
|
250
|
+
log(` ${tag} Cancel requested for ${[...symbols].join(", ")} on ${[...adapters.keys()].join(", ")}`);
|
|
287
251
|
}
|
|
@@ -2,6 +2,8 @@ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from
|
|
|
2
2
|
export declare class FundingArbV2Strategy implements Strategy {
|
|
3
3
|
readonly name = "funding-arb-v2";
|
|
4
4
|
private _config;
|
|
5
|
+
/** Symbols this bot entered or recovered this run — the only symbols its stop cancels orders on. */
|
|
6
|
+
private _traded;
|
|
5
7
|
describe(): {
|
|
6
8
|
description: string;
|
|
7
9
|
params: ({
|
|
@@ -24,8 +26,38 @@ export declare class FundingArbV2Strategy implements Strategy {
|
|
|
24
26
|
private get sizeUsd();
|
|
25
27
|
private get scanIntervalSec();
|
|
26
28
|
init(ctx: StrategyContext, _snapshot: EnrichedSnapshot): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Pairs held on the venues: a long on one venue + a short of the same symbol and EXACTLY the
|
|
31
|
+
* same size on another. A single leg or a size-mismatched long/short is quarantined (both
|
|
32
|
+
* sizes recorded) — never tracked, never stacked on. A venue whose positions cannot be read
|
|
33
|
+
* throws (the start fails and is retried): unknown exposure is never "flat".
|
|
34
|
+
*/
|
|
35
|
+
private recoverPositions;
|
|
27
36
|
onTick(ctx: StrategyContext, _snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Close every tracked position (reduce-only, verified). A position that cannot be
|
|
39
|
+
* closed is never dropped silently: it is quarantined (persisted with what is open)
|
|
40
|
+
* so no bot / arb path touches it until a human resolves it.
|
|
41
|
+
*/
|
|
28
42
|
onStop(ctx: StrategyContext): Promise<StrategyAction[]>;
|
|
43
|
+
/** A live position of `symbol` on one of the venues that the bot does not track (or an unreadable venue): no entry on top of it. */
|
|
44
|
+
private heldUntracked;
|
|
45
|
+
private quarantine;
|
|
46
|
+
/**
|
|
47
|
+
* Long then short, each verified; the short is sized to the long's verified fill and
|
|
48
|
+
* any excess the short did not cover is trimmed off the long (reduce-only, verified).
|
|
49
|
+
* A definite short failure rolls the long back reduce-only. Returns the hedged size
|
|
50
|
+
* (both legs), or null when nothing is open / the symbol was quarantined.
|
|
51
|
+
*/
|
|
52
|
+
private openPosition;
|
|
53
|
+
/**
|
|
54
|
+
* Close both legs reduce-only with the opened size, verified. Reduce-only makes a
|
|
55
|
+
* retry safe (a leg already flat is rejected, never re-opened), so a definite failure
|
|
56
|
+
* of the LONG close keeps the (still hedged) position for the next tick; MAX_CLOSE_FAILURES
|
|
57
|
+
* in a row, an unknown outcome, or a short close that definitely failed once the long is
|
|
58
|
+
* closed (naked short) quarantines it. Returns true when both legs are flat.
|
|
59
|
+
*/
|
|
60
|
+
private closePosition;
|
|
29
61
|
private getAdapters;
|
|
30
62
|
/** Convert 8h funding rate difference to annualized spread % */
|
|
31
63
|
private annualizeSpread;
|