perp-cli 0.9.8 → 0.12.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 +30 -25
- package/dist/_stderr-filter.d.ts +0 -0
- package/dist/_stderr-filter.js +30 -0
- package/dist/agent-wallet/expiry.d.ts +15 -0
- package/dist/agent-wallet/expiry.js +26 -0
- package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
- package/dist/agent-wallet/lighter-keystore.js +232 -0
- package/dist/agent-wallet/passphrase.d.ts +13 -0
- package/dist/agent-wallet/passphrase.js +54 -0
- package/dist/agent-wallet/signing-strategy.d.ts +67 -0
- package/dist/agent-wallet/signing-strategy.js +51 -0
- package/dist/agent-wallet/store.d.ts +37 -0
- package/dist/agent-wallet/store.js +172 -0
- package/dist/api/public/_http.d.ts +15 -0
- package/dist/api/public/_http.js +24 -0
- package/dist/api/public/hyperliquid.js +36 -24
- package/dist/api/public/lighter.d.ts +7 -1
- package/dist/api/public/lighter.js +85 -37
- package/dist/api/public/pacifica.js +47 -18
- package/dist/bot/engine.js +23 -4
- package/dist/bot/presets.js +3 -3
- package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
- package/dist/bot/strategies/funding-arb-strategy.js +41 -176
- package/dist/bot/strategies/funding-auto.js +2 -2
- package/dist/bridge-engine.js +102 -49
- package/dist/cli-spec.js +1 -2
- package/dist/commands/account.js +2 -500
- package/dist/commands/agent.d.ts +8 -2
- package/dist/commands/agent.js +1903 -366
- package/dist/commands/alerts.js +3 -1
- package/dist/commands/arb/index.js +37 -7
- package/dist/commands/arb-auto.js +44 -11
- package/dist/commands/arb.js +83 -34
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +20 -16
- package/dist/commands/bridge.d.ts +1 -1
- package/dist/commands/bridge.js +4 -4
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +18 -68
- package/dist/commands/jobs.d.ts +1 -1
- package/dist/commands/jobs.js +2 -2
- package/dist/commands/manage.d.ts +1 -1
- package/dist/commands/manage.js +12 -84
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/plan.js +2 -2
- package/dist/commands/portfolio.d.ts +19 -3
- package/dist/commands/portfolio.js +747 -163
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +18 -12
- package/dist/commands/risk.js +2 -6
- package/dist/commands/settings.js +30 -53
- package/dist/commands/trade.js +16 -15
- package/dist/commands/wallet.d.ts +2 -1
- package/dist/commands/wallet.js +1122 -176
- package/dist/config.d.ts +9 -0
- package/dist/config.js +55 -9
- package/dist/errors.d.ts +64 -0
- package/dist/errors.js +82 -2
- package/dist/event-stream.js +12 -1
- package/dist/exchanges/aster-typed-data.d.ts +156 -0
- package/dist/exchanges/aster-typed-data.js +189 -0
- package/dist/exchanges/aster.d.ts +60 -11
- package/dist/exchanges/aster.js +278 -136
- package/dist/exchanges/hyperliquid.d.ts +14 -0
- package/dist/exchanges/hyperliquid.js +88 -34
- package/dist/exchanges/lighter-spot.js +19 -5
- package/dist/exchanges/lighter.d.ts +66 -0
- package/dist/exchanges/lighter.js +213 -31
- package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
- package/dist/exchanges/pacifica-typed-data.js +74 -0
- package/dist/exchanges/pacifica.d.ts +29 -1
- package/dist/exchanges/pacifica.js +94 -6
- package/dist/funding/rates.js +59 -21
- package/dist/guardrail/contracts.d.ts +14 -0
- package/dist/guardrail/contracts.js +72 -0
- package/dist/guardrail/perp-guardrail.d.ts +21 -0
- package/dist/guardrail/perp-guardrail.js +145 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +307 -362
- package/dist/jobs.js +3 -1
- package/dist/mcp-server.js +92 -88
- package/dist/position-history.js +47 -4
- package/dist/settings.d.ts +69 -3
- package/dist/settings.js +3 -2
- package/dist/signer/index.d.ts +2 -0
- package/dist/signer/index.js +2 -0
- package/dist/signer/ows-evm.d.ts +32 -0
- package/dist/signer/ows-evm.js +86 -0
- package/dist/signer/ows-loader.d.ts +2 -0
- package/dist/signer/ows-loader.js +6 -0
- package/dist/signer/ows-solana.d.ts +25 -0
- package/dist/signer/ows-solana.js +56 -0
- package/dist/smart-order.d.ts +8 -2
- package/dist/smart-order.js +46 -11
- package/dist/strategies/funding-arb.js +29 -8
- package/dist/trade-validator.js +30 -5
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +19 -0
- package/package.json +4 -2
- package/skills/perp-cli/SKILL.md +23 -14
- package/skills/perp-cli/references/agent-operations.md +6 -4
- package/skills/perp-cli/references/commands.md +55 -34
- package/skills/perp-cli/scripts/preflight.sh +4 -4
- package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
- package/dist/__tests__/alert-logic.test.d.ts +0 -1
- package/dist/__tests__/alert-logic.test.js +0 -107
- package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
- package/dist/__tests__/arb-auto-3dex.test.js +0 -397
- package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
- package/dist/__tests__/arb-history-stats.test.js +0 -176
- package/dist/__tests__/arb-logic.test.d.ts +0 -1
- package/dist/__tests__/arb-logic.test.js +0 -84
- package/dist/__tests__/arb-manage.test.d.ts +0 -1
- package/dist/__tests__/arb-manage.test.js +0 -253
- package/dist/__tests__/arb-new-features.test.d.ts +0 -1
- package/dist/__tests__/arb-new-features.test.js +0 -457
- package/dist/__tests__/arb-sizing.test.d.ts +0 -1
- package/dist/__tests__/arb-sizing.test.js +0 -48
- package/dist/__tests__/arb-state.test.d.ts +0 -1
- package/dist/__tests__/arb-state.test.js +0 -284
- package/dist/__tests__/arb-userflow.test.d.ts +0 -1
- package/dist/__tests__/arb-userflow.test.js +0 -945
- package/dist/__tests__/arb-utils.test.d.ts +0 -1
- package/dist/__tests__/arb-utils.test.js +0 -264
- package/dist/__tests__/bot-conditions.test.d.ts +0 -1
- package/dist/__tests__/bot-conditions.test.js +0 -341
- package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
- package/dist/__tests__/client-id-tracker.test.js +0 -137
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
- package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
- package/dist/__tests__/commands/order-intent.test.js +0 -600
- package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
- package/dist/__tests__/commands/trade-commands.test.js +0 -821
- package/dist/__tests__/config.test.d.ts +0 -1
- package/dist/__tests__/config.test.js +0 -86
- package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
- package/dist/__tests__/cross-chain-margin.test.js +0 -287
- package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
- package/dist/__tests__/dex-asset-map.test.js +0 -191
- package/dist/__tests__/errors.test.d.ts +0 -1
- package/dist/__tests__/errors.test.js +0 -110
- package/dist/__tests__/event-stream.test.d.ts +0 -1
- package/dist/__tests__/event-stream.test.js +0 -276
- package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
- package/dist/__tests__/exchanges/interface.test.js +0 -132
- package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
- package/dist/__tests__/exchanges/mock-adapter.js +0 -137
- package/dist/__tests__/execution-log.test.d.ts +0 -1
- package/dist/__tests__/execution-log.test.js +0 -106
- package/dist/__tests__/funding-calc.test.d.ts +0 -1
- package/dist/__tests__/funding-calc.test.js +0 -71
- package/dist/__tests__/funding-history.test.d.ts +0 -1
- package/dist/__tests__/funding-history.test.js +0 -343
- package/dist/__tests__/funding-rates.test.d.ts +0 -1
- package/dist/__tests__/funding-rates.test.js +0 -342
- package/dist/__tests__/funding.test.d.ts +0 -1
- package/dist/__tests__/funding.test.js +0 -173
- package/dist/__tests__/gap-logic.test.d.ts +0 -1
- package/dist/__tests__/gap-logic.test.js +0 -43
- package/dist/__tests__/hip3-dex.test.d.ts +0 -1
- package/dist/__tests__/hip3-dex.test.js +0 -234
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
- package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/bridge.integration.test.js +0 -309
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
- package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/lighter.integration.test.js +0 -53
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
- package/dist/__tests__/liquidity.test.d.ts +0 -1
- package/dist/__tests__/liquidity.test.js +0 -225
- package/dist/__tests__/plan-executor.test.d.ts +0 -1
- package/dist/__tests__/plan-executor.test.js +0 -314
- package/dist/__tests__/position-history.test.d.ts +0 -1
- package/dist/__tests__/position-history.test.js +0 -367
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -310
- package/dist/__tests__/risk-assessment.test.d.ts +0 -1
- package/dist/__tests__/risk-assessment.test.js +0 -288
- package/dist/__tests__/security-adversarial.test.d.ts +0 -1
- package/dist/__tests__/security-adversarial.test.js +0 -574
- package/dist/__tests__/strategies.test.d.ts +0 -1
- package/dist/__tests__/strategies.test.js +0 -539
- package/dist/__tests__/trade-execution.test.d.ts +0 -1
- package/dist/__tests__/trade-execution.test.js +0 -129
- package/dist/__tests__/trade-validator.test.d.ts +0 -1
- package/dist/__tests__/trade-validator.test.js +0 -655
- package/dist/__tests__/utils.test.d.ts +0 -1
- package/dist/__tests__/utils.test.js +0 -76
- package/dist/arb/reconcile.d.ts +0 -28
- package/dist/arb/reconcile.js +0 -162
- package/dist/commands/alert.d.ts +0 -2
- package/dist/commands/alert.js +0 -421
- package/dist/commands/analytics.d.ts +0 -3
- package/dist/commands/analytics.js +0 -311
- package/dist/commands/arb-manage.d.ts +0 -5
- package/dist/commands/arb-manage.js +0 -5
- package/dist/commands/dashboard.d.ts +0 -3
- package/dist/commands/dashboard.js +0 -176
- package/dist/commands/deposit.d.ts +0 -4
- package/dist/commands/deposit.js +0 -573
- package/dist/commands/dex.d.ts +0 -3
- package/dist/commands/dex.js +0 -120
- package/dist/commands/env.d.ts +0 -2
- package/dist/commands/env.js +0 -136
- package/dist/commands/funding.d.ts +0 -3
- package/dist/commands/funding.js +0 -554
- package/dist/commands/gap.d.ts +0 -2
- package/dist/commands/gap.js +0 -305
- package/dist/commands/health.d.ts +0 -2
- package/dist/commands/health.js +0 -67
- package/dist/commands/multileg.d.ts +0 -16
- package/dist/commands/multileg.js +0 -174
- package/dist/commands/pnl.d.ts +0 -12
- package/dist/commands/pnl.js +0 -243
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -202
- package/dist/commands/stream.d.ts +0 -5
- package/dist/commands/stream.js +0 -200
- package/dist/commands/withdraw.d.ts +0 -3
- package/dist/commands/withdraw.js +0 -187
- package/dist/exchanges/edgex.d.ts +0 -62
- package/dist/exchanges/edgex.js +0 -552
- package/dist/exchanges/nado.d.ts +0 -52
- package/dist/exchanges/nado.js +0 -223
- package/dist/funding/index.d.ts +0 -3
- package/dist/funding/index.js +0 -3
- package/dist/retry.d.ts +0 -74
- package/dist/retry.js +0 -129
- package/dist/ws/market-feed.d.ts +0 -74
- package/dist/ws/market-feed.js +0 -227
|
@@ -69,7 +69,11 @@ perp --json trade market <SYM> buy <SIZE> --split # split via market command f
|
|
|
69
69
|
perp --json trade market <SYM> buy <SIZE> --split --max-slippage 0.5
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
## Funds (Deposit, Withdraw, Transfer, Bridge)
|
|
72
|
+
## Funds (Deposit, Withdraw, Transfer, Bridge, Rebalance)
|
|
73
|
+
|
|
74
|
+
All fund movement lives under `perp funds`.
|
|
75
|
+
|
|
76
|
+
### Exchange in/out
|
|
73
77
|
```bash
|
|
74
78
|
perp --json funds deposit pacifica <AMOUNT>
|
|
75
79
|
perp --json funds deposit hyperliquid <AMOUNT>
|
|
@@ -79,19 +83,26 @@ perp --json funds deposit lighter info # all Lighter deposi
|
|
|
79
83
|
perp --json funds withdraw pacifica <AMOUNT>
|
|
80
84
|
perp --json funds withdraw hyperliquid <AMOUNT>
|
|
81
85
|
perp --json funds withdraw lighter <AMOUNT>
|
|
82
|
-
perp --json funds transfer <AMOUNT> <ADDRESS>
|
|
83
|
-
perp --json funds
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
perp --json funds transfer <AMOUNT> <ADDRESS> # HL internal transfer (instant)
|
|
87
|
+
perp --json funds info # combined routes & limits
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Cross-chain bridge (multi-provider router: cctp / relay / debridge — auto-cheapest)
|
|
91
|
+
```bash
|
|
92
|
+
perp --json funds bridge chains # supported chains
|
|
93
|
+
perp --json funds bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT>
|
|
94
|
+
perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> # auto-cheapest
|
|
95
|
+
perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> --provider cctp
|
|
96
|
+
perp --json funds bridge exchange --from <EX> --to <EX> --amount <AMT> # between exchanges
|
|
97
|
+
perp --json funds bridge status <ORDER_ID>
|
|
86
98
|
```
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
### Inter-exchange rebalance (orchestrated withdraw → bridge → deposit)
|
|
89
101
|
```bash
|
|
90
|
-
perp --json
|
|
91
|
-
perp --json
|
|
92
|
-
perp --json
|
|
93
|
-
perp --json
|
|
94
|
-
perp --json bridge status <ORDER_ID>
|
|
102
|
+
perp --json funds rebalance check # balances across exchanges
|
|
103
|
+
perp --json funds rebalance plan # compute optimal moves
|
|
104
|
+
perp --json funds rebalance execute --auto-bridge # withdraw → bridge → deposit
|
|
105
|
+
perp --json funds rebalance execute --withdraw-only # only withdraw, manual rest
|
|
95
106
|
```
|
|
96
107
|
|
|
97
108
|
## Arbitrage
|
|
@@ -137,7 +148,6 @@ perp --json risk liquidation-distance # % distance from liquidation for AL
|
|
|
137
148
|
perp --json risk limits # view current risk limits
|
|
138
149
|
perp --json risk limits --min-liq-distance 30 --max-leverage 5 # set risk limits
|
|
139
150
|
perp --json risk check --notional 1000 --leverage 3 # pre-trade risk check
|
|
140
|
-
perp --json agent ping # exchange connectivity & latency
|
|
141
151
|
perp --json history summary # trading performance
|
|
142
152
|
perp --json history pnl # P&L breakdown by exchange
|
|
143
153
|
perp --json history funding # funding payment aggregation
|
|
@@ -148,24 +158,34 @@ perp --json history perf --period summary # performance summary stats
|
|
|
148
158
|
perp --json history list # execution audit trail
|
|
149
159
|
```
|
|
150
160
|
|
|
151
|
-
## Automated Strategies (
|
|
161
|
+
## Automated Strategies (strategy — 19 algorithms + nested scripted plans)
|
|
152
162
|
```bash
|
|
153
|
-
perp --json
|
|
154
|
-
perp --json
|
|
155
|
-
perp --json
|
|
156
|
-
perp --json
|
|
157
|
-
perp --json
|
|
158
|
-
perp --json
|
|
159
|
-
perp --json
|
|
160
|
-
perp --json
|
|
161
|
-
perp --json
|
|
162
|
-
perp --json
|
|
163
|
-
perp --json
|
|
164
|
-
perp --json
|
|
165
|
-
perp --json
|
|
166
|
-
perp --json
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
perp --json strategy list-strategies # list all 19 strategies
|
|
164
|
+
perp --json strategy run <STRATEGY> [SYMBOL] # run any registered strategy
|
|
165
|
+
perp --json strategy run funding-auto # multi-exchange funding arb (no symbol needed)
|
|
166
|
+
perp --json strategy apex [SYMBOL] # APEX autonomous orchestrator (Radar+Pulse+Guard)
|
|
167
|
+
perp --json strategy reflect # trading performance analysis (win rate, fees, PnL)
|
|
168
|
+
perp --json strategy preset-list # list strategy presets
|
|
169
|
+
perp --json strategy preset <NAME> [SYMBOL] # run from preset
|
|
170
|
+
perp --json strategy twap <SYMBOL> <SIDE> <SIZE> <DURATION> # TWAP execution
|
|
171
|
+
perp --json strategy grid <SYMBOL> --range <PCT> --grids <N> --size <USD>
|
|
172
|
+
perp --json strategy dca <SYMBOL> <SIDE> <AMOUNT> <INTERVAL>
|
|
173
|
+
perp --json strategy trailing-stop <SYMBOL> # trailing stop with callback %
|
|
174
|
+
perp --json strategy funding-arb # auto funding arb
|
|
175
|
+
perp --json strategy quick-grid <SYMBOL> # quick grid bot
|
|
176
|
+
perp --json strategy quick-arb # quick arb bot
|
|
177
|
+
|
|
178
|
+
# Scripted execution plans (one-shot multi-step)
|
|
179
|
+
perp --json strategy plan example # show plan format
|
|
180
|
+
perp --json strategy plan validate <FILE> # validate plan
|
|
181
|
+
perp --json strategy plan execute <FILE> --dry-run # dry-run plan
|
|
182
|
+
|
|
183
|
+
# Background process supervisor (tmux)
|
|
184
|
+
perp --json background list # list running jobs
|
|
185
|
+
perp --json background stop <ID> # stop a job
|
|
186
|
+
perp --json background logs <ID> # view job logs
|
|
187
|
+
perp --json background remove <ID> # remove a job entry
|
|
188
|
+
perp --json background clean # remove all stopped/done jobs
|
|
169
189
|
```
|
|
170
190
|
|
|
171
191
|
### Available Strategies
|
|
@@ -175,10 +195,11 @@ Arbitrage: `funding-arb`, `funding-auto`, `basis-arb`
|
|
|
175
195
|
Infrastructure: `hedge-agent`, `rfq-agent`, `claude-agent`
|
|
176
196
|
Classic: `grid`, `dca`, `twap`, `apex`
|
|
177
197
|
|
|
178
|
-
##
|
|
198
|
+
## Agent Wallet Management
|
|
179
199
|
```bash
|
|
180
|
-
perp
|
|
181
|
-
perp agent
|
|
182
|
-
perp agent
|
|
183
|
-
perp agent
|
|
200
|
+
perp wallet agent approve <exchange> [--master <name>] # Register an agent wallet
|
|
201
|
+
perp wallet agent list [exchange] # List registered agents
|
|
202
|
+
perp wallet agent revoke <exchange> <agentName> # Revoke an agent
|
|
203
|
+
perp wallet agent rotate <exchange> [agentName] # Revoke + re-approve same name
|
|
204
|
+
perp wallet agent verify [exchange] [agentName] # Verify via DEX query API
|
|
184
205
|
```
|
|
@@ -41,12 +41,12 @@ else
|
|
|
41
41
|
add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> <key>"
|
|
42
42
|
fi
|
|
43
43
|
|
|
44
|
-
# 3. Exchange connectivity
|
|
45
|
-
HEALTH_OUT=$($PERP --json
|
|
44
|
+
# 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
|
|
45
|
+
HEALTH_OUT=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
|
|
46
46
|
if echo "$HEALTH_OUT" | grep -q '"ok":true'; then
|
|
47
|
-
add_check "connectivity" "pass" "
|
|
47
|
+
add_check "connectivity" "pass" "Exchange reachable"
|
|
48
48
|
else
|
|
49
|
-
add_check "connectivity" "warn" "
|
|
49
|
+
add_check "connectivity" "warn" "Exchange unreachable"
|
|
50
50
|
fi
|
|
51
51
|
|
|
52
52
|
# 4. Portfolio & balances
|
|
@@ -38,8 +38,8 @@ fi
|
|
|
38
38
|
# 2. Run appropriate scan
|
|
39
39
|
SCAN_RESULT=$($PERP --json arb scan --mode "$MODE" --min "$MIN_SPREAD" 2>/dev/null || echo '{"ok":false}')
|
|
40
40
|
|
|
41
|
-
# 3. Get exchange health for context
|
|
42
|
-
HEALTH=$($PERP --json
|
|
41
|
+
# 3. Get exchange health for context (proxy via market prices fetch)
|
|
42
|
+
HEALTH=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
|
|
43
43
|
|
|
44
44
|
if $JSON_MODE; then
|
|
45
45
|
cat <<EOF
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
/**
|
|
3
|
-
* Tests for the alert price merging logic (extracted from alert.ts).
|
|
4
|
-
* Validates the fix for the price map key inconsistency bug.
|
|
5
|
-
*/
|
|
6
|
-
function mergePrices(pacData, hlData) {
|
|
7
|
-
const map = new Map();
|
|
8
|
-
if (pacData) {
|
|
9
|
-
for (const p of pacData) {
|
|
10
|
-
map.set(`pac:${p.symbol}`, Number(p.mark));
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
if (hlData) {
|
|
14
|
-
for (const [symbol, price] of Object.entries(hlData)) {
|
|
15
|
-
map.set(`hl:${symbol}`, Number(price));
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
// Merge into bare symbol keys: prefer pac price, fallback to hl
|
|
19
|
-
const allSymbols = new Set();
|
|
20
|
-
for (const k of map.keys()) {
|
|
21
|
-
if (k.includes(":"))
|
|
22
|
-
allSymbols.add(k.split(":")[1]);
|
|
23
|
-
}
|
|
24
|
-
for (const sym of allSymbols) {
|
|
25
|
-
const pacPrice = map.get(`pac:${sym}`);
|
|
26
|
-
const hlPrice = map.get(`hl:${sym}`);
|
|
27
|
-
map.set(sym, pacPrice ?? hlPrice ?? 0);
|
|
28
|
-
}
|
|
29
|
-
return map;
|
|
30
|
-
}
|
|
31
|
-
describe("Alert price merging", () => {
|
|
32
|
-
it("prefers Pacifica price when both available", () => {
|
|
33
|
-
const map = mergePrices([{ symbol: "BTC", mark: "100000" }], { BTC: "99900" });
|
|
34
|
-
expect(map.get("BTC")).toBe(100000);
|
|
35
|
-
expect(map.get("pac:BTC")).toBe(100000);
|
|
36
|
-
expect(map.get("hl:BTC")).toBe(99900);
|
|
37
|
-
});
|
|
38
|
-
it("falls back to HL price when pac unavailable", () => {
|
|
39
|
-
const map = mergePrices(null, { ETH: "3500" });
|
|
40
|
-
expect(map.get("ETH")).toBe(3500);
|
|
41
|
-
expect(map.has("pac:ETH")).toBe(false);
|
|
42
|
-
expect(map.get("hl:ETH")).toBe(3500);
|
|
43
|
-
});
|
|
44
|
-
it("handles Pacifica-only symbols", () => {
|
|
45
|
-
const map = mergePrices([{ symbol: "SOL", mark: "150" }], {});
|
|
46
|
-
expect(map.get("SOL")).toBe(150);
|
|
47
|
-
});
|
|
48
|
-
it("handles empty data", () => {
|
|
49
|
-
const map = mergePrices(null, null);
|
|
50
|
-
expect(map.size).toBe(0);
|
|
51
|
-
});
|
|
52
|
-
it("handles multiple symbols from both sources", () => {
|
|
53
|
-
const map = mergePrices([
|
|
54
|
-
{ symbol: "BTC", mark: "100000" },
|
|
55
|
-
{ symbol: "ETH", mark: "3500" },
|
|
56
|
-
], {
|
|
57
|
-
BTC: "99900",
|
|
58
|
-
ETH: "3490",
|
|
59
|
-
SOL: "150",
|
|
60
|
-
});
|
|
61
|
-
// Pac preferred for BTC and ETH
|
|
62
|
-
expect(map.get("BTC")).toBe(100000);
|
|
63
|
-
expect(map.get("ETH")).toBe(3500);
|
|
64
|
-
// HL-only for SOL
|
|
65
|
-
expect(map.get("SOL")).toBe(150);
|
|
66
|
-
});
|
|
67
|
-
it("bare symbol key is always set (no orphaned prefixed keys)", () => {
|
|
68
|
-
const map = mergePrices([{ symbol: "DOGE", mark: "0.15" }], { DOGE: "0.14", ARB: "1.20" });
|
|
69
|
-
// Every symbol with a prefixed key should have a bare key
|
|
70
|
-
for (const k of map.keys()) {
|
|
71
|
-
if (k.includes(":")) {
|
|
72
|
-
const bare = k.split(":")[1];
|
|
73
|
-
expect(map.has(bare)).toBe(true);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
describe("Alert trigger conditions", () => {
|
|
79
|
-
it("fires price above alert", () => {
|
|
80
|
-
const currentPrice = 105000;
|
|
81
|
-
const threshold = 100000;
|
|
82
|
-
const condition = "above";
|
|
83
|
-
expect(condition === "above" && currentPrice >= threshold).toBe(true);
|
|
84
|
-
});
|
|
85
|
-
it("does NOT fire price above when below", () => {
|
|
86
|
-
const currentPrice = 95000;
|
|
87
|
-
const threshold = 100000;
|
|
88
|
-
const condition = "above";
|
|
89
|
-
expect(condition === "above" && currentPrice >= threshold).toBe(false);
|
|
90
|
-
});
|
|
91
|
-
it("fires price below alert", () => {
|
|
92
|
-
const currentPrice = 95000;
|
|
93
|
-
const threshold = 100000;
|
|
94
|
-
const condition = "below";
|
|
95
|
-
expect(condition === "below" && currentPrice <= threshold).toBe(true);
|
|
96
|
-
});
|
|
97
|
-
it("fires funding spread alert", () => {
|
|
98
|
-
const spread = 35; // annual %
|
|
99
|
-
const alertThreshold = 30;
|
|
100
|
-
expect(spread >= alertThreshold).toBe(true);
|
|
101
|
-
});
|
|
102
|
-
it("does NOT fire funding spread below threshold", () => {
|
|
103
|
-
const spread = 25;
|
|
104
|
-
const alertThreshold = 30;
|
|
105
|
-
expect(spread >= alertThreshold).toBe(false);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { computeNetSpread, computeRoundTripCostPct, getNextSettlement, isNearSettlement, isSpreadReversed, } from "../commands/arb-auto.js";
|
|
3
|
-
/**
|
|
4
|
-
* Mirrors the direction logic in arb-auto.ts:
|
|
5
|
-
* Sort all available exchange rates by hourly-normalized rate.
|
|
6
|
-
* Long on the lowest-rate exchange, short on the highest.
|
|
7
|
-
*/
|
|
8
|
-
function determine3DexDirection(snap) {
|
|
9
|
-
return {
|
|
10
|
-
longExchange: snap.longExch,
|
|
11
|
-
shortExchange: snap.shortExch,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Mirrors the funding accumulation logic in arb-auto:
|
|
16
|
-
* Estimate funding collected based on rate differential and elapsed time.
|
|
17
|
-
*/
|
|
18
|
-
function accumulateFunding(pos, current, nowMs) {
|
|
19
|
-
const elapsedHours = (nowMs - pos.lastCheckTime) / (1000 * 60 * 60);
|
|
20
|
-
const notional = parseFloat(pos.size) * current.markPrice;
|
|
21
|
-
const rateFor = (e) => e === "pacifica" ? current.pacRate : e === "hyperliquid" ? current.hlRate : current.ltRate;
|
|
22
|
-
const longHourly = rateFor(pos.longExchange) / 1; // all exchanges are hourly
|
|
23
|
-
const shortHourly = rateFor(pos.shortExchange) / 1; // all exchanges are hourly
|
|
24
|
-
const hourlyIncome = (shortHourly - longHourly) * notional;
|
|
25
|
-
return hourlyIncome * elapsedHours;
|
|
26
|
-
}
|
|
27
|
-
// ──────────────────────────────────────────────
|
|
28
|
-
// 3-DEX direction
|
|
29
|
-
// ──────────────────────────────────────────────
|
|
30
|
-
describe("3-DEX direction determination", () => {
|
|
31
|
-
it("uses longExch/shortExch from snapshot (all 3 available)", () => {
|
|
32
|
-
const snap = {
|
|
33
|
-
symbol: "BTC",
|
|
34
|
-
pacRate: 0.0006, // pac = 0.0006/hr
|
|
35
|
-
hlRate: 0.0003, // hl = 0.0003/hr
|
|
36
|
-
ltRate: 0.0002, // lt = 0.0002/hr (lowest!)
|
|
37
|
-
spread: 240.9,
|
|
38
|
-
longExch: "lighter",
|
|
39
|
-
shortExch: "pacifica",
|
|
40
|
-
markPrice: 60000,
|
|
41
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
42
|
-
};
|
|
43
|
-
const { longExchange, shortExchange } = determine3DexDirection(snap);
|
|
44
|
-
expect(longExchange).toBe("lighter");
|
|
45
|
-
expect(shortExchange).toBe("pacifica");
|
|
46
|
-
});
|
|
47
|
-
it("handles pac vs hl only (lighter missing)", () => {
|
|
48
|
-
const snap = {
|
|
49
|
-
symbol: "ETH",
|
|
50
|
-
pacRate: 0.002, // pac = 0.002/hr (highest)
|
|
51
|
-
hlRate: 0.00005, // hl = 0.00005/hr (lowest)
|
|
52
|
-
ltRate: 0, // no lighter
|
|
53
|
-
spread: 175.2,
|
|
54
|
-
longExch: "hyperliquid",
|
|
55
|
-
shortExch: "pacifica",
|
|
56
|
-
markPrice: 3000,
|
|
57
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
58
|
-
};
|
|
59
|
-
const { longExchange, shortExchange } = determine3DexDirection(snap);
|
|
60
|
-
expect(longExchange).toBe("hyperliquid");
|
|
61
|
-
expect(shortExchange).toBe("pacifica");
|
|
62
|
-
});
|
|
63
|
-
it("handles lighter vs pac when HL missing", () => {
|
|
64
|
-
const snap = {
|
|
65
|
-
symbol: "SOL",
|
|
66
|
-
pacRate: 0.0001,
|
|
67
|
-
hlRate: 0,
|
|
68
|
-
ltRate: 0.001,
|
|
69
|
-
spread: 98.55,
|
|
70
|
-
longExch: "pacifica",
|
|
71
|
-
shortExch: "lighter",
|
|
72
|
-
markPrice: 150,
|
|
73
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
74
|
-
};
|
|
75
|
-
const { longExchange, shortExchange } = determine3DexDirection(snap);
|
|
76
|
-
expect(longExchange).toBe("pacifica");
|
|
77
|
-
expect(shortExchange).toBe("lighter");
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
// ──────────────────────────────────────────────
|
|
81
|
-
// Funding accumulation
|
|
82
|
-
// ──────────────────────────────────────────────
|
|
83
|
-
describe("Funding accumulation tracking", () => {
|
|
84
|
-
const baseTime = Date.now();
|
|
85
|
-
it("accumulates positive income when spread is favorable", () => {
|
|
86
|
-
const pos = {
|
|
87
|
-
symbol: "BTC",
|
|
88
|
-
longExchange: "hyperliquid", // low funding
|
|
89
|
-
shortExchange: "pacifica", // high funding
|
|
90
|
-
size: "0.1",
|
|
91
|
-
entrySpread: 50,
|
|
92
|
-
entryTime: new Date().toISOString(),
|
|
93
|
-
entryMarkPrice: 60000,
|
|
94
|
-
accumulatedFundingUsd: 0,
|
|
95
|
-
lastCheckTime: baseTime,
|
|
96
|
-
};
|
|
97
|
-
const snap = {
|
|
98
|
-
symbol: "BTC",
|
|
99
|
-
pacRate: 0.000125, // pac = 0.000125/hr (all hourly now)
|
|
100
|
-
hlRate: 0.00005, // hl = 0.00005/hr
|
|
101
|
-
ltRate: 0,
|
|
102
|
-
spread: 65.7,
|
|
103
|
-
longExch: "hyperliquid",
|
|
104
|
-
shortExch: "pacifica",
|
|
105
|
-
markPrice: 60000,
|
|
106
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
107
|
-
};
|
|
108
|
-
// 1 hour elapsed
|
|
109
|
-
const income = accumulateFunding(pos, snap, baseTime + 3600_000);
|
|
110
|
-
// shortHourly = 0.000125
|
|
111
|
-
// longHourly = 0.00005
|
|
112
|
-
// diff = 0.000125 - 0.00005 = 0.000075
|
|
113
|
-
// notional = 0.1 * 60000 = 6000
|
|
114
|
-
// income = 0.000075 * 6000 * 1 = $0.45/hr
|
|
115
|
-
expect(income).toBeCloseTo(0.45, 2);
|
|
116
|
-
});
|
|
117
|
-
it("returns zero income when no time elapsed", () => {
|
|
118
|
-
const pos = {
|
|
119
|
-
symbol: "ETH",
|
|
120
|
-
longExchange: "lighter",
|
|
121
|
-
shortExchange: "pacifica",
|
|
122
|
-
size: "1",
|
|
123
|
-
entrySpread: 30,
|
|
124
|
-
entryTime: new Date().toISOString(),
|
|
125
|
-
entryMarkPrice: 3000,
|
|
126
|
-
accumulatedFundingUsd: 0,
|
|
127
|
-
lastCheckTime: baseTime,
|
|
128
|
-
};
|
|
129
|
-
const snap = {
|
|
130
|
-
symbol: "ETH",
|
|
131
|
-
pacRate: 0.0005, // all hourly now
|
|
132
|
-
hlRate: 0.0001,
|
|
133
|
-
ltRate: 0.0001,
|
|
134
|
-
spread: 43.8,
|
|
135
|
-
longExch: "lighter",
|
|
136
|
-
shortExch: "pacifica",
|
|
137
|
-
markPrice: 3000,
|
|
138
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
139
|
-
};
|
|
140
|
-
const income = accumulateFunding(pos, snap, baseTime); // same time
|
|
141
|
-
expect(income).toBe(0);
|
|
142
|
-
});
|
|
143
|
-
it("scales with elapsed time", () => {
|
|
144
|
-
const pos = {
|
|
145
|
-
symbol: "BTC",
|
|
146
|
-
longExchange: "hyperliquid",
|
|
147
|
-
shortExchange: "pacifica",
|
|
148
|
-
size: "0.1",
|
|
149
|
-
entrySpread: 50,
|
|
150
|
-
entryTime: new Date().toISOString(),
|
|
151
|
-
entryMarkPrice: 60000,
|
|
152
|
-
accumulatedFundingUsd: 0,
|
|
153
|
-
lastCheckTime: baseTime,
|
|
154
|
-
};
|
|
155
|
-
const snap = {
|
|
156
|
-
symbol: "BTC",
|
|
157
|
-
pacRate: 0.000125,
|
|
158
|
-
hlRate: 0.00005,
|
|
159
|
-
ltRate: 0,
|
|
160
|
-
spread: 65.7,
|
|
161
|
-
longExch: "hyperliquid",
|
|
162
|
-
shortExch: "pacifica",
|
|
163
|
-
markPrice: 60000,
|
|
164
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
165
|
-
};
|
|
166
|
-
const income1h = accumulateFunding(pos, snap, baseTime + 3600_000);
|
|
167
|
-
const income24h = accumulateFunding(pos, snap, baseTime + 24 * 3600_000);
|
|
168
|
-
expect(income24h).toBeCloseTo(income1h * 24, 2);
|
|
169
|
-
});
|
|
170
|
-
it("can produce negative income if spread reverses", () => {
|
|
171
|
-
const pos = {
|
|
172
|
-
symbol: "BTC",
|
|
173
|
-
longExchange: "pacifica", // originally was low
|
|
174
|
-
shortExchange: "hyperliquid", // originally was high
|
|
175
|
-
size: "0.1",
|
|
176
|
-
entrySpread: 20,
|
|
177
|
-
entryTime: new Date().toISOString(),
|
|
178
|
-
entryMarkPrice: 60000,
|
|
179
|
-
accumulatedFundingUsd: 0,
|
|
180
|
-
lastCheckTime: baseTime,
|
|
181
|
-
};
|
|
182
|
-
// Now PAC rate is HIGHER than HL — bad for our direction
|
|
183
|
-
const snap = {
|
|
184
|
-
symbol: "BTC",
|
|
185
|
-
pacRate: 0.00025, // pac now high: 0.00025/hr
|
|
186
|
-
hlRate: 0.00001, // hl now low: 0.00001/hr
|
|
187
|
-
ltRate: 0,
|
|
188
|
-
spread: 218.3,
|
|
189
|
-
longExch: "hyperliquid",
|
|
190
|
-
shortExch: "pacifica",
|
|
191
|
-
markPrice: 60000,
|
|
192
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
193
|
-
};
|
|
194
|
-
const income = accumulateFunding(pos, snap, baseTime + 3600_000);
|
|
195
|
-
// shortHourly (HL) = 0.00001
|
|
196
|
-
// longHourly (PAC) = 0.00025
|
|
197
|
-
// diff = 0.00001 - 0.00025 = -0.00024
|
|
198
|
-
// notional = 6000
|
|
199
|
-
// income = -0.00024 * 6000 * 1 = -$1.44
|
|
200
|
-
expect(income).toBeLessThan(0);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
// ──────────────────────────────────────────────
|
|
204
|
-
// Entry/exit conditions with 3 DEXs
|
|
205
|
-
// ──────────────────────────────────────────────
|
|
206
|
-
describe("3-DEX entry/exit conditions", () => {
|
|
207
|
-
const minSpread = 30;
|
|
208
|
-
const closeSpread = 5;
|
|
209
|
-
it("enters when any 2-exchange spread exceeds threshold", () => {
|
|
210
|
-
// Only PAC and LT available, but spread is large
|
|
211
|
-
const absSpread = 45;
|
|
212
|
-
expect(absSpread >= minSpread).toBe(true);
|
|
213
|
-
});
|
|
214
|
-
it("closes when best available spread drops below close threshold", () => {
|
|
215
|
-
const currentSpread = 3;
|
|
216
|
-
expect(currentSpread <= closeSpread).toBe(true);
|
|
217
|
-
});
|
|
218
|
-
it("does not enter if max positions reached", () => {
|
|
219
|
-
const maxPositions = 3;
|
|
220
|
-
const openPositions = 3;
|
|
221
|
-
expect(openPositions >= maxPositions).toBe(true);
|
|
222
|
-
});
|
|
223
|
-
it("skips symbol already in open positions", () => {
|
|
224
|
-
const openPositions = [{
|
|
225
|
-
symbol: "BTC",
|
|
226
|
-
longExchange: "hyperliquid",
|
|
227
|
-
shortExchange: "pacifica",
|
|
228
|
-
size: "0.1",
|
|
229
|
-
entrySpread: 40,
|
|
230
|
-
entryTime: new Date().toISOString(),
|
|
231
|
-
entryMarkPrice: 60000,
|
|
232
|
-
accumulatedFundingUsd: 0.5,
|
|
233
|
-
lastCheckTime: Date.now(),
|
|
234
|
-
}];
|
|
235
|
-
const alreadyOpen = openPositions.some(p => p.symbol === "BTC");
|
|
236
|
-
expect(alreadyOpen).toBe(true);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
// ──────────────────────────────────────────────
|
|
240
|
-
// Net spread calculation
|
|
241
|
-
// ──────────────────────────────────────────────
|
|
242
|
-
describe("computeNetSpread", () => {
|
|
243
|
-
it("correctly deducts annualized round-trip cost from gross spread", () => {
|
|
244
|
-
// gross=30%, hold=7d, roundTrip=0.14% → net = 30 - (0.14/7*365) = 30 - 7.3 = 22.7
|
|
245
|
-
const net = computeNetSpread(30, 7, 0.14);
|
|
246
|
-
expect(net).toBeCloseTo(22.7, 1);
|
|
247
|
-
});
|
|
248
|
-
it("returns gross spread when costs are zero", () => {
|
|
249
|
-
const net = computeNetSpread(50, 7, 0);
|
|
250
|
-
expect(net).toBe(50);
|
|
251
|
-
});
|
|
252
|
-
it("can produce negative net spread when costs exceed gross", () => {
|
|
253
|
-
// gross=5%, hold=1d, roundTrip=0.14% → net = 5 - (0.14*365) = 5 - 51.1 = -46.1
|
|
254
|
-
const net = computeNetSpread(5, 1, 0.14);
|
|
255
|
-
expect(net).toBeLessThan(0);
|
|
256
|
-
});
|
|
257
|
-
it("longer hold periods reduce annualized cost impact", () => {
|
|
258
|
-
const net7d = computeNetSpread(30, 7, 0.14);
|
|
259
|
-
const net30d = computeNetSpread(30, 30, 0.14);
|
|
260
|
-
expect(net30d).toBeGreaterThan(net7d);
|
|
261
|
-
});
|
|
262
|
-
it("includes bridge cost in net spread calculation", () => {
|
|
263
|
-
// Without bridge cost
|
|
264
|
-
const netNoBridge = computeNetSpread(30, 7, 0.14, 0, 100);
|
|
265
|
-
// With $0.50 bridge cost, $100 position
|
|
266
|
-
// bridgeRoundTripPct = (0.5 * 2 / 100) * 100 = 1%
|
|
267
|
-
// bridgeAnnualized = (1/7) * 365 = 52.14%
|
|
268
|
-
const netWithBridge = computeNetSpread(30, 7, 0.14, 0.5, 100);
|
|
269
|
-
expect(netWithBridge).toBeLessThan(netNoBridge);
|
|
270
|
-
// Difference should be the annualized bridge cost
|
|
271
|
-
const bridgeDiff = netNoBridge - netWithBridge;
|
|
272
|
-
expect(bridgeDiff).toBeCloseTo(52.14, 0);
|
|
273
|
-
});
|
|
274
|
-
it("bridge cost impact scales inversely with position size", () => {
|
|
275
|
-
const netSmall = computeNetSpread(30, 7, 0.14, 0.5, 50); // $50 position
|
|
276
|
-
const netLarge = computeNetSpread(30, 7, 0.14, 0.5, 500); // $500 position
|
|
277
|
-
// Larger positions dilute bridge cost
|
|
278
|
-
expect(netLarge).toBeGreaterThan(netSmall);
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
describe("computeRoundTripCostPct", () => {
|
|
282
|
-
it("computes round-trip cost for same-fee exchanges", () => {
|
|
283
|
-
// 2 × (0.035% + 0.035%) + 2 × 0.05% = 0.24%
|
|
284
|
-
const cost = computeRoundTripCostPct("hyperliquid", "pacifica", 0.05);
|
|
285
|
-
expect(cost).toBeCloseTo(0.24, 4);
|
|
286
|
-
});
|
|
287
|
-
it("uses default slippage of 0.05%", () => {
|
|
288
|
-
const cost = computeRoundTripCostPct("hyperliquid", "lighter");
|
|
289
|
-
// 2 × (0.035% + 0.035%) + 2 × 0.05% = 0.24%
|
|
290
|
-
expect(cost).toBeCloseTo(0.24, 4);
|
|
291
|
-
});
|
|
292
|
-
it("handles custom slippage", () => {
|
|
293
|
-
const cost = computeRoundTripCostPct("hyperliquid", "pacifica", 0.1);
|
|
294
|
-
// 2 × (0.035% + 0.035%) + 2 × 0.1% = 0.34%
|
|
295
|
-
expect(cost).toBeCloseTo(0.34, 4);
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
// ──────────────────────────────────────────────
|
|
299
|
-
// Spread reversal detection
|
|
300
|
-
// ──────────────────────────────────────────────
|
|
301
|
-
describe("Spread reversal detection", () => {
|
|
302
|
-
it("detects reversal when long exchange rate exceeds short", () => {
|
|
303
|
-
const snap = {
|
|
304
|
-
symbol: "BTC",
|
|
305
|
-
pacRate: 0.0001, // PAC low (was short, now low)
|
|
306
|
-
hlRate: 0.0005, // HL high (was long, now high)
|
|
307
|
-
ltRate: 0,
|
|
308
|
-
spread: 30,
|
|
309
|
-
longExch: "pacifica",
|
|
310
|
-
shortExch: "hyperliquid",
|
|
311
|
-
markPrice: 60000,
|
|
312
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
313
|
-
};
|
|
314
|
-
// Position: long HL, short PAC — but now HL hourly (0.0005) > PAC hourly (0.0001)
|
|
315
|
-
const reversed = isSpreadReversed("hyperliquid", "pacifica", snap);
|
|
316
|
-
expect(reversed).toBe(true);
|
|
317
|
-
});
|
|
318
|
-
it("does not flag reversal when spread is still favorable", () => {
|
|
319
|
-
const snap = {
|
|
320
|
-
symbol: "BTC",
|
|
321
|
-
pacRate: 0.001, // PAC high (0.001/hr)
|
|
322
|
-
hlRate: 0.00005, // HL low (0.00005/hr)
|
|
323
|
-
ltRate: 0,
|
|
324
|
-
spread: 65.7,
|
|
325
|
-
longExch: "hyperliquid",
|
|
326
|
-
shortExch: "pacifica",
|
|
327
|
-
markPrice: 60000,
|
|
328
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
329
|
-
};
|
|
330
|
-
// Position: long HL, short PAC — HL hourly (0.00005) < PAC hourly (0.001) → no reversal
|
|
331
|
-
const reversed = isSpreadReversed("hyperliquid", "pacifica", snap);
|
|
332
|
-
expect(reversed).toBe(false);
|
|
333
|
-
});
|
|
334
|
-
it("handles lighter vs pacifica reversal", () => {
|
|
335
|
-
const snap = {
|
|
336
|
-
symbol: "SOL",
|
|
337
|
-
pacRate: 0.0001, // PAC hourly = 0.0001
|
|
338
|
-
hlRate: 0,
|
|
339
|
-
ltRate: 0.0016, // LT 8h rate, hourly = 0.0016/8 = 0.0002
|
|
340
|
-
spread: 10,
|
|
341
|
-
longExch: "pacifica",
|
|
342
|
-
shortExch: "lighter",
|
|
343
|
-
markPrice: 150,
|
|
344
|
-
pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
|
|
345
|
-
};
|
|
346
|
-
// Position: long LT, short PAC — LT hourly (0.0002) > PAC hourly (0.0001) → reversed
|
|
347
|
-
const reversed = isSpreadReversed("lighter", "pacifica", snap);
|
|
348
|
-
expect(reversed).toBe(true);
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
// ──────────────────────────────────────────────
|
|
352
|
-
// Settlement timing awareness
|
|
353
|
-
// ──────────────────────────────────────────────
|
|
354
|
-
describe("Settlement timing awareness", () => {
|
|
355
|
-
it("getNextSettlement returns next hour for HL", () => {
|
|
356
|
-
// At 14:30 UTC, next HL settlement is at 15:00 UTC
|
|
357
|
-
const now = new Date("2025-01-15T14:30:00Z");
|
|
358
|
-
const next = getNextSettlement("hyperliquid", now);
|
|
359
|
-
expect(next.getUTCHours()).toBe(15);
|
|
360
|
-
expect(next.getUTCMinutes()).toBe(0);
|
|
361
|
-
});
|
|
362
|
-
it("getNextSettlement returns next hour for PAC (hourly like HL)", () => {
|
|
363
|
-
// At 06:00 UTC, next PAC settlement is at 07:00 UTC
|
|
364
|
-
const now = new Date("2025-01-15T06:30:00Z");
|
|
365
|
-
const next = getNextSettlement("pacifica", now);
|
|
366
|
-
expect(next.getUTCHours()).toBe(7);
|
|
367
|
-
});
|
|
368
|
-
it("getNextSettlement wraps to next day for PAC when at 23:xx", () => {
|
|
369
|
-
// At 23:30 UTC, next PAC settlement is at 00:00 next day
|
|
370
|
-
const now = new Date("2025-01-15T23:30:00Z");
|
|
371
|
-
const next = getNextSettlement("pacifica", now);
|
|
372
|
-
expect(next.getUTCHours()).toBe(0);
|
|
373
|
-
expect(next.getUTCDate()).toBe(16);
|
|
374
|
-
});
|
|
375
|
-
it("isNearSettlement blocks entry within 5 minutes of settlement", () => {
|
|
376
|
-
// 3 minutes before 08:00 UTC settlement — all exchanges settle hourly now
|
|
377
|
-
const now = new Date("2025-01-15T07:57:00Z");
|
|
378
|
-
const result = isNearSettlement("lighter", "pacifica", 5, now);
|
|
379
|
-
expect(result.blocked).toBe(true);
|
|
380
|
-
// Both lighter and pacifica settle at 08:00, lighter checked first
|
|
381
|
-
expect(result.exchange).toBe("lighter");
|
|
382
|
-
expect(result.minutesUntil).toBeLessThanOrEqual(5);
|
|
383
|
-
});
|
|
384
|
-
it("isNearSettlement allows entry far from settlement", () => {
|
|
385
|
-
// 30 minutes past the hour — next settlement is 30 min away
|
|
386
|
-
const now = new Date("2025-01-15T15:30:00Z");
|
|
387
|
-
const result = isNearSettlement("lighter", "pacifica", 5, now);
|
|
388
|
-
expect(result.blocked).toBe(false);
|
|
389
|
-
});
|
|
390
|
-
it("isNearSettlement checks both exchanges", () => {
|
|
391
|
-
// 2 minutes before hourly HL settlement
|
|
392
|
-
const now = new Date("2025-01-15T14:58:00Z");
|
|
393
|
-
const result = isNearSettlement("hyperliquid", "pacifica", 5, now);
|
|
394
|
-
expect(result.blocked).toBe(true);
|
|
395
|
-
expect(result.exchange).toBe("hyperliquid");
|
|
396
|
-
});
|
|
397
|
-
});
|