perp-cli 0.12.17 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +247 -27
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +70 -92
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +142 -55
  348. package/skills/perp-cli/references/agent-operations.md +110 -56
  349. package/skills/perp-cli/references/commands.md +266 -71
  350. package/skills/perp-cli/references/strategies.md +34 -31
  351. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  352. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +31 -15
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. 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 rates settle **every 1 hour** on all supported exchanges
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 in display but applied hourly: `hourly = annual / 8760`
12
- - Scan opportunities: `perp --json arb scan --min 5` (shows spread, longExch, shortExch, netSpread)
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
 
@@ -69,12 +70,14 @@ The `arb scan` output tells you EXACTLY what to do:
69
70
  ```
70
71
  longExch: "hyperliquid" → open LONG on this exchange
71
72
  shortExch: "pacifica" → open SHORT on this exchange
72
- netSpread: 12.87 → profit after fees (bps/hour)
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)
73
76
  ```
74
77
 
75
78
  **Rules:**
76
79
  1. **ALWAYS follow longExch/shortExch directions exactly.** DO NOT reverse them.
77
- 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`.
78
81
  3. The direction logic: go LONG where funding is negative (longs receive), go SHORT where funding is positive (shorts receive).
79
82
 
80
83
  **Why these directions?**
@@ -94,7 +97,7 @@ When evaluating an arb opportunity:
94
97
 
95
98
  3. **How long will the spread persist?**
96
99
  - Historical funding tends to mean-revert
97
- - Higher confidence in moderate, stable spreads (20-50 bps) than extreme spikes (>100 bps)
100
+ - Higher confidence in moderate, stable spreads (e.g. 10–50% annualized netSpread) than extreme spikes (hundreds of % annualized)
98
101
 
99
102
  4. **Can I execute both legs atomically?**
100
103
  - Both positions should open near-simultaneously to minimize directional exposure
@@ -105,19 +108,20 @@ When evaluating an arb opportunity:
105
108
  **Before entering any arb position, set a target hold duration.** This is critical for calculating whether the position is worth entering.
106
109
 
107
110
  ```
108
- Expected Profit = hourly_spread × target_hours
109
- Entry/Exit Cost = open_fees + close_fees + slippage (both legs)
110
- Net Profit = Expected Profit - Entry/Exit Cost
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
111
114
  ```
115
+ (`netSpread` already does this amortization for `--hold-days`; pass `--hold-days` = your target to read it directly.)
112
116
 
113
117
  **Only enter if Net Profit > 0 with a comfortable margin.**
114
118
 
115
119
  Guidelines for hold duration:
116
- - **Stable spreads (20-50 bps):** target 8-24 hours. These are reliable but low-yield — you need time for the funding to accumulate past your entry/exit costs.
117
- - **Elevated spreads (50-100 bps):** target 4-8 hours. Higher yield, but likely to compress. Take profit earlier.
118
- - **Spike spreads (>100 bps):** target 1-4 hours. These revert quickly. Must cover entry/exit costs within a few funding cycles.
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.
119
123
 
120
- **Re-evaluate at each funding settlement (every hour):**
124
+ **Re-evaluate at each funding settlement (hourly on most venues):**
121
125
  1. Is the spread still above breakeven for the remaining target hours?
122
126
  2. If spread compressed, should I exit early or extend the hold?
123
127
  3. Has a better opportunity appeared? (Remember: switching has its own cost)
@@ -126,12 +130,12 @@ Guidelines for hold duration:
126
130
 
127
131
  Example entry decision log:
128
132
  ```
129
- Entry: BTC HL↔PAC | Spread: 35 bps | Target hold: 12h
130
- Expected: 35 bps × 12h = 420 bps gross
131
- Entry/exit cost: ~80 bps (fees + slippage both legs)
132
- Net expected: ~340 bps → ENTER
133
- Hour 6 check: spread compressed to 15 bps → below breakeven for remaining 6h → EXIT
134
- Actual hold: 6h | Actual net: ~130 bps
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
135
139
  ```
136
140
 
137
141
  ### Monitoring Active Positions
@@ -181,6 +185,8 @@ Round `ORDER_SIZE` to the coarser step size of the two exchanges. If the matched
181
185
 
182
186
  #### Step 2: Execute Legs Sequentially with Same Size
183
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
+
184
190
  Once you have a single `ORDER_SIZE`, execute both legs using THAT EXACT SIZE:
185
191
 
186
192
  ```bash
@@ -243,7 +249,7 @@ Leg B: perp --json -e <SHORT_EX> trade split <SYM> sell 5000
243
249
  #### Using Limit Orders for Better Execution
244
250
  For non-urgent entries, use limit orders at best bid/ask instead of market:
245
251
  ```bash
246
- perp --json -e <EX> trade buy <SYM> <ORDER_SIZE> -p <PRICE> # limit order
252
+ perp --json -e <EX> trade limit <SYM> buy <PRICE> <ORDER_SIZE> # limit order (GTC default; --tif ALO = post-only)
247
253
  ```
248
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`.**
249
255
 
@@ -311,7 +317,7 @@ perp --json -e <EX> trade leverage <SYM> <LEVERAGE>
311
317
  **For funding rate arb, use ISOLATED margin.** Each leg should be independent — if one side gets liquidated, the other side survives.
312
318
 
313
319
  ```bash
314
- perp --json manage margin <SYM> isolated # set isolated margin
320
+ perp --json -e <EX> wallet manage margin <SYM> isolated # set isolated margin
315
321
  perp --json -e <EX> trade leverage <SYM> <LEV> --isolated # set leverage + isolated at once
316
322
  ```
317
323
 
@@ -407,8 +413,8 @@ Rules of thumb:
407
413
 
408
414
  Each exchange holds its own separate balance. Before entering any arb:
409
415
  ```bash
410
- perp --json -e <EX_A> account balance # check available balance on exchange A
411
- perp --json -e <EX_B> account balance # check available balance on exchange 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
412
418
  ```
413
419
 
414
420
  **Size each leg to fit the available balance on that exchange:**
@@ -428,15 +434,12 @@ Even "market-neutral" arb can lose money if:
428
434
  - Extreme funding spike in the wrong direction
429
435
  - Slippage on entry/exit far exceeds estimates
430
436
 
431
- **Always set stop losses on both legs:**
432
- ```bash
433
- perp --json -e <EX_A> trade sl <SYM> <PRICE> # stop loss on leg A
434
- perp --json -e <EX_B> trade sl <SYM> <PRICE> # stop loss on leg B
435
- ```
436
-
437
- 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):
438
438
  ```bash
439
- perp --json -e <EX> trade tp-sl <SYM> --tp <PRICE> --sl <PRICE>
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
440
443
  ```
441
444
 
442
445
  ### What to Track Over Time
@@ -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
@@ -24,8 +29,8 @@ while [[ $# -gt 0 ]]; do
24
29
  done
25
30
 
26
31
  # 1. Get current arb status (already includes funding earned per period)
27
- ARB_STATUS=$($PERP --json arb status 2>/dev/null || echo '{"ok":false}')
28
- if ! echo "$ARB_STATUS" | grep -q '"ok":true'; then
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
@@ -35,13 +40,13 @@ if ! echo "$ARB_STATUS" | grep -q '"ok":true'; then
35
40
  fi
36
41
 
37
42
  # 2. Get current spreads (all modes)
38
- ARB_SCAN=$($PERP --json arb scan --mode all --min 0 2>/dev/null || echo '{"ok":false,"data":{"opportunities":[]}}')
43
+ ARB_SCAN=$(pj --json arb scan --mode all --min 0)
39
44
 
40
45
  # 3. Get liquidation distances
41
- LIQ_DIST=$($PERP --json risk liquidation-distance 2>/dev/null || echo '{"ok":false}')
46
+ LIQ_DIST=$(pj --json risk liquidation-distance)
42
47
 
43
48
  # 4. Get risk overview
44
- RISK_STATUS=$($PERP --json risk status 2>/dev/null || echo '{"ok":false}')
49
+ RISK_STATUS=$(pj --json risk status)
45
50
 
46
51
  # 5. Output combined analysis
47
52
  if $JSON_MODE; then
@@ -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,10 +31,10 @@ done
26
31
 
27
32
  if [[ -n "$SYMBOL" ]]; then
28
33
  # Single symbol detailed analysis
29
- FUNDING=$($PERP --json arb scan --history "$SYMBOL" 2>/dev/null || echo '{"ok":false}')
30
- COMPARE=$($PERP --json arb scan --compare "$SYMBOL" 2>/dev/null || echo '{"ok":false}')
31
- SCAN=$($PERP --json arb scan --mode perp-perp --min 0 2>/dev/null || echo '{"ok":false}')
32
- SPOT_SCAN=$($PERP --json arb scan --mode spot-perp --min 0 2>/dev/null || echo '{"ok":false}')
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)
33
38
 
34
39
  if $JSON_MODE; then
35
40
  cat <<EOF
@@ -55,19 +60,19 @@ EOF
55
60
  echo "--- Perp-Perp Arb ---"
56
61
  echo "$SCAN" | jq -r '
57
62
  .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
58
- " \(.longExch // .longExchange)→\(.shortExch // .shortExchange): \(.netSpread // .spread)% annual"
63
+ " \(.longExch // .longExchange)→\(.shortExch // .shortExchange): net \(.netSpread)% annual"
59
64
  ' 2>/dev/null || echo " (no opportunity)"
60
65
  echo ""
61
66
  echo "--- Spot+Perp Arb ---"
62
67
  echo "$SPOT_SCAN" | jq -r '
63
68
  .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
64
- " \(.direction): \(.annualSpreadPct // .spread)% annual"
69
+ " \(.direction) spot:\((.spotExchanges // []) | join("/"))→\(.perpExchange): net \(.netSpreadPct)% annual"
65
70
  ' 2>/dev/null || echo " (no opportunity)"
66
71
  fi
67
72
  else
68
73
  # All symbols scan
69
- SCAN=$($PERP --json arb scan --mode all --min 0 2>/dev/null || echo '{"ok":false}')
70
- FUNDING_DETAIL=$($PERP --json arb scan --rates 2>/dev/null || echo '{"ok":false}')
74
+ SCAN=$(pj --json arb scan --mode all --min 0)
75
+ FUNDING_DETAIL=$(pj --json arb scan --rates)
71
76
 
72
77
  if $JSON_MODE; then
73
78
  cat <<EOF
@@ -84,7 +89,7 @@ EOF
84
89
  echo ""
85
90
  echo "$SCAN" | jq -r "
86
91
  .data // [] | .[0:$TOP] | .[] |
87
- \" \(.symbol) | \(.mode // \"perp\") | \(.netSpread // .spread)bps | \(.longExch // .longExchange)→\(.shortExch // .shortExchange)\"
92
+ \" \(.symbol) | \(.mode // \"perp\") | net \(.netSpread // .netSpreadPct)%/yr | \(.longExch // (\"spot:\" + ((.spotExchanges // []) | join(\"/\"))))→\(.shortExch // .perpExchange)\"
88
93
  " 2>/dev/null || echo "(no data)"
89
94
  fi
90
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" ]] && issues+=("$name: $detail")
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 — run: npm i -g perp-cli@latest or use npx"
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=$($PERP --json wallet show 2>/dev/null || echo '{"ok":false}')
38
- if echo "$WALLET_OUT" | grep -q '"ok":true'; then
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> <key>"
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=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
46
- if echo "$HEALTH_OUT" | grep -q '"ok":true'; then
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=$($PERP --json portfolio 2>/dev/null || echo '{"ok":false}')
54
- if echo "$PORTFOLIO_OUT" | grep -q '"ok":true'; then
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=$($PERP --json risk status 2>/dev/null || echo '{"ok":false}')
62
- if echo "$RISK_OUT" | grep -q '"canTrade":true'; then
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 echo "$RISK_OUT" | grep -q '"canTrade":false'; then
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 ]] && READY=false
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=$(printf '%s\n' "${issues[@]}" | jq -R . | jq -s .)
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=$($PERP --json portfolio 2>/dev/null || echo '{"ok":false}')
29
- if ! echo "$PORTFOLIO" | grep -q '"ok":true'; then
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=$($PERP --json arb scan --mode "$MODE" --min "$MIN_SPREAD" 2>/dev/null || echo '{"ok":false}')
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=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
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} bps"
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
- " \(.exchange): $\(.balance // .equity // "N/A")"
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") | spread: \(.netSpread // .spread)bps | \(.longExch // .longExchange)→\(.shortExch // .shortExchange)"
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
@@ -14,6 +14,11 @@ else
14
14
  PERP="npx -y perp-cli@latest"
15
15
  fi
16
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
+
17
22
  SYM="${1:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]}"
18
23
  LONG_EX="${2:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD>}"
19
24
  SHORT_EX="${3:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD>}"
@@ -34,12 +39,22 @@ pass=true
34
39
  add_result() {
35
40
  local name="$1" ok="$2" detail="$3"
36
41
  checks+=("{\"check\":\"$name\",\"ok\":$ok,\"detail\":\"$detail\"}")
37
- [[ "$ok" == "false" ]] && pass=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
38
53
  }
39
54
 
40
55
  # 1. Price comparison — both exchanges must report similar prices
41
- LONG_PRICE=$($PERP --json -e "$LONG_EX" market mid "$SYM" 2>/dev/null | jq -r '.data.mid // "0"')
42
- SHORT_PRICE=$($PERP --json -e "$SHORT_EX" market mid "$SYM" 2>/dev/null | jq -r '.data.mid // "0"')
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)
43
58
 
44
59
  if [[ "$LONG_PRICE" == "0" || "$SHORT_PRICE" == "0" ]]; then
45
60
  add_result "price" "false" "Cannot fetch price: long=$LONG_PRICE short=$SHORT_PRICE"
@@ -54,8 +69,8 @@ else
54
69
  fi
55
70
 
56
71
  # 2. Balance check on both exchanges
57
- LONG_BAL=$($PERP --json -e "$LONG_EX" account balance 2>/dev/null | jq -r '.data.available // .data.equity // "0"')
58
- SHORT_BAL=$($PERP --json -e "$SHORT_EX" account balance 2>/dev/null | jq -r '.data.available // .data.equity // "0"')
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)
59
74
 
60
75
  NEEDED=$(echo "$SIZE" | awk '{print $1}')
61
76
  if (( $(echo "$LONG_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
@@ -70,31 +85,32 @@ else
70
85
  fi
71
86
 
72
87
  # 3. Market info (symbol availability)
73
- LONG_INFO=$($PERP --json -e "$LONG_EX" market info "$SYM" 2>/dev/null || echo '{"ok":false}')
74
- SHORT_INFO=$($PERP --json -e "$SHORT_EX" market info "$SYM" 2>/dev/null || echo '{"ok":false}')
88
+ LONG_INFO=$(pj --json -e "$LONG_VENUE" market info "$SYM")
89
+ SHORT_INFO=$(pj --json -e "$SHORT_EX" market info "$SYM")
75
90
 
76
- if echo "$LONG_INFO" | grep -q '"ok":true' && echo "$SHORT_INFO" | grep -q '"ok":true'; then
91
+ if isok "$LONG_INFO" && isok "$SHORT_INFO"; then
77
92
  add_result "market_info" "true" "Both exchanges support $SYM"
78
93
  else
79
94
  add_result "market_info" "false" "Symbol $SYM not available on one or both exchanges"
80
95
  fi
81
96
 
82
- # 4. Risk pre-check
97
+ # 4. Risk pre-check (risk check requires --leverage; default 1x when not given)
83
98
  LEV_FLAG=""
84
99
  [[ -n "$LEVERAGE" ]] && LEV_FLAG="--leverage $LEVERAGE"
85
- RISK_CHECK=$($PERP --json risk check --notional "$SIZE" $LEV_FLAG 2>/dev/null || echo '{"ok":false}')
86
- if echo "$RISK_CHECK" | grep -q '"ok":true'; then
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
87
102
  add_result "risk" "true" "Within risk limits"
88
103
  else
89
- add_result "risk" "false" "Risk check failed — review risk limits"
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"
90
106
  fi
91
107
 
92
108
  # 5. Dry-run
93
- DRY_RUN=$($PERP --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LEV_FLAG 2>/dev/null || echo '{"ok":false}')
94
- if echo "$DRY_RUN" | grep -q '"ok":true'; then
109
+ DRY_RUN=$(pj --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LEV_FLAG)
110
+ if isok "$DRY_RUN"; then
95
111
  add_result "dry_run" "true" "Dry-run succeeded"
96
112
  else
97
- DRY_ERR=$(echo "$DRY_RUN" | jq -r '.error.message // .error // "unknown"' 2>/dev/null)
113
+ DRY_ERR=$(jq -r '.error.code // "unknown"' <<<"$DRY_RUN" 2>/dev/null || echo unknown)
98
114
  add_result "dry_run" "false" "Dry-run failed: $DRY_ERR"
99
115
  fi
100
116
 
@@ -0,0 +1,2 @@
1
+ 781ba28b5e7fca1ea816f516f28fe2adbe704b734828e0c941025f8133bd7b4b lighter-signer.wasm
2
+ 0c949f4996f9a89698e4b5c586de32249c3b69b7baadb64d220073cc04acba14 wasm_exec.js
@@ -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.