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
|
@@ -6,10 +6,11 @@ You are not expected to follow rigid rules — use this as a decision framework
|
|
|
6
6
|
## Core Concepts
|
|
7
7
|
|
|
8
8
|
### Funding Rate Mechanics
|
|
9
|
-
- Funding
|
|
9
|
+
- Funding settles **every 1 hour** on every venue except Aster, whose interval is **per symbol** (from its fundingInfo `fundingIntervalHours`)
|
|
10
|
+
- QFEX funding rates are unverified: excluded from scans / arb; shown only with `arb scan --rates --include-unverified` (display only)
|
|
10
11
|
- Positive rate = longs pay shorts, negative rate = shorts pay longs
|
|
11
|
-
- Rates are annualized
|
|
12
|
-
- Scan opportunities: `perp --json arb scan --min 5` (shows
|
|
12
|
+
- Rates are displayed annualized: per hour ≈ `annual / 8760`
|
|
13
|
+
- Scan opportunities: `perp --json arb scan --min 5` (shows grossSpread, netSpread, longExch, shortExch)
|
|
13
14
|
|
|
14
15
|
### Opportunity Cost Awareness
|
|
15
16
|
|
|
@@ -26,9 +27,9 @@ Switch Cost =
|
|
|
26
27
|
|
|
27
28
|
Query each component:
|
|
28
29
|
```bash
|
|
29
|
-
perp --json -e <FROM> trade check <SYM> <SIDE> <SIZE> # close cost estimate
|
|
30
|
-
perp --json bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT> # bridge cost
|
|
31
|
-
perp --json -e <TO> trade check <SYM> <SIDE> <SIZE> # open cost estimate
|
|
30
|
+
perp --json -e <FROM> trade check <SYM> <SIDE> <SIZE> --leverage <L> # close cost estimate
|
|
31
|
+
perp --json funds bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT> # bridge cost
|
|
32
|
+
perp --json -e <TO> trade check <SYM> <SIDE> <SIZE> --leverage <L> # open cost estimate
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
**Only switch if:**
|
|
@@ -56,8 +57,10 @@ During transition, you are **unhedged**. Price can move against you. Factor this
|
|
|
56
57
|
|
|
57
58
|
### Discovery Loop
|
|
58
59
|
```bash
|
|
59
|
-
perp --json arb scan --min 5 # find spreads > 5
|
|
60
|
-
|
|
60
|
+
perp --json arb scan --min 5 # find spreads > 5% annual (shows longExch/shortExch/netSpread)
|
|
61
|
+
perp --json arb scan --rates # raw funding rates across all exchanges
|
|
62
|
+
perp --json arb scan --history <SYMBOL> # historical funding for a symbol
|
|
63
|
+
# NOTE: legacy 'arb rates'/'arb prices'/'arb dex' are removed — all routes go through 'arb scan'
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
### CRITICAL: Reading arb scan Results
|
|
@@ -67,12 +70,14 @@ The `arb scan` output tells you EXACTLY what to do:
|
|
|
67
70
|
```
|
|
68
71
|
longExch: "hyperliquid" → open LONG on this exchange
|
|
69
72
|
shortExch: "pacifica" → open SHORT on this exchange
|
|
70
|
-
|
|
73
|
+
grossSpread: 15.2 → funding spread, annualized %
|
|
74
|
+
netSpread: 12.87 → annualized % after round-trip trading costs amortized over --hold-days (default 7)
|
|
75
|
+
(and --bridge-cost if given)
|
|
71
76
|
```
|
|
72
77
|
|
|
73
78
|
**Rules:**
|
|
74
79
|
1. **ALWAYS follow longExch/shortExch directions exactly.** DO NOT reverse them.
|
|
75
|
-
2. **NEVER enter if netSpread ≤ 0.** Negative netSpread = loss after fees.
|
|
80
|
+
2. **NEVER enter if netSpread ≤ 0.** Negative netSpread = loss after fees over the assumed hold. A shorter real hold makes costs weigh more — re-scan with your `--hold-days`.
|
|
76
81
|
3. The direction logic: go LONG where funding is negative (longs receive), go SHORT where funding is positive (shorts receive).
|
|
77
82
|
|
|
78
83
|
**Why these directions?**
|
|
@@ -92,7 +97,7 @@ When evaluating an arb opportunity:
|
|
|
92
97
|
|
|
93
98
|
3. **How long will the spread persist?**
|
|
94
99
|
- Historical funding tends to mean-revert
|
|
95
|
-
- Higher confidence in moderate, stable spreads (
|
|
100
|
+
- Higher confidence in moderate, stable spreads (e.g. 10–50% annualized netSpread) than extreme spikes (hundreds of % annualized)
|
|
96
101
|
|
|
97
102
|
4. **Can I execute both legs atomically?**
|
|
98
103
|
- Both positions should open near-simultaneously to minimize directional exposure
|
|
@@ -103,19 +108,20 @@ When evaluating an arb opportunity:
|
|
|
103
108
|
**Before entering any arb position, set a target hold duration.** This is critical for calculating whether the position is worth entering.
|
|
104
109
|
|
|
105
110
|
```
|
|
106
|
-
Expected
|
|
107
|
-
Entry/Exit Cost
|
|
108
|
-
Net Profit
|
|
111
|
+
Expected Funding = notional × (grossSpread% / 100) × target_hours / 8760
|
|
112
|
+
Entry/Exit Cost = open_fees + close_fees + slippage (both legs)
|
|
113
|
+
Net Profit = Expected Funding - Entry/Exit Cost
|
|
109
114
|
```
|
|
115
|
+
(`netSpread` already does this amortization for `--hold-days`; pass `--hold-days` = your target to read it directly.)
|
|
110
116
|
|
|
111
117
|
**Only enter if Net Profit > 0 with a comfortable margin.**
|
|
112
118
|
|
|
113
119
|
Guidelines for hold duration:
|
|
114
|
-
- **
|
|
115
|
-
- **Elevated spreads (50
|
|
116
|
-
- **
|
|
120
|
+
- **Moderate, stable spreads (≈10–50% annualized):** target days, not hours — the funding must accumulate past entry/exit costs.
|
|
121
|
+
- **Elevated spreads (≈50–100% annualized):** shorter targets; likely to compress. Take profit earlier.
|
|
122
|
+
- **Spikes (well above 100% annualized):** revert quickly — enter only if the funding over a few settlements covers entry/exit costs.
|
|
117
123
|
|
|
118
|
-
**Re-evaluate at each funding settlement (
|
|
124
|
+
**Re-evaluate at each funding settlement (hourly on most venues):**
|
|
119
125
|
1. Is the spread still above breakeven for the remaining target hours?
|
|
120
126
|
2. If spread compressed, should I exit early or extend the hold?
|
|
121
127
|
3. Has a better opportunity appeared? (Remember: switching has its own cost)
|
|
@@ -124,18 +130,18 @@ Guidelines for hold duration:
|
|
|
124
130
|
|
|
125
131
|
Example entry decision log:
|
|
126
132
|
```
|
|
127
|
-
Entry: BTC HL↔PAC |
|
|
128
|
-
Expected:
|
|
129
|
-
Entry/exit cost:
|
|
130
|
-
Net expected:
|
|
131
|
-
|
|
132
|
-
Actual hold:
|
|
133
|
+
Entry: BTC HL↔PAC | $1000/leg | grossSpread 80% annual | Target hold: 7d
|
|
134
|
+
Expected funding: 1000 × 0.80 × 168 / 8760 ≈ $15.3
|
|
135
|
+
Entry/exit cost: ~$4 (fees + slippage, both legs, open + close)
|
|
136
|
+
Net expected: ≈ $11 → ENTER (arb scan --hold-days 7 netSpread > 0 agrees)
|
|
137
|
+
Day 3 check: grossSpread compressed to 10% → remaining 4d earn ≈ $1.1 → EXIT if it keeps falling / flips, or a better net opportunity exists
|
|
138
|
+
Actual hold: 3d | log actual funding vs estimate
|
|
133
139
|
```
|
|
134
140
|
|
|
135
141
|
### Monitoring Active Positions
|
|
136
142
|
```bash
|
|
137
143
|
perp --json portfolio # unified multi-exchange view
|
|
138
|
-
perp --json risk
|
|
144
|
+
perp --json risk status # cross-exchange risk assessment (level, violations, canTrade)
|
|
139
145
|
perp --json -e <EX> account positions # per-exchange positions
|
|
140
146
|
perp --json arb scan --min 5 # are current rates still favorable?
|
|
141
147
|
```
|
|
@@ -179,6 +185,8 @@ Round `ORDER_SIZE` to the coarser step size of the two exchanges. If the matched
|
|
|
179
185
|
|
|
180
186
|
#### Step 2: Execute Legs Sequentially with Same Size
|
|
181
187
|
|
|
188
|
+
Prefer `perp --json arb exec <SYM> <LONG_EX> <SHORT_EX> <USD>` — it does exactly this: long first, then the short sized to the verified long fill, reconcile always on, and an unconfirmed leg quarantines the symbol. Manual legs (below) only when `arb exec` cannot be used.
|
|
189
|
+
|
|
182
190
|
Once you have a single `ORDER_SIZE`, execute both legs using THAT EXACT SIZE:
|
|
183
191
|
|
|
184
192
|
```bash
|
|
@@ -241,7 +249,7 @@ Leg B: perp --json -e <SHORT_EX> trade split <SYM> sell 5000
|
|
|
241
249
|
#### Using Limit Orders for Better Execution
|
|
242
250
|
For non-urgent entries, use limit orders at best bid/ask instead of market:
|
|
243
251
|
```bash
|
|
244
|
-
perp --json -e <EX> trade
|
|
252
|
+
perp --json -e <EX> trade limit <SYM> buy <PRICE> <ORDER_SIZE> # limit order (GTC default; --tif ALO = post-only)
|
|
245
253
|
```
|
|
246
254
|
This avoids crossing the spread but risks not getting filled. Set a timeout and fall back to market if unfilled. **Both legs must still use the same `ORDER_SIZE`.**
|
|
247
255
|
|
|
@@ -309,7 +317,7 @@ perp --json -e <EX> trade leverage <SYM> <LEVERAGE>
|
|
|
309
317
|
**For funding rate arb, use ISOLATED margin.** Each leg should be independent — if one side gets liquidated, the other side survives.
|
|
310
318
|
|
|
311
319
|
```bash
|
|
312
|
-
perp --json manage margin <SYM> isolated
|
|
320
|
+
perp --json -e <EX> wallet manage margin <SYM> isolated # set isolated margin
|
|
313
321
|
perp --json -e <EX> trade leverage <SYM> <LEV> --isolated # set leverage + isolated at once
|
|
314
322
|
```
|
|
315
323
|
|
|
@@ -405,8 +413,8 @@ Rules of thumb:
|
|
|
405
413
|
|
|
406
414
|
Each exchange holds its own separate balance. Before entering any arb:
|
|
407
415
|
```bash
|
|
408
|
-
perp --json -e <EX_A>
|
|
409
|
-
perp --json -e <EX_B>
|
|
416
|
+
perp --json portfolio -e <EX_A> # check available balance on exchange A
|
|
417
|
+
perp --json portfolio -e <EX_B> # check available balance on exchange B
|
|
410
418
|
```
|
|
411
419
|
|
|
412
420
|
**Size each leg to fit the available balance on that exchange:**
|
|
@@ -426,15 +434,12 @@ Even "market-neutral" arb can lose money if:
|
|
|
426
434
|
- Extreme funding spike in the wrong direction
|
|
427
435
|
- Slippage on entry/exit far exceeds estimates
|
|
428
436
|
|
|
429
|
-
**Always set stop losses on both legs
|
|
430
|
-
```bash
|
|
431
|
-
perp --json -e <EX_A> trade sl <SYM> <PRICE> # stop loss on leg A
|
|
432
|
-
perp --json -e <EX_B> trade sl <SYM> <PRICE> # stop loss on leg B
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
Or use TP/SL together:
|
|
437
|
+
**Always set stop losses on both legs** (`long|short` = the OPEN position's side; legs are reduce-only, sized to the live position):
|
|
436
438
|
```bash
|
|
437
|
-
perp --json -e <
|
|
439
|
+
perp --json --dry-run -e <EX_A> trade tpsl <SYM> long --sl <PRICE> # leg A (long): SL below mark
|
|
440
|
+
perp --json -e <EX_A> trade tpsl <SYM> long --sl <PRICE>
|
|
441
|
+
perp --json -e <EX_B> trade tpsl <SYM> short --sl <PRICE> # leg B (short): SL above mark
|
|
442
|
+
perp --json -e <EX> trade tpsl <SYM> long --sl <PRICE> --tp <PRICE> # TP + SL together
|
|
438
443
|
```
|
|
439
444
|
|
|
440
445
|
### What to Track Over Time
|
|
@@ -453,7 +458,8 @@ This is YOUR operational knowledge. Use it to make better decisions over time.
|
|
|
453
458
|
### Cross-Exchange Capital Allocation
|
|
454
459
|
Your capital is split across exchanges. Rebalancing has real costs:
|
|
455
460
|
```bash
|
|
456
|
-
perp --json bridge quote --from solana --to arbitrum --amount 1000
|
|
461
|
+
perp --json funds bridge quote --from solana --to arbitrum --amount 1000
|
|
462
|
+
perp --json funds rebalance plan # auto-compute optimal moves across exchanges
|
|
457
463
|
```
|
|
458
464
|
|
|
459
465
|
Before rebalancing, ask:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Monitor open arb positions and evaluate exit conditions
|
|
3
3
|
# Usage: ./arb-monitor.sh [--json] [--min-spread 5]
|
|
4
|
-
# Checks: current spread vs entry, funding earned, PnL, liquidation distance
|
|
4
|
+
# Checks: current spread vs entry, funding earned (from arb status), PnL, liquidation distance
|
|
5
5
|
# Returns JSON with position health + actionable recommendations
|
|
6
6
|
|
|
7
7
|
set -euo pipefail
|
|
@@ -13,6 +13,11 @@ else
|
|
|
13
13
|
PERP="npx -y perp-cli@latest"
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
|
+
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
17
|
+
# (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
|
|
18
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
19
|
+
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
20
|
+
|
|
16
21
|
JSON_MODE=false
|
|
17
22
|
MIN_SPREAD=5
|
|
18
23
|
while [[ $# -gt 0 ]]; do
|
|
@@ -23,9 +28,9 @@ while [[ $# -gt 0 ]]; do
|
|
|
23
28
|
esac
|
|
24
29
|
done
|
|
25
30
|
|
|
26
|
-
# 1. Get current arb status
|
|
27
|
-
ARB_STATUS=$(
|
|
28
|
-
if !
|
|
31
|
+
# 1. Get current arb status (already includes funding earned per period)
|
|
32
|
+
ARB_STATUS=$(pj --json arb status)
|
|
33
|
+
if ! isok "$ARB_STATUS"; then
|
|
29
34
|
if $JSON_MODE; then
|
|
30
35
|
echo '{"ok":false,"error":"No arb positions or arb status unavailable"}'
|
|
31
36
|
else
|
|
@@ -34,14 +39,14 @@ if ! echo "$ARB_STATUS" | grep -q '"ok":true'; then
|
|
|
34
39
|
exit 0
|
|
35
40
|
fi
|
|
36
41
|
|
|
37
|
-
# 2. Get current spreads
|
|
38
|
-
ARB_SCAN=$(
|
|
42
|
+
# 2. Get current spreads (all modes)
|
|
43
|
+
ARB_SCAN=$(pj --json arb scan --mode all --min 0)
|
|
39
44
|
|
|
40
|
-
# 3. Get
|
|
41
|
-
|
|
45
|
+
# 3. Get liquidation distances
|
|
46
|
+
LIQ_DIST=$(pj --json risk liquidation-distance)
|
|
42
47
|
|
|
43
|
-
# 4. Get
|
|
44
|
-
|
|
48
|
+
# 4. Get risk overview
|
|
49
|
+
RISK_STATUS=$(pj --json risk status)
|
|
45
50
|
|
|
46
51
|
# 5. Output combined analysis
|
|
47
52
|
if $JSON_MODE; then
|
|
@@ -51,8 +56,8 @@ if $JSON_MODE; then
|
|
|
51
56
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
52
57
|
"positions": $ARB_STATUS,
|
|
53
58
|
"currentSpreads": $ARB_SCAN,
|
|
54
|
-
"fundingEarned": $FUNDING,
|
|
55
59
|
"liquidationDistance": $LIQ_DIST,
|
|
60
|
+
"riskStatus": $RISK_STATUS,
|
|
56
61
|
"minSpreadThreshold": $MIN_SPREAD
|
|
57
62
|
}
|
|
58
63
|
EOF
|
|
@@ -60,15 +65,15 @@ else
|
|
|
60
65
|
echo "=== Arb Position Monitor ==="
|
|
61
66
|
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
62
67
|
echo ""
|
|
63
|
-
echo "--- Positions ---"
|
|
68
|
+
echo "--- Positions (with funding earned) ---"
|
|
64
69
|
echo "$ARB_STATUS" | jq -r '.data // empty'
|
|
65
70
|
echo ""
|
|
66
|
-
echo "--- Funding Earned ---"
|
|
67
|
-
echo "$FUNDING" | jq -r '.data // empty'
|
|
68
|
-
echo ""
|
|
69
71
|
echo "--- Liquidation Distance ---"
|
|
70
72
|
echo "$LIQ_DIST" | jq -r '.data // empty'
|
|
71
73
|
echo ""
|
|
72
|
-
echo "
|
|
73
|
-
echo "
|
|
74
|
+
echo "--- Risk Status ---"
|
|
75
|
+
echo "$RISK_STATUS" | jq -r '.data // empty'
|
|
76
|
+
echo ""
|
|
77
|
+
echo "Min spread threshold: ${MIN_SPREAD}% annual"
|
|
78
|
+
echo "Recommendation: Check if current spreads exceed ${MIN_SPREAD}% to continue holding."
|
|
74
79
|
fi
|
|
@@ -12,6 +12,11 @@ else
|
|
|
12
12
|
PERP="npx -y perp-cli@latest"
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
+
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
16
|
+
# (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
|
|
17
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
18
|
+
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
19
|
+
|
|
15
20
|
JSON_MODE=false
|
|
16
21
|
SYMBOL=""
|
|
17
22
|
TOP=10
|
|
@@ -26,9 +31,10 @@ done
|
|
|
26
31
|
|
|
27
32
|
if [[ -n "$SYMBOL" ]]; then
|
|
28
33
|
# Single symbol detailed analysis
|
|
29
|
-
FUNDING=$(
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
FUNDING=$(pj --json arb scan --history "$SYMBOL")
|
|
35
|
+
COMPARE=$(pj --json arb scan --compare "$SYMBOL")
|
|
36
|
+
SCAN=$(pj --json arb scan --mode perp-perp --min 0)
|
|
37
|
+
SPOT_SCAN=$(pj --json arb scan --mode spot-perp --min 0)
|
|
32
38
|
|
|
33
39
|
if $JSON_MODE; then
|
|
34
40
|
cat <<EOF
|
|
@@ -36,7 +42,8 @@ if [[ -n "$SYMBOL" ]]; then
|
|
|
36
42
|
"ok": true,
|
|
37
43
|
"symbol": "$SYMBOL",
|
|
38
44
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
39
|
-
"
|
|
45
|
+
"fundingHistory": $FUNDING,
|
|
46
|
+
"compare": $COMPARE,
|
|
40
47
|
"perpPerpArb": $SCAN,
|
|
41
48
|
"spotPerpArb": $SPOT_SCAN
|
|
42
49
|
}
|
|
@@ -44,25 +51,28 @@ EOF
|
|
|
44
51
|
else
|
|
45
52
|
echo "=== Funding Analysis: $SYMBOL ==="
|
|
46
53
|
echo ""
|
|
47
|
-
echo "---
|
|
54
|
+
echo "--- Cross-Exchange Compare ---"
|
|
55
|
+
echo "$COMPARE" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
|
|
56
|
+
echo ""
|
|
57
|
+
echo "--- Funding History ---"
|
|
48
58
|
echo "$FUNDING" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
|
|
49
59
|
echo ""
|
|
50
60
|
echo "--- Perp-Perp Arb ---"
|
|
51
61
|
echo "$SCAN" | jq -r '
|
|
52
62
|
.data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
|
|
53
|
-
" \(.longExch // .longExchange)→\(.shortExch // .shortExchange): \(.netSpread
|
|
63
|
+
" \(.longExch // .longExchange)→\(.shortExch // .shortExchange): net \(.netSpread)% annual"
|
|
54
64
|
' 2>/dev/null || echo " (no opportunity)"
|
|
55
65
|
echo ""
|
|
56
66
|
echo "--- Spot+Perp Arb ---"
|
|
57
67
|
echo "$SPOT_SCAN" | jq -r '
|
|
58
68
|
.data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
|
|
59
|
-
" \(.direction)
|
|
69
|
+
" \(.direction) spot:\((.spotExchanges // []) | join("/"))→\(.perpExchange): net \(.netSpreadPct)% annual"
|
|
60
70
|
' 2>/dev/null || echo " (no opportunity)"
|
|
61
71
|
fi
|
|
62
72
|
else
|
|
63
73
|
# All symbols scan
|
|
64
|
-
SCAN=$(
|
|
65
|
-
FUNDING_DETAIL=$(
|
|
74
|
+
SCAN=$(pj --json arb scan --mode all --min 0)
|
|
75
|
+
FUNDING_DETAIL=$(pj --json arb scan --rates)
|
|
66
76
|
|
|
67
77
|
if $JSON_MODE; then
|
|
68
78
|
cat <<EOF
|
|
@@ -79,7 +89,7 @@ EOF
|
|
|
79
89
|
echo ""
|
|
80
90
|
echo "$SCAN" | jq -r "
|
|
81
91
|
.data // [] | .[0:$TOP] | .[] |
|
|
82
|
-
\" \(.symbol) | \(.mode // \"perp\") | \(.netSpread // .
|
|
92
|
+
\" \(.symbol) | \(.mode // \"perp\") | net \(.netSpread // .netSpreadPct)%/yr | \(.longExch // (\"spot:\" + ((.spotExchanges // []) | join(\"/\"))))→\(.shortExch // .perpExchange)\"
|
|
83
93
|
" 2>/dev/null || echo "(no data)"
|
|
84
94
|
fi
|
|
85
95
|
fi
|
|
@@ -13,6 +13,11 @@ else
|
|
|
13
13
|
PERP="npx -y perp-cli@latest"
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
|
+
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
17
|
+
# (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
|
|
18
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
19
|
+
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
20
|
+
|
|
16
21
|
JSON_MODE=false
|
|
17
22
|
[[ "${1:-}" == "--json" ]] && JSON_MODE=true
|
|
18
23
|
|
|
@@ -22,7 +27,7 @@ checks=()
|
|
|
22
27
|
add_check() {
|
|
23
28
|
local name="$1" status="$2" detail="$3"
|
|
24
29
|
checks+=("{\"name\":\"$name\",\"status\":\"$status\",\"detail\":\"$detail\"}")
|
|
25
|
-
[[ "$status" == "fail" ]]
|
|
30
|
+
if [[ "$status" == "fail" ]]; then issues+=("$name: $detail"); fi
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
# 1. perp-cli installed and version check
|
|
@@ -30,38 +35,39 @@ VERSION=$($PERP --version 2>/dev/null || echo "unknown")
|
|
|
30
35
|
if [[ "$VERSION" != "unknown" ]]; then
|
|
31
36
|
add_check "install" "pass" "perp-cli $VERSION ($(command -v perp &>/dev/null && echo 'global' || echo 'npx'))"
|
|
32
37
|
else
|
|
33
|
-
add_check "install" "fail" "perp-cli not found —
|
|
38
|
+
add_check "install" "fail" "perp-cli not found — ask the user before installing (npm i -g perp-cli@latest) or use npx"
|
|
34
39
|
fi
|
|
35
40
|
|
|
36
41
|
# 2. Wallet configured
|
|
37
|
-
WALLET_OUT=$(
|
|
38
|
-
if
|
|
42
|
+
WALLET_OUT=$(pj --json wallet show)
|
|
43
|
+
if isok "$WALLET_OUT"; then
|
|
39
44
|
add_check "wallet" "pass" "Wallet configured"
|
|
40
45
|
else
|
|
41
|
-
add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange>
|
|
46
|
+
add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> (key via hidden prompt or stdin)"
|
|
42
47
|
fi
|
|
43
48
|
|
|
44
49
|
# 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
|
|
45
|
-
HEALTH_OUT=$(
|
|
46
|
-
if
|
|
50
|
+
HEALTH_OUT=$(pj --json market prices)
|
|
51
|
+
if isok "$HEALTH_OUT"; then
|
|
47
52
|
add_check "connectivity" "pass" "Exchange reachable"
|
|
48
53
|
else
|
|
49
54
|
add_check "connectivity" "warn" "Exchange unreachable"
|
|
50
55
|
fi
|
|
51
56
|
|
|
52
57
|
# 4. Portfolio & balances
|
|
53
|
-
PORTFOLIO_OUT=$(
|
|
54
|
-
if
|
|
58
|
+
PORTFOLIO_OUT=$(pj --json portfolio)
|
|
59
|
+
if isok "$PORTFOLIO_OUT"; then
|
|
55
60
|
add_check "portfolio" "pass" "Portfolio accessible"
|
|
56
61
|
else
|
|
57
62
|
add_check "portfolio" "warn" "Could not fetch portfolio"
|
|
58
63
|
fi
|
|
59
64
|
|
|
60
65
|
# 5. Risk status
|
|
61
|
-
RISK_OUT=$(
|
|
62
|
-
|
|
66
|
+
RISK_OUT=$(pj --json risk status)
|
|
67
|
+
CAN_TRADE=$(jq -r '.data.canTrade // empty' <<<"$RISK_OUT" 2>/dev/null || true)
|
|
68
|
+
if [[ "$CAN_TRADE" == "true" ]]; then
|
|
63
69
|
add_check "risk" "pass" "Trading allowed (canTrade=true)"
|
|
64
|
-
elif
|
|
70
|
+
elif [[ "$CAN_TRADE" == "false" ]]; then
|
|
65
71
|
add_check "risk" "fail" "Trading blocked — check risk violations"
|
|
66
72
|
else
|
|
67
73
|
add_check "risk" "warn" "Could not check risk status"
|
|
@@ -69,11 +75,12 @@ fi
|
|
|
69
75
|
|
|
70
76
|
# Output
|
|
71
77
|
READY=true
|
|
72
|
-
[[ ${#issues[@]} -gt 0 ]]
|
|
78
|
+
if [[ ${#issues[@]} -gt 0 ]]; then READY=false; fi
|
|
73
79
|
|
|
74
80
|
if $JSON_MODE; then
|
|
75
81
|
CHECKS_JSON=$(IFS=,; echo "${checks[*]}")
|
|
76
|
-
ISSUES_JSON
|
|
82
|
+
ISSUES_JSON='[]'
|
|
83
|
+
if [[ ${#issues[@]} -gt 0 ]]; then ISSUES_JSON=$(printf '%s\n' "${issues[@]}" | jq -R . | jq -s .); fi
|
|
77
84
|
echo "{\"ready\":$READY,\"checks\":[$CHECKS_JSON],\"issues\":$ISSUES_JSON}"
|
|
78
85
|
else
|
|
79
86
|
echo "=== perp-cli Pre-flight Check ==="
|
|
@@ -12,6 +12,11 @@ else
|
|
|
12
12
|
PERP="npx -y perp-cli@latest"
|
|
13
13
|
fi
|
|
14
14
|
|
|
15
|
+
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
16
|
+
# (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
|
|
17
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
18
|
+
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
19
|
+
|
|
15
20
|
JSON_MODE=false
|
|
16
21
|
MODE="all"
|
|
17
22
|
MIN_SPREAD=10
|
|
@@ -25,8 +30,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
25
30
|
done
|
|
26
31
|
|
|
27
32
|
# 1. Portfolio check (ensure we have balances)
|
|
28
|
-
PORTFOLIO=$(
|
|
29
|
-
if !
|
|
33
|
+
PORTFOLIO=$(pj --json portfolio)
|
|
34
|
+
if ! isok "$PORTFOLIO"; then
|
|
30
35
|
if $JSON_MODE; then
|
|
31
36
|
echo '{"ok":false,"error":"Cannot fetch portfolio — check wallet setup"}'
|
|
32
37
|
else
|
|
@@ -36,10 +41,10 @@ if ! echo "$PORTFOLIO" | grep -q '"ok":true'; then
|
|
|
36
41
|
fi
|
|
37
42
|
|
|
38
43
|
# 2. Run appropriate scan
|
|
39
|
-
SCAN_RESULT=$(
|
|
44
|
+
SCAN_RESULT=$(pj --json arb scan --mode "$MODE" --min "$MIN_SPREAD")
|
|
40
45
|
|
|
41
46
|
# 3. Get exchange health for context (proxy via market prices fetch)
|
|
42
|
-
HEALTH=$(
|
|
47
|
+
HEALTH=$(pj --json market prices)
|
|
43
48
|
|
|
44
49
|
if $JSON_MODE; then
|
|
45
50
|
cat <<EOF
|
|
@@ -55,14 +60,14 @@ if $JSON_MODE; then
|
|
|
55
60
|
EOF
|
|
56
61
|
else
|
|
57
62
|
echo "=== Spot+Perp Arb Scanner ==="
|
|
58
|
-
echo "Mode: $MODE | Min spread: ${MIN_SPREAD}
|
|
63
|
+
echo "Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
|
|
59
64
|
echo ""
|
|
60
65
|
|
|
61
66
|
# Show balances
|
|
62
67
|
echo "--- Balances ---"
|
|
63
68
|
echo "$PORTFOLIO" | jq -r '
|
|
64
69
|
.data.exchanges // [] | .[] |
|
|
65
|
-
" \(.
|
|
70
|
+
" \(.name): equity $\(.perp.equity // "N/A")"
|
|
66
71
|
' 2>/dev/null || echo " (unable to parse)"
|
|
67
72
|
echo ""
|
|
68
73
|
|
|
@@ -70,7 +75,7 @@ else
|
|
|
70
75
|
echo "--- Opportunities ---"
|
|
71
76
|
echo "$SCAN_RESULT" | jq -r '
|
|
72
77
|
.data // [] | .[] |
|
|
73
|
-
" \(.symbol) | \(.mode // "perp-perp") |
|
|
78
|
+
" \(.symbol) | \(.mode // "perp-perp") | net: \(.netSpread // .netSpreadPct)%/yr | \(.longExch // ("spot:" + ((.spotExchanges // []) | join("/"))))→\(.shortExch // .perpExchange)"
|
|
74
79
|
' 2>/dev/null || echo " (no opportunities or parse error)"
|
|
75
80
|
|
|
76
81
|
# Show exchange health
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Validate an arb opportunity before execution
|
|
3
3
|
# Usage: ./validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]
|
|
4
|
-
# Performs: price check, liquidity check, balance check, risk check,
|
|
4
|
+
# Performs: price check, liquidity check, balance check, risk check, dry-run via 'arb exec'
|
|
5
5
|
# Returns JSON validation report
|
|
6
|
+
# NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl).
|
|
6
7
|
|
|
7
8
|
set -euo pipefail
|
|
8
9
|
|
|
@@ -13,6 +14,11 @@ else
|
|
|
13
14
|
PERP="npx -y perp-cli@latest"
|
|
14
15
|
fi
|
|
15
16
|
|
|
17
|
+
# perp exits 1 on {"ok":false} and prints exactly one JSON document: capture it as-is
|
|
18
|
+
# (never append a fallback document); empty output (crash / not installed) → {"ok":false}.
|
|
19
|
+
pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false}'; fi; }
|
|
20
|
+
isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
|
|
21
|
+
|
|
16
22
|
SYM="${1:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]}"
|
|
17
23
|
LONG_EX="${2:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD>}"
|
|
18
24
|
SHORT_EX="${3:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD>}"
|
|
@@ -33,12 +39,22 @@ pass=true
|
|
|
33
39
|
add_result() {
|
|
34
40
|
local name="$1" ok="$2" detail="$3"
|
|
35
41
|
checks+=("{\"check\":\"$name\",\"ok\":$ok,\"detail\":\"$detail\"}")
|
|
36
|
-
[[ "$ok" == "false" ]]
|
|
42
|
+
if [[ "$ok" == "false" ]]; then pass=false; fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below
|
|
46
|
+
LONG_VENUE="${LONG_EX#spot:}"
|
|
47
|
+
# portfolio -e accepts only full venue ids (no aliases)
|
|
48
|
+
venue_id() {
|
|
49
|
+
case "$1" in
|
|
50
|
+
pac) echo pacifica ;; hl) echo hyperliquid ;; lt) echo lighter ;; lrh|rh) echo lighter-rh ;;
|
|
51
|
+
ast) echo aster ;; ondo-perps) echo ondo ;; qf) echo qfex ;; *) echo "$1" ;;
|
|
52
|
+
esac
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
# 1. Price comparison — both exchanges must report similar prices
|
|
40
|
-
LONG_PRICE=$(
|
|
41
|
-
SHORT_PRICE=$(
|
|
56
|
+
LONG_PRICE=$(pj --json -e "$LONG_VENUE" market mid "$SYM" | jq -r '.data.mid // "0"' 2>/dev/null || echo 0)
|
|
57
|
+
SHORT_PRICE=$(pj --json -e "$SHORT_EX" market mid "$SYM" | jq -r '.data.mid // "0"' 2>/dev/null || echo 0)
|
|
42
58
|
|
|
43
59
|
if [[ "$LONG_PRICE" == "0" || "$SHORT_PRICE" == "0" ]]; then
|
|
44
60
|
add_result "price" "false" "Cannot fetch price: long=$LONG_PRICE short=$SHORT_PRICE"
|
|
@@ -53,8 +69,8 @@ else
|
|
|
53
69
|
fi
|
|
54
70
|
|
|
55
71
|
# 2. Balance check on both exchanges
|
|
56
|
-
LONG_BAL=$(
|
|
57
|
-
SHORT_BAL=$(
|
|
72
|
+
LONG_BAL=$(pj --json portfolio -e "$(venue_id "$LONG_VENUE")" | jq -r '.data.exchanges[0].perp.available // .data.exchanges[0].perp.equity // "0"' 2>/dev/null || echo 0)
|
|
73
|
+
SHORT_BAL=$(pj --json portfolio -e "$(venue_id "$SHORT_EX")" | jq -r '.data.exchanges[0].perp.available // .data.exchanges[0].perp.equity // "0"' 2>/dev/null || echo 0)
|
|
58
74
|
|
|
59
75
|
NEEDED=$(echo "$SIZE" | awk '{print $1}')
|
|
60
76
|
if (( $(echo "$LONG_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
|
|
@@ -69,31 +85,32 @@ else
|
|
|
69
85
|
fi
|
|
70
86
|
|
|
71
87
|
# 3. Market info (symbol availability)
|
|
72
|
-
LONG_INFO=$(
|
|
73
|
-
SHORT_INFO=$(
|
|
88
|
+
LONG_INFO=$(pj --json -e "$LONG_VENUE" market info "$SYM")
|
|
89
|
+
SHORT_INFO=$(pj --json -e "$SHORT_EX" market info "$SYM")
|
|
74
90
|
|
|
75
|
-
if
|
|
91
|
+
if isok "$LONG_INFO" && isok "$SHORT_INFO"; then
|
|
76
92
|
add_result "market_info" "true" "Both exchanges support $SYM"
|
|
77
93
|
else
|
|
78
94
|
add_result "market_info" "false" "Symbol $SYM not available on one or both exchanges"
|
|
79
95
|
fi
|
|
80
96
|
|
|
81
|
-
# 4. Risk pre-check
|
|
97
|
+
# 4. Risk pre-check (risk check requires --leverage; default 1x when not given)
|
|
82
98
|
LEV_FLAG=""
|
|
83
99
|
[[ -n "$LEVERAGE" ]] && LEV_FLAG="--leverage $LEVERAGE"
|
|
84
|
-
RISK_CHECK=$(
|
|
85
|
-
if
|
|
100
|
+
RISK_CHECK=$(pj --json risk check --notional "$SIZE" --leverage "${LEVERAGE:-1}")
|
|
101
|
+
if isok "$RISK_CHECK" && [[ "$(jq -r '.data.allowed' <<<"$RISK_CHECK" 2>/dev/null || true)" == "true" ]]; then
|
|
86
102
|
add_result "risk" "true" "Within risk limits"
|
|
87
103
|
else
|
|
88
|
-
|
|
104
|
+
RISK_WHY=$(jq -r '.data.reason // .error.code // "unknown"' <<<"$RISK_CHECK" 2>/dev/null || echo unknown)
|
|
105
|
+
add_result "risk" "false" "Risk check failed: $RISK_WHY"
|
|
89
106
|
fi
|
|
90
107
|
|
|
91
108
|
# 5. Dry-run
|
|
92
|
-
DRY_RUN=$(
|
|
93
|
-
if
|
|
109
|
+
DRY_RUN=$(pj --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LEV_FLAG)
|
|
110
|
+
if isok "$DRY_RUN"; then
|
|
94
111
|
add_result "dry_run" "true" "Dry-run succeeded"
|
|
95
112
|
else
|
|
96
|
-
DRY_ERR=$(
|
|
113
|
+
DRY_ERR=$(jq -r '.error.code // "unknown"' <<<"$DRY_RUN" 2>/dev/null || echo unknown)
|
|
97
114
|
add_result "dry_run" "false" "Dry-run failed: $DRY_ERR"
|
|
98
115
|
fi
|
|
99
116
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
lighter-signer.wasm: elliottech/lighter-go release v1.0.9 (2026-09-14), asset "lighter-signer.wasm"
|
|
2
|
+
https://github.com/elliottech/lighter-go/releases/download/v1.0.9/lighter-signer.wasm
|
|
3
|
+
built from lighter-go 8854554 ("allow markets gr 4095 (#89)") with go1.25.6 (GOOS=js GOARCH=wasm)
|
|
4
|
+
sha256 matches the release asset digest published by GitHub.
|
|
5
|
+
wasm_exec.js: Go go1.25.6 lib/wasm/wasm_exec.js (the runtime glue matching the toolchain the wasm was built with)
|
|
6
|
+
https://github.com/golang/go/blob/go1.25.6/lib/wasm/wasm_exec.js
|
|
7
|
+
Functions/arguments: https://apidocs.lighter.xyz/docs/wasm-signer
|
|
8
|
+
Both files are verified against SHA256SUMS (also pinned in src/exchanges/lighter-signer.ts) before load; a mismatch refuses to sign.
|
|
Binary file
|