perp-cli 0.14.3 → 0.16.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 (363) hide show
  1. package/README.ja.md +243 -0
  2. package/README.ko.md +243 -0
  3. package/README.md +138 -405
  4. package/README.zh.md +243 -0
  5. package/dist/agent-wallet/store.d.ts +3 -2
  6. package/dist/agent-wallet/store.js +2 -2
  7. package/dist/api/public/hyperliquid.js +4 -0
  8. package/dist/api/public/lighter.d.ts +5 -0
  9. package/dist/api/public/lighter.js +5 -0
  10. package/dist/api/public/pacifica.js +85 -15
  11. package/dist/api/public/urls.d.ts +2 -0
  12. package/dist/api/public/urls.js +3 -0
  13. package/dist/arb/sizing.js +4 -2
  14. package/dist/arb/state.d.ts +6 -0
  15. package/dist/arb/utils.d.ts +1 -2
  16. package/dist/arb/utils.js +1 -2
  17. package/dist/balance-by-address.d.ts +39 -0
  18. package/dist/balance-by-address.js +44 -0
  19. package/dist/bot/apex/pulse.js +3 -0
  20. package/dist/bot/apex/radar.js +2 -1
  21. package/dist/bot/engine.d.ts +10 -1
  22. package/dist/bot/engine.js +14 -6
  23. package/dist/bot/indicators.d.ts +7 -0
  24. package/dist/bot/indicators.js +16 -0
  25. package/dist/bot/perp-order.d.ts +4 -4
  26. package/dist/bot/perp-order.js +5 -5
  27. package/dist/bot/quarantine.d.ts +1 -1
  28. package/dist/bot/quarantine.js +1 -1
  29. package/dist/bot/quote-rules.js +1 -1
  30. package/dist/bot/strategies/apex-strategy.js +3 -2
  31. package/dist/bot/strategies/engine-mm.js +6 -5
  32. package/dist/bot/strategies/funding-arb-strategy.js +12 -5
  33. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -3
  34. package/dist/bot/strategies/funding-arb-utils.js +53 -20
  35. package/dist/bot/strategies/funding-auto.js +2 -2
  36. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  37. package/dist/bot/strategies/liquidation-mm.js +8 -2
  38. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  39. package/dist/bot/strategies/momentum-breakout.js +11 -3
  40. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +1 -1
  41. package/dist/bot/strategies/spot-perp-arb-strategy.js +6 -4
  42. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  43. package/dist/bot/strategies/spot-perp-executor.js +3 -3
  44. package/dist/bot/strategy-types.d.ts +2 -1
  45. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  46. package/dist/bot/tui/BotDashboard.js +1 -1
  47. package/dist/cache.js +12 -6
  48. package/dist/cli-program.d.ts +15 -0
  49. package/dist/cli-program.js +126 -6
  50. package/dist/commands/account.js +168 -24
  51. package/dist/commands/agent-import.d.ts +7 -0
  52. package/dist/commands/agent-import.js +30 -1
  53. package/dist/commands/agent.d.ts +6 -3
  54. package/dist/commands/agent.js +160 -12
  55. package/dist/commands/arb/index.js +23 -81
  56. package/dist/commands/arb-auto.d.ts +24 -0
  57. package/dist/commands/arb-auto.js +175 -97
  58. package/dist/commands/arb.d.ts +20 -0
  59. package/dist/commands/arb.js +107 -163
  60. package/dist/commands/backtest.js +23 -21
  61. package/dist/commands/bot.js +1 -1
  62. package/dist/commands/builder.js +1 -1
  63. package/dist/commands/funds.js +113 -3
  64. package/dist/commands/init.d.ts +1 -1
  65. package/dist/commands/init.js +27 -7
  66. package/dist/commands/manage.js +23 -21
  67. package/dist/commands/market.d.ts +48 -0
  68. package/dist/commands/market.js +395 -100
  69. package/dist/commands/options.d.ts +14 -0
  70. package/dist/commands/options.js +151 -0
  71. package/dist/commands/pacifica-earn.d.ts +5 -0
  72. package/dist/commands/pacifica-earn.js +455 -0
  73. package/dist/commands/pacifica-orders.d.ts +14 -0
  74. package/dist/commands/pacifica-orders.js +142 -0
  75. package/dist/commands/portfolio.d.ts +16 -0
  76. package/dist/commands/portfolio.js +83 -55
  77. package/dist/commands/rebalance.d.ts +1 -0
  78. package/dist/commands/rebalance.js +15 -0
  79. package/dist/commands/risk.d.ts +11 -2
  80. package/dist/commands/risk.js +39 -44
  81. package/dist/commands/trade.d.ts +1 -0
  82. package/dist/commands/trade.js +67 -62
  83. package/dist/commands/wallet.js +1 -1
  84. package/dist/config.d.ts +2 -2
  85. package/dist/config.js +15 -4
  86. package/dist/constants.js +6 -0
  87. package/dist/cross-chain-margin.d.ts +3 -1
  88. package/dist/cross-chain-margin.js +4 -2
  89. package/dist/dashboard/server.js +3 -3
  90. package/dist/dashboard/ui.js +9 -6
  91. package/dist/errors.d.ts +29 -1
  92. package/dist/errors.js +35 -3
  93. package/dist/exchanges/address.d.ts +8 -0
  94. package/dist/exchanges/address.js +47 -0
  95. package/dist/exchanges/arcus/adapter.d.ts +16 -12
  96. package/dist/exchanges/arcus/adapter.js +45 -18
  97. package/dist/exchanges/arcus/agent.d.ts +1 -1
  98. package/dist/exchanges/arcus/agent.js +1 -1
  99. package/dist/exchanges/arcus/client.js +1 -1
  100. package/dist/exchanges/arcus/public.js +3 -2
  101. package/dist/exchanges/arcus/signing.d.ts +2 -3
  102. package/dist/exchanges/arcus/signing.js +1 -1
  103. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  104. package/dist/exchanges/arcus/spot-chain.js +1 -1
  105. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  106. package/dist/exchanges/arcus/spot-router.js +2 -2
  107. package/dist/exchanges/arcus/spot.d.ts +2 -3
  108. package/dist/exchanges/arcus/spot.js +2 -3
  109. package/dist/exchanges/aster-spot.d.ts +3 -3
  110. package/dist/exchanges/aster-spot.js +6 -9
  111. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  112. package/dist/exchanges/aster-typed-data.js +1 -1
  113. package/dist/exchanges/aster.d.ts +30 -10
  114. package/dist/exchanges/aster.js +153 -53
  115. package/dist/exchanges/builder-fee.d.ts +1 -1
  116. package/dist/exchanges/builder-fee.js +13 -2
  117. package/dist/exchanges/canonical-symbols.d.ts +8 -2
  118. package/dist/exchanges/canonical-symbols.js +46 -2
  119. package/dist/exchanges/capabilities.d.ts +2 -0
  120. package/dist/exchanges/capabilities.js +2 -1
  121. package/dist/exchanges/derive/client.d.ts +37 -0
  122. package/dist/exchanges/derive/client.js +135 -0
  123. package/dist/exchanges/derive/options.d.ts +73 -0
  124. package/dist/exchanges/derive/options.js +379 -0
  125. package/dist/exchanges/extended/adapter.d.ts +96 -0
  126. package/dist/exchanges/extended/adapter.js +241 -0
  127. package/dist/exchanges/extended/client.d.ts +38 -0
  128. package/dist/exchanges/extended/client.js +88 -0
  129. package/dist/exchanges/extended/public.d.ts +133 -0
  130. package/dist/exchanges/extended/public.js +436 -0
  131. package/dist/exchanges/grvt/adapter.d.ts +100 -0
  132. package/dist/exchanges/grvt/adapter.js +283 -0
  133. package/dist/exchanges/grvt/client.d.ts +25 -0
  134. package/dist/exchanges/grvt/client.js +86 -0
  135. package/dist/exchanges/grvt/public.d.ts +151 -0
  136. package/dist/exchanges/grvt/public.js +438 -0
  137. package/dist/exchanges/hyperliquid-outcome.d.ts +2 -0
  138. package/dist/exchanges/hyperliquid-outcome.js +29 -1
  139. package/dist/exchanges/hyperliquid-spot.d.ts +9 -1
  140. package/dist/exchanges/hyperliquid-spot.js +24 -16
  141. package/dist/exchanges/hyperliquid.d.ts +199 -22
  142. package/dist/exchanges/hyperliquid.js +550 -202
  143. package/dist/exchanges/index.d.ts +1 -1
  144. package/dist/exchanges/index.js +1 -1
  145. package/dist/exchanges/interface.d.ts +157 -13
  146. package/dist/exchanges/interface.js +47 -0
  147. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  148. package/dist/exchanges/ioc-outcome.js +1 -1
  149. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  150. package/dist/exchanges/legacy-trading-v2.js +7 -2
  151. package/dist/exchanges/lighter-public.d.ts +67 -12
  152. package/dist/exchanges/lighter-public.js +250 -82
  153. package/dist/exchanges/lighter-signer.d.ts +1 -1
  154. package/dist/exchanges/lighter-signer.js +1 -1
  155. package/dist/exchanges/lighter-spot.d.ts +10 -5
  156. package/dist/exchanges/lighter-spot.js +105 -97
  157. package/dist/exchanges/lighter.d.ts +80 -21
  158. package/dist/exchanges/lighter.js +312 -105
  159. package/dist/exchanges/ondo/adapter.d.ts +38 -13
  160. package/dist/exchanges/ondo/adapter.js +86 -27
  161. package/dist/exchanges/ondo/agent.d.ts +1 -1
  162. package/dist/exchanges/ondo/agent.js +1 -1
  163. package/dist/exchanges/ondo/client.d.ts +1 -1
  164. package/dist/exchanges/ondo/client.js +2 -2
  165. package/dist/exchanges/ondo/public.d.ts +35 -3
  166. package/dist/exchanges/ondo/public.js +75 -32
  167. package/dist/exchanges/ondo/spot.d.ts +2 -2
  168. package/dist/exchanges/ondo/spot.js +3 -3
  169. package/dist/exchanges/options-interface.d.ts +158 -0
  170. package/dist/exchanges/options-interface.js +17 -0
  171. package/dist/exchanges/options-venues.d.ts +12 -0
  172. package/dist/exchanges/options-venues.js +35 -0
  173. package/dist/exchanges/outcome-interface.d.ts +4 -0
  174. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  175. package/dist/exchanges/pacifica-atomic.js +440 -0
  176. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  177. package/dist/exchanges/pacifica-decimal.js +51 -0
  178. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  179. package/dist/exchanges/pacifica-presigned.js +141 -0
  180. package/dist/exchanges/pacifica-print.d.ts +200 -0
  181. package/dist/exchanges/pacifica-print.js +417 -0
  182. package/dist/exchanges/pacifica-spot.d.ts +8 -2
  183. package/dist/exchanges/pacifica-spot.js +26 -18
  184. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  185. package/dist/exchanges/pacifica-typed-data.js +1 -1
  186. package/dist/exchanges/pacifica-vaults.d.ts +130 -0
  187. package/dist/exchanges/pacifica-vaults.js +277 -0
  188. package/dist/exchanges/pacifica.d.ts +108 -10
  189. package/dist/exchanges/pacifica.js +428 -171
  190. package/dist/exchanges/precision.d.ts +3 -1
  191. package/dist/exchanges/precision.js +9 -2
  192. package/dist/exchanges/public-balances.d.ts +25 -0
  193. package/dist/exchanges/public-balances.js +158 -0
  194. package/dist/exchanges/public-positions.d.ts +43 -0
  195. package/dist/exchanges/public-positions.js +154 -0
  196. package/dist/exchanges/qfex/adapter.d.ts +69 -13
  197. package/dist/exchanges/qfex/adapter.js +102 -17
  198. package/dist/exchanges/qfex/agent.d.ts +1 -1
  199. package/dist/exchanges/qfex/agent.js +1 -1
  200. package/dist/exchanges/qfex/client.d.ts +1 -1
  201. package/dist/exchanges/qfex/client.js +1 -1
  202. package/dist/exchanges/qfex/errors.d.ts +1 -1
  203. package/dist/exchanges/qfex/errors.js +2 -2
  204. package/dist/exchanges/qfex/public.d.ts +65 -12
  205. package/dist/exchanges/qfex/public.js +131 -25
  206. package/dist/exchanges/qfex/signing.d.ts +2 -2
  207. package/dist/exchanges/qfex/signing.js +4 -5
  208. package/dist/exchanges/qfex/ws-transport.d.ts +3 -3
  209. package/dist/exchanges/qfex/ws-transport.js +3 -3
  210. package/dist/exchanges/rate-limiter.d.ts +1 -1
  211. package/dist/exchanges/rate-limiter.js +5 -2
  212. package/dist/exchanges/registry.d.ts +1 -1
  213. package/dist/exchanges/registry.js +1 -1
  214. package/dist/exchanges/risex/adapter.d.ts +160 -0
  215. package/dist/exchanges/risex/adapter.js +842 -0
  216. package/dist/exchanges/risex/agent.d.ts +154 -0
  217. package/dist/exchanges/risex/agent.js +384 -0
  218. package/dist/exchanges/risex/client.d.ts +47 -0
  219. package/dist/exchanges/risex/client.js +174 -0
  220. package/dist/exchanges/risex/nonce.d.ts +43 -0
  221. package/dist/exchanges/risex/nonce.js +212 -0
  222. package/dist/exchanges/risex/public.d.ts +64 -0
  223. package/dist/exchanges/risex/public.js +150 -0
  224. package/dist/exchanges/risex/session.d.ts +71 -0
  225. package/dist/exchanges/risex/session.js +171 -0
  226. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  227. package/dist/exchanges/risex/typed-data.js +240 -0
  228. package/dist/exchanges/risex/units.d.ts +29 -0
  229. package/dist/exchanges/risex/units.js +104 -0
  230. package/dist/exchanges/slippage.d.ts +1 -1
  231. package/dist/exchanges/slippage.js +1 -1
  232. package/dist/exchanges/spot-factory.d.ts +3 -3
  233. package/dist/exchanges/spot-factory.js +8 -8
  234. package/dist/exchanges/spot-interface.d.ts +5 -5
  235. package/dist/exchanges/spot-interface.js +2 -2
  236. package/dist/exchanges/spot-result.d.ts +3 -3
  237. package/dist/exchanges/spot-result.js +3 -3
  238. package/dist/exchanges/trading-v2.d.ts +3 -3
  239. package/dist/exchanges/trading-v2.js +9 -6
  240. package/dist/exchanges/txflow/adapter.d.ts +61 -0
  241. package/dist/exchanges/txflow/adapter.js +150 -0
  242. package/dist/exchanges/txflow/client.d.ts +47 -0
  243. package/dist/exchanges/txflow/client.js +155 -0
  244. package/dist/exchanges/txflow/public.d.ts +87 -0
  245. package/dist/exchanges/txflow/public.js +355 -0
  246. package/dist/exchanges/unknown-symbol.d.ts +34 -0
  247. package/dist/exchanges/unknown-symbol.js +56 -0
  248. package/dist/exchanges/variational/adapter.d.ts +67 -0
  249. package/dist/exchanges/variational/adapter.js +137 -0
  250. package/dist/exchanges/variational/client.d.ts +21 -0
  251. package/dist/exchanges/variational/client.js +66 -0
  252. package/dist/exchanges/variational/public.d.ts +126 -0
  253. package/dist/exchanges/variational/public.js +430 -0
  254. package/dist/exchanges/venue-types.d.ts +52 -10
  255. package/dist/exchanges/venue-types.js +82 -11
  256. package/dist/exchanges/venue.d.ts +150 -11
  257. package/dist/exchanges/venue.js +126 -4
  258. package/dist/exchanges/venues/arcus.d.ts +1 -2
  259. package/dist/exchanges/venues/arcus.js +5 -4
  260. package/dist/exchanges/venues/aster.d.ts +2 -2
  261. package/dist/exchanges/venues/aster.js +6 -4
  262. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  263. package/dist/exchanges/venues/create-helpers.js +3 -3
  264. package/dist/exchanges/venues/extended.d.ts +21 -0
  265. package/dist/exchanges/venues/extended.js +96 -0
  266. package/dist/exchanges/venues/grvt.d.ts +30 -0
  267. package/dist/exchanges/venues/grvt.js +98 -0
  268. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  269. package/dist/exchanges/venues/hyperliquid.js +19 -8
  270. package/dist/exchanges/venues/index.d.ts +7 -2
  271. package/dist/exchanges/venues/index.js +8 -2
  272. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  273. package/dist/exchanges/venues/lighter-family.js +9 -11
  274. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  275. package/dist/exchanges/venues/lighter-rh.js +20 -8
  276. package/dist/exchanges/venues/lighter.d.ts +1 -1
  277. package/dist/exchanges/venues/lighter.js +18 -6
  278. package/dist/exchanges/venues/ondo.d.ts +1 -1
  279. package/dist/exchanges/venues/ondo.js +5 -3
  280. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  281. package/dist/exchanges/venues/pacifica.js +15 -4
  282. package/dist/exchanges/venues/qfex.d.ts +1 -1
  283. package/dist/exchanges/venues/qfex.js +4 -2
  284. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  285. package/dist/exchanges/venues/risex-release.js +14 -0
  286. package/dist/exchanges/venues/risex.d.ts +31 -0
  287. package/dist/exchanges/venues/risex.js +131 -0
  288. package/dist/exchanges/venues/txflow.d.ts +18 -0
  289. package/dist/exchanges/venues/txflow.js +77 -0
  290. package/dist/exchanges/venues/variational.d.ts +21 -0
  291. package/dist/exchanges/venues/variational.js +85 -0
  292. package/dist/execution-log.d.ts +1 -1
  293. package/dist/funding/legacy-public.d.ts +6 -4
  294. package/dist/funding/legacy-public.js +42 -41
  295. package/dist/funding/normalize.d.ts +1 -1
  296. package/dist/funding/normalize.js +1 -1
  297. package/dist/funding/rates.d.ts +95 -6
  298. package/dist/funding/rates.js +99 -19
  299. package/dist/index.js +7 -6
  300. package/dist/jobs.d.ts +1 -0
  301. package/dist/jobs.js +25 -1
  302. package/dist/landing.d.ts +2 -0
  303. package/dist/landing.js +1 -1
  304. package/dist/liquidity.d.ts +3 -1
  305. package/dist/liquidity.js +8 -4
  306. package/dist/mcp-pacifica-earn.d.ts +9 -0
  307. package/dist/mcp-pacifica-earn.js +64 -0
  308. package/dist/mcp-server.js +211 -68
  309. package/dist/pacifica/client.d.ts +69 -5
  310. package/dist/pacifica/client.js +181 -8
  311. package/dist/pacifica/rate-limit.d.ts +144 -0
  312. package/dist/pacifica/rate-limit.js +267 -0
  313. package/dist/pacifica/types/order.d.ts +2 -1
  314. package/dist/plan-executor.js +7 -2
  315. package/dist/positions-by-address.d.ts +34 -0
  316. package/dist/positions-by-address.js +46 -0
  317. package/dist/rebalance.js +1 -1
  318. package/dist/risk.d.ts +2 -1
  319. package/dist/risk.js +2 -4
  320. package/dist/settings.d.ts +32 -3
  321. package/dist/signer/ows-evm.d.ts +1 -1
  322. package/dist/signer/ows-evm.js +1 -1
  323. package/dist/smart-order.d.ts +1 -1
  324. package/dist/smart-order.js +1 -1
  325. package/dist/strategies/funding-arb.d.ts +1 -1
  326. package/dist/strategies/funding-arb.js +36 -63
  327. package/dist/trade-validator.js +2 -2
  328. package/dist/utils.d.ts +22 -3
  329. package/dist/utils.js +11 -3
  330. package/dist/venue-health.d.ts +40 -0
  331. package/dist/venue-health.js +75 -0
  332. package/dist/withdraw-gate.d.ts +1 -1
  333. package/dist/withdraw-gate.js +1 -1
  334. package/dist/ws/arcus-market.d.ts +9 -0
  335. package/dist/ws/arcus-market.js +9 -0
  336. package/dist/ws/aster-market.d.ts +33 -0
  337. package/dist/ws/aster-market.js +30 -0
  338. package/dist/ws/index.d.ts +980 -0
  339. package/dist/ws/index.js +4460 -0
  340. package/dist/ws/lighter-market.d.ts +14 -0
  341. package/dist/ws/lighter-market.js +13 -0
  342. package/dist/ws/node.d.ts +996 -0
  343. package/dist/ws/node.js +4554 -0
  344. package/dist/ws/pacifica-instrument.d.ts +8 -0
  345. package/dist/ws/pacifica-instrument.js +8 -0
  346. package/dist/ws/unclassified.d.ts +16 -0
  347. package/dist/ws/unclassified.js +43 -0
  348. package/dist/ws-cli/credentials.d.ts +62 -0
  349. package/dist/ws-cli/credentials.js +126 -0
  350. package/package.json +71 -10
  351. package/skills/perp-cli/SKILL.md +153 -36
  352. package/skills/perp-cli/package.json +15 -2
  353. package/skills/perp-cli/references/agent-operations.md +22 -12
  354. package/skills/perp-cli/references/commands.md +143 -17
  355. package/skills/perp-cli/references/strategies.md +38 -3
  356. package/skills/perp-cli/references/ws-library.md +523 -0
  357. package/skills/perp-cli/scripts/arb-monitor.sh +13 -10
  358. package/skills/perp-cli/scripts/funding-analysis.sh +57 -29
  359. package/skills/perp-cli/scripts/preflight.sh +27 -19
  360. package/skills/perp-cli/scripts/spot-perp-scan.sh +58 -38
  361. package/skills/perp-cli/scripts/validate-arb.sh +56 -39
  362. package/dist/cli-spec.d.ts +0 -46
  363. package/dist/cli-spec.js +0 -81
@@ -1,73 +1,103 @@
1
1
  #!/bin/bash
2
2
  # Funding rate analysis across all exchanges
3
- # Usage: ./funding-analysis.sh [--json] [--symbol ETH] [--top 10]
4
- # Fetches current + historical funding data for comparison
3
+ # Usage: ./funding-analysis.sh [--json] [--symbol ETH] [--top 10] [--hours 24] [-n mainnet|testnet]
4
+ # --symbol: cross-venue compare + each verified venue's SETTLED funding history
5
+ # (`-e <venue> market funding <SYM> -l <hours>`, averaged per venue). `arb scan --history` is only
6
+ # the locally recorded snapshots (written by earlier `arb scan --rates` runs) and is often empty.
7
+ # Testnet funding / spreads are not meaningful — use mainnet (the default) for yield analysis.
5
8
 
6
9
  set -euo pipefail
7
10
 
8
- # Auto-detect perp command (supports npx fallback for agents without install permissions)
9
- if command -v perp &>/dev/null; then
10
- PERP="perp"
11
- else
12
- PERP="npx -y perp-cli@latest"
11
+ # perp must be installed (no npx / auto-install path: the user decides what gets installed).
12
+ if ! command -v perp &>/dev/null; then
13
+ echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
14
+ echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
15
+ exit 1
13
16
  fi
17
+ PERP="perp"
14
18
 
15
19
  # 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; }
20
+ # (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
21
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
18
22
  isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
19
23
 
20
24
  JSON_MODE=false
21
25
  SYMBOL=""
22
26
  TOP=10
27
+ HOURS=24
28
+ NETWORK="mainnet"
23
29
  while [[ $# -gt 0 ]]; do
24
30
  case "$1" in
25
31
  --json) JSON_MODE=true; shift ;;
26
32
  --symbol) SYMBOL="$2"; shift 2 ;;
27
33
  --top) TOP="$2"; shift 2 ;;
28
- *) shift ;;
34
+ --hours) HOURS="$2"; shift 2 ;;
35
+ -n|--network) NETWORK="$2"; shift 2 ;;
36
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
29
37
  esac
30
38
  done
39
+ PERP="perp -n $NETWORK"
40
+
41
+ # Row label per scan mode (perp-perp rows: longExch/shortExch; spot-perp rows: spotExchanges/perpExchange/direction)
42
+ ROW_JQ='if .mode == "spot-perp" then
43
+ " \(.symbol) | spot-perp | net \(.netSpread)%/yr | \(.direction) spot:\(.spotExchanges | join("/")) perp:\(.perpExchange)"
44
+ + (if .direction == "long-spot-short-perp" then "" else " [NOT executable with arb exec]" end)
45
+ else
46
+ " \(.symbol) | perp-perp | net \(.netSpread)%/yr | long \(.longExch) / short \(.shortExch)"
47
+ end'
31
48
 
32
49
  if [[ -n "$SYMBOL" ]]; then
33
50
  # Single symbol detailed analysis
34
- FUNDING=$(pj --json arb scan --history "$SYMBOL")
35
51
  COMPARE=$(pj --json arb scan --compare "$SYMBOL")
52
+ LOCAL_HISTORY=$(pj --json arb scan --history "$SYMBOL" --hours "$HOURS")
36
53
  SCAN=$(pj --json arb scan --mode perp-perp --min 0)
37
54
  SPOT_SCAN=$(pj --json arb scan --mode spot-perp --min 0)
38
55
 
56
+ # Settled funding history per verified venue of the compare (rows newest first; rate = fraction per intervalHours)
57
+ SETTLED="[]"
58
+ if isok "$COMPARE" && [[ "$(jq -r '.data.available' <<<"$COMPARE")" != "false" ]]; then
59
+ for VENUE in $(jq -r '.data.rates[] | select(.fundingVerified) | .exchange' <<<"$COMPARE"); do
60
+ H=$(pj --json -e "$VENUE" market funding "$SYMBOL" -l "$HOURS")
61
+ ROW=$(jq -c --arg v "$VENUE" '
62
+ if .ok then
63
+ { exchange: $v, rows: (.data | length),
64
+ avgHourlyRate: (if (.data | length) > 0 and all(.data[]; .rate != null and .intervalHours != null)
65
+ then ([.data[] | ((.rate | tonumber) / .intervalHours)] | add / length) else null end) }
66
+ | .avgAnnualPct = (if .avgHourlyRate == null then null else .avgHourlyRate * 8760 * 100 end)
67
+ else { exchange: $v, error: .error.code, message: .error.message } end' <<<"$H")
68
+ SETTLED=$(jq -c --argjson r "$ROW" '. + [$r]' <<<"$SETTLED")
69
+ done
70
+ fi
71
+
39
72
  if $JSON_MODE; then
40
73
  cat <<EOF
41
74
  {
42
75
  "ok": true,
76
+ "network": "$NETWORK",
43
77
  "symbol": "$SYMBOL",
78
+ "hours": $HOURS,
44
79
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
45
- "fundingHistory": $FUNDING,
46
80
  "compare": $COMPARE,
81
+ "settledHistory": $SETTLED,
82
+ "localSnapshotHistory": $LOCAL_HISTORY,
47
83
  "perpPerpArb": $SCAN,
48
84
  "spotPerpArb": $SPOT_SCAN
49
85
  }
50
86
  EOF
51
87
  else
52
- echo "=== Funding Analysis: $SYMBOL ==="
88
+ echo "=== Funding Analysis: $SYMBOL ($NETWORK) ==="
53
89
  echo ""
54
90
  echo "--- Cross-Exchange Compare ---"
55
- echo "$COMPARE" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
91
+ if isok "$COMPARE"; then jq -r '.data' <<<"$COMPARE"; else echo " ERROR: $(jq -r '.error.code' <<<"$COMPARE")"; fi
56
92
  echo ""
57
- echo "--- Funding History ---"
58
- echo "$FUNDING" | jq -r '.data // empty' 2>/dev/null || echo "(unavailable)"
93
+ echo "--- Settled funding, last $HOURS rows per venue (avg annualized %) ---"
94
+ jq -r '.[] | if .error then " \(.exchange): ERROR \(.error)" else " \(.exchange): \(.rows) rows, avg \(.avgAnnualPct)%/yr" end' <<<"$SETTLED"
59
95
  echo ""
60
96
  echo "--- Perp-Perp Arb ---"
61
- echo "$SCAN" | jq -r '
62
- .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
63
- " \(.longExch // .longExchange)→\(.shortExch // .shortExchange): net \(.netSpread)% annual"
64
- ' 2>/dev/null || echo " (no opportunity)"
97
+ if isok "$SCAN"; then jq -r --arg s "$SYMBOL" ".data[] | select(.symbol == \$s) | $ROW_JQ" <<<"$SCAN"; else echo " ERROR: $(jq -r '.error.code' <<<"$SCAN")"; fi
65
98
  echo ""
66
99
  echo "--- Spot+Perp Arb ---"
67
- echo "$SPOT_SCAN" | jq -r '
68
- .data // [] | map(select(.symbol == "'"$SYMBOL"'")) | .[] |
69
- " \(.direction) spot:\((.spotExchanges // []) | join("/"))→\(.perpExchange): net \(.netSpreadPct)% annual"
70
- ' 2>/dev/null || echo " (no opportunity)"
100
+ if isok "$SPOT_SCAN"; then jq -r --arg s "$SYMBOL" ".data[] | select(.symbol == \$s) | $ROW_JQ" <<<"$SPOT_SCAN"; else echo " ERROR: $(jq -r '.error.code' <<<"$SPOT_SCAN")"; fi
71
101
  fi
72
102
  else
73
103
  # All symbols scan
@@ -78,6 +108,7 @@ else
78
108
  cat <<EOF
79
109
  {
80
110
  "ok": true,
111
+ "network": "$NETWORK",
81
112
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
82
113
  "topN": $TOP,
83
114
  "allOpportunities": $SCAN,
@@ -85,11 +116,8 @@ else
85
116
  }
86
117
  EOF
87
118
  else
88
- echo "=== Funding Rate Overview (top $TOP) ==="
119
+ echo "=== Funding Rate Overview (top $TOP, $NETWORK) ==="
89
120
  echo ""
90
- echo "$SCAN" | jq -r "
91
- .data // [] | .[0:$TOP] | .[] |
92
- \" \(.symbol) | \(.mode // \"perp\") | net \(.netSpread // .netSpreadPct)%/yr | \(.longExch // (\"spot:\" + ((.spotExchanges // []) | join(\"/\"))))→\(.shortExch // .perpExchange)\"
93
- " 2>/dev/null || echo "(no data)"
121
+ if isok "$SCAN"; then jq -r ".data[0:$TOP][] | $ROW_JQ" <<<"$SCAN"; else echo " ERROR: $(jq -r '.error.code' <<<"$SCAN")"; fi
94
122
  fi
95
123
  fi
@@ -6,16 +6,17 @@
6
6
 
7
7
  set -euo pipefail
8
8
 
9
- # Auto-detect perp command (supports npx fallback for agents without install permissions)
10
- if command -v perp &>/dev/null; then
11
- PERP="perp"
12
- else
13
- PERP="npx -y perp-cli@latest"
9
+ # perp must be installed (no npx / auto-install path: the user decides what gets installed).
10
+ if ! command -v perp &>/dev/null; then
11
+ echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
12
+ echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
13
+ exit 1
14
14
  fi
15
+ PERP="perp"
15
16
 
16
17
  # 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; }
18
+ # (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
19
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
19
20
  isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
20
21
 
21
22
  JSON_MODE=false
@@ -30,28 +31,35 @@ add_check() {
30
31
  if [[ "$status" == "fail" ]]; then issues+=("$name: $detail"); fi
31
32
  }
32
33
 
33
- # 1. perp-cli installed and version check
34
- VERSION=$($PERP --version 2>/dev/null || echo "unknown")
35
- if [[ "$VERSION" != "unknown" ]]; then
36
- add_check "install" "pass" "perp-cli $VERSION ($(command -v perp &>/dev/null && echo 'global' || echo 'npx'))"
34
+ # 1. perp-cli version (must be >= 0.16.0)
35
+ VERSION=$($PERP --version 2>/dev/null || true)
36
+ MIN_VERSION="0.16.0"
37
+ if [[ -z "$VERSION" ]]; then
38
+ add_check "install" "fail" "perp --version printed nothing — ask the user to (re)install perp-cli >= $MIN_VERSION"
39
+ elif [[ "$(printf '%s\n%s\n' "$MIN_VERSION" "$VERSION" | sort -V | head -1)" != "$MIN_VERSION" ]]; then
40
+ add_check "install" "fail" "perp-cli $VERSION < $MIN_VERSION — ask the user to upgrade"
37
41
  else
38
- add_check "install" "fail" "perp-cli not found — ask the user before installing (npm i -g perp-cli@latest) or use npx"
42
+ add_check "install" "pass" "perp-cli $VERSION"
39
43
  fi
40
44
 
41
- # 2. Wallet configured
45
+ # 2. Wallet configured (with an OWS wallet, `exchanges: []` in wallet show is normal)
42
46
  WALLET_OUT=$(pj --json wallet show)
43
47
  if isok "$WALLET_OUT"; then
44
48
  add_check "wallet" "pass" "Wallet configured"
45
49
  else
46
- add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> (key via hidden prompt or stdin)"
50
+ add_check "wallet" "fail" "wallet show failed ($(jq -r '.error.code' <<<"$WALLET_OUT")) — the user registers a key: perp wallet set <exchange> (stdin / hidden prompt)"
47
51
  fi
48
52
 
49
- # 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
50
- HEALTH_OUT=$(pj --json market prices)
51
- if isok "$HEALTH_OUT"; then
52
- add_check "connectivity" "pass" "Exchange reachable"
53
+ # 3. Venue connectivity: perp health (healthy = every OFFICIAL-API venue is up;
54
+ # allHealthy also counts unofficial APIs such as TxFlow)
55
+ HEALTH_OUT=$(pj --json health)
56
+ if ! isok "$HEALTH_OUT"; then
57
+ add_check "connectivity" "fail" "perp health failed ($(jq -r '.error.code' <<<"$HEALTH_OUT"))"
58
+ elif [[ "$(jq -r '.data.healthy' <<<"$HEALTH_OUT")" == "true" ]]; then
59
+ add_check "connectivity" "pass" "All official venues reachable"
53
60
  else
54
- add_check "connectivity" "warn" "Exchange unreachable"
61
+ DOWN=$(jq -r '[.data.exchanges[] | select(.status != "ok") | .exchange] | join(",")' <<<"$HEALTH_OUT")
62
+ add_check "connectivity" "warn" "Official venues down: $DOWN — do not use them until perp health shows them ok"
55
63
  fi
56
64
 
57
65
  # 4. Portfolio & balances
@@ -1,56 +1,70 @@
1
1
  #!/bin/bash
2
- # Scan spot+perp funding rate arbitrage opportunities
3
- # Usage: ./spot-perp-scan.sh [--json] [--mode all|spot-perp|perp-perp] [--min 10]
4
- # Combines perp-perp and spot-perp scans with validation
2
+ # Scan spot+perp (and perp-perp) funding rate arbitrage opportunities
3
+ # Usage: ./spot-perp-scan.sh [--json] [--mode all|spot-perp|perp-perp] [--min 10] [-n mainnet|testnet] [--no-portfolio]
4
+ # Combines the scan with balances (portfolio) and venue health (perp health).
5
+ # --no-portfolio: skip the balance read (no wallet needed); the output says it was skipped.
6
+ # Spot-perp rows: only direction == "long-spot-short-perp" is executable with `arb exec <SYM> spot:<venue> <perp>`.
7
+ # Testnet funding / spreads are not meaningful — use mainnet (the default) for yield analysis.
5
8
 
6
9
  set -euo pipefail
7
10
 
8
- # Auto-detect perp command (supports npx fallback for agents without install permissions)
9
- if command -v perp &>/dev/null; then
10
- PERP="perp"
11
- else
12
- PERP="npx -y perp-cli@latest"
11
+ # perp must be installed (no npx / auto-install path: the user decides what gets installed).
12
+ if ! command -v perp &>/dev/null; then
13
+ echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
14
+ echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
15
+ exit 1
13
16
  fi
17
+ PERP="perp"
14
18
 
15
19
  # 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; }
20
+ # (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
21
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
18
22
  isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
19
23
 
20
24
  JSON_MODE=false
21
25
  MODE="all"
22
26
  MIN_SPREAD=10
27
+ NETWORK="mainnet"
28
+ WITH_PORTFOLIO=true
23
29
  while [[ $# -gt 0 ]]; do
24
30
  case "$1" in
25
31
  --json) JSON_MODE=true; shift ;;
26
32
  --mode) MODE="$2"; shift 2 ;;
27
33
  --min) MIN_SPREAD="$2"; shift 2 ;;
28
- *) shift ;;
34
+ -n|--network) NETWORK="$2"; shift 2 ;;
35
+ --no-portfolio) WITH_PORTFOLIO=false; shift ;;
36
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
29
37
  esac
30
38
  done
39
+ PERP="perp -n $NETWORK"
31
40
 
32
- # 1. Portfolio check (ensure we have balances)
33
- PORTFOLIO=$(pj --json portfolio)
34
- if ! isok "$PORTFOLIO"; then
35
- if $JSON_MODE; then
36
- echo '{"ok":false,"error":"Cannot fetch portfolio — check wallet setup"}'
37
- else
38
- echo "ERROR: Cannot fetch portfolio. Check wallet setup first."
41
+ # 1. Portfolio (balances). Explicitly skipped with --no-portfolio; a failed read aborts.
42
+ if $WITH_PORTFOLIO; then
43
+ PORTFOLIO=$(pj --json portfolio)
44
+ if ! isok "$PORTFOLIO"; then
45
+ if $JSON_MODE; then
46
+ jq -c '{ok:false, error:{code:.error.code, message:.error.message, source:"perp --json portfolio", remediation:"Check the wallet setup, or rerun with --no-portfolio to scan without balances"}}' <<<"$PORTFOLIO"
47
+ else
48
+ echo "ERROR: portfolio failed ($(jq -r '.error.code' <<<"$PORTFOLIO")). Check the wallet setup, or rerun with --no-portfolio." >&2
49
+ fi
50
+ exit 1
39
51
  fi
40
- exit 1
52
+ else
53
+ PORTFOLIO='{"skipped":true,"reason":"--no-portfolio"}'
41
54
  fi
42
55
 
43
56
  # 2. Run appropriate scan
44
57
  SCAN_RESULT=$(pj --json arb scan --mode "$MODE" --min "$MIN_SPREAD")
45
58
 
46
- # 3. Get exchange health for context (proxy via market prices fetch)
47
- HEALTH=$(pj --json market prices)
59
+ # 3. Venue health (data.healthy = every official-API venue is up)
60
+ HEALTH=$(pj --json health)
48
61
 
49
62
  if $JSON_MODE; then
50
63
  cat <<EOF
51
64
  {
52
65
  "ok": true,
53
66
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
67
+ "network": "$NETWORK",
54
68
  "mode": "$MODE",
55
69
  "minSpread": $MIN_SPREAD,
56
70
  "portfolio": $PORTFOLIO,
@@ -60,29 +74,35 @@ if $JSON_MODE; then
60
74
  EOF
61
75
  else
62
76
  echo "=== Spot+Perp Arb Scanner ==="
63
- echo "Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
77
+ echo "Network: $NETWORK | Mode: $MODE | Min spread: ${MIN_SPREAD}% annual"
64
78
  echo ""
65
79
 
66
- # Show balances
67
80
  echo "--- Balances ---"
68
- echo "$PORTFOLIO" | jq -r '
69
- .data.exchanges // [] | .[] |
70
- " \(.name): equity $\(.perp.equity // "N/A")"
71
- ' 2>/dev/null || echo " (unable to parse)"
81
+ if $WITH_PORTFOLIO; then
82
+ jq -r '.data.exchanges[] | " \(.name): equity $\(.perp.equity) available $\(.perp.available)"' <<<"$PORTFOLIO"
83
+ else
84
+ echo " (skipped: --no-portfolio)"
85
+ fi
72
86
  echo ""
73
87
 
74
- # Show opportunities
75
88
  echo "--- Opportunities ---"
76
- echo "$SCAN_RESULT" | jq -r '
77
- .data // [] | .[] |
78
- " \(.symbol) | \(.mode // "perp-perp") | net: \(.netSpread // .netSpreadPct)%/yr | \(.longExch // ("spot:" + ((.spotExchanges // []) | join("/"))))→\(.shortExch // .perpExchange)"
79
- ' 2>/dev/null || echo " (no opportunities or parse error)"
89
+ if isok "$SCAN_RESULT"; then
90
+ jq -r '.data[] |
91
+ if .mode == "spot-perp" then
92
+ " \(.symbol) | spot-perp | net \(.netSpread)%/yr | \(.direction) spot:\(.spotExchanges | join("/")) perp:\(.perpExchange)"
93
+ + (if .direction == "long-spot-short-perp" then "" else " [NOT executable with arb exec]" end)
94
+ else
95
+ " \(.symbol) | perp-perp | net \(.netSpread)%/yr | long \(.longExch) / short \(.shortExch)"
96
+ end' <<<"$SCAN_RESULT"
97
+ else
98
+ echo " ERROR: arb scan failed: $(jq -r '"\(.error.code): \(.error.message)"' <<<"$SCAN_RESULT")"
99
+ fi
80
100
 
81
- # Show exchange health
82
101
  echo ""
83
- echo "--- Exchange Health ---"
84
- echo "$HEALTH" | jq -r '
85
- .data // {} | to_entries[] |
86
- " \(.key): \(.value.status // .value // "unknown")"
87
- ' 2>/dev/null || echo " (unable to check)"
102
+ echo "--- Venue Health ---"
103
+ if isok "$HEALTH"; then
104
+ jq -r '" healthy (official venues): \(.data.healthy)", (.data.exchanges[] | " \(.exchange): \(.status)")' <<<"$HEALTH"
105
+ else
106
+ echo " ERROR: perp health failed: $(jq -r '.error.code' <<<"$HEALTH")"
107
+ fi
88
108
  fi
@@ -3,20 +3,22 @@
3
3
  # Usage: ./validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]
4
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
+ # NOTE: For spot+perp arb, pass spot:<exch> as LONG_EX (e.g. spot:hl) — only for a scan row with
7
+ # direction == "long-spot-short-perp" (arb exec spot: always buys spot + shorts the perp).
7
8
 
8
9
  set -euo pipefail
9
10
 
10
- # Auto-detect perp command (supports npx fallback for agents without install permissions)
11
- if command -v perp &>/dev/null; then
12
- PERP="perp"
13
- else
14
- PERP="npx -y perp-cli@latest"
11
+ # perp must be installed (no npx / auto-install path: the user decides what gets installed).
12
+ if ! command -v perp &>/dev/null; then
13
+ echo "perp not found — ask the user to install perp-cli >= 0.16.0" >&2
14
+ echo '{"ok":false,"error":{"code":"PERP_NOT_FOUND","message":"perp not found — ask the user to install perp-cli >= 0.16.0"}}'
15
+ exit 1
15
16
  fi
17
+ PERP="perp"
16
18
 
17
19
  # 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
+ # (never append a fallback document); empty output (crash) → an explicit NO_OUTPUT error document.
21
+ pj() { local out; out=$($PERP "$@" 2>/dev/null) || true; if [[ -n "$out" ]]; then printf '%s\n' "$out"; else echo '{"ok":false,"error":{"code":"NO_OUTPUT","message":"perp printed nothing (crashed?)"}}'; fi; }
20
22
  isok() { jq -e '.ok == true' >/dev/null 2>&1 <<<"$1"; }
21
23
 
22
24
  SYM="${1:?Usage: validate-arb.sh <SYMBOL> <LONG_EX> <SHORT_EX> <SIZE_USD> [--leverage N]}"
@@ -42,24 +44,23 @@ add_result() {
42
44
  if [[ "$ok" == "false" ]]; then pass=false; fi
43
45
  }
44
46
 
45
- # Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below
47
+ # Venue of the long leg (spot:<venue> → <venue>) for the per-venue reads below.
48
+ # Aliases (hl, pac, lt, lrh, ast, …) are accepted everywhere, portfolio included.
46
49
  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
53
- }
54
-
55
- # 1. Price comparison — both exchanges must report similar prices
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)
58
-
59
- if [[ "$LONG_PRICE" == "0" || "$SHORT_PRICE" == "0" ]]; then
60
- add_result "price" "false" "Cannot fetch price: long=$LONG_PRICE short=$SHORT_PRICE"
50
+ SPOT_MODE=false
51
+ [[ "$LONG_EX" == spot:* ]] && SPOT_MODE=true
52
+
53
+ # 1. Price comparison — both exchanges must report similar prices.
54
+ # `market mid` reads the PERP book; there is no spot price read — in spot mode the long price
55
+ # is the spot venue's perp mid (arb exec itself compares the spot and perp books and refuses
56
+ # an empty book or a > 5% gap).
57
+ LONG_MID=$(pj --json -e "$LONG_VENUE" market mid "$SYM")
58
+ SHORT_MID=$(pj --json -e "$SHORT_EX" market mid "$SYM")
59
+ if ! isok "$LONG_MID" || ! isok "$SHORT_MID"; then
60
+ add_result "price" "false" "Cannot fetch mid: long=$(jq -r '.error.code // .data.mid' <<<"$LONG_MID") short=$(jq -r '.error.code // .data.mid' <<<"$SHORT_MID")"
61
61
  else
62
- # Check deviation
62
+ LONG_PRICE=$(jq -r '.data.mid' <<<"$LONG_MID")
63
+ SHORT_PRICE=$(jq -r '.data.mid' <<<"$SHORT_MID")
63
64
  DEVIATION=$(echo "$LONG_PRICE $SHORT_PRICE" | awk '{d=($1-$2)/$2*100; print (d<0?-d:d)}')
64
65
  if (( $(echo "$DEVIATION > 5" | bc -l) )); then
65
66
  add_result "price" "false" "Price deviation ${DEVIATION}% > 5% — possible wrong token"
@@ -68,21 +69,37 @@ else
68
69
  fi
69
70
  fi
70
71
 
71
- # 2. Balance check on both exchanges
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)
74
-
75
- NEEDED=$(echo "$SIZE" | awk '{print $1}')
76
- if (( $(echo "$LONG_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
77
- add_result "balance_long" "false" "$LONG_EX balance $LONG_BAL < needed $NEEDED"
78
- else
79
- add_result "balance_long" "true" "$LONG_EX balance=$LONG_BAL"
80
- fi
81
- if (( $(echo "$SHORT_BAL < $NEEDED" | bc -l 2>/dev/null || echo 1) )); then
82
- add_result "balance_short" "false" "$SHORT_EX balance $SHORT_BAL < needed $NEEDED"
72
+ # 2. Balances. Perp legs: portfolio .perp.available only (missing / unreadable = fail, never 0 or equity).
73
+ # The comparison against the full SIZE is conservative (arb exec needs notional / leverage × buffer).
74
+ perp_available() {
75
+ local out; out=$(pj --json portfolio -e "$1")
76
+ if ! isok "$out"; then echo "ERROR:$(jq -r '.error.code' <<<"$out")"; return; fi
77
+ jq -r '.data.exchanges[0].perp.available // "MISSING"' <<<"$out"
78
+ }
79
+ check_perp_balance() {
80
+ local name="$1" venue="$2" bal; bal=$(perp_available "$venue")
81
+ if [[ "$bal" == ERROR:* || "$bal" == "MISSING" ]]; then
82
+ add_result "$name" "false" "$venue perp available unreadable ($bal)"
83
+ elif (( $(echo "$bal < $SIZE" | bc -l) )); then
84
+ add_result "$name" "false" "$venue perp available $bal < $SIZE"
85
+ else
86
+ add_result "$name" "true" "$venue perp available=$bal"
87
+ fi
88
+ }
89
+ if $SPOT_MODE; then
90
+ # Spot leg: needs spot quote balance (or perp funds the CLI transfers perp→spot on that venue).
91
+ # The exact rule (shared vs separate spot margin, transfer shortfall) is enforced by the arb exec
92
+ # dry-run below; here the spot balances are read so a read failure is surfaced.
93
+ SPOT_BAL=$(pj --json -e "$LONG_VENUE" trade spot-balance)
94
+ if isok "$SPOT_BAL"; then
95
+ add_result "balance_spot" "true" "$LONG_VENUE spot balances: $(jq -c '[.data.balances[] | {token, available}]' <<<"$SPOT_BAL" | tr -d '"') — sufficiency checked by the dry-run"
96
+ else
97
+ add_result "balance_spot" "false" "$LONG_VENUE spot balance unreadable ($(jq -r '.error.code' <<<"$SPOT_BAL"))"
98
+ fi
83
99
  else
84
- add_result "balance_short" "true" "$SHORT_EX balance=$SHORT_BAL"
100
+ check_perp_balance "balance_long" "$LONG_VENUE"
85
101
  fi
102
+ check_perp_balance "balance_short" "$SHORT_EX"
86
103
 
87
104
  # 3. Market info (symbol availability)
88
105
  LONG_INFO=$(pj --json -e "$LONG_VENUE" market info "$SYM")
@@ -101,7 +118,7 @@ RISK_CHECK=$(pj --json risk check --notional "$SIZE" --leverage "${LEVERAGE:-1}"
101
118
  if isok "$RISK_CHECK" && [[ "$(jq -r '.data.allowed' <<<"$RISK_CHECK" 2>/dev/null || true)" == "true" ]]; then
102
119
  add_result "risk" "true" "Within risk limits"
103
120
  else
104
- RISK_WHY=$(jq -r '.data.reason // .error.code // "unknown"' <<<"$RISK_CHECK" 2>/dev/null || echo unknown)
121
+ if isok "$RISK_CHECK"; then RISK_WHY=$(jq -r '.data.reason' <<<"$RISK_CHECK"); else RISK_WHY=$(jq -r '.error.code' <<<"$RISK_CHECK"); fi
105
122
  add_result "risk" "false" "Risk check failed: $RISK_WHY"
106
123
  fi
107
124
 
@@ -110,7 +127,7 @@ DRY_RUN=$(pj --json --dry-run arb exec "$SYM" "$LONG_EX" "$SHORT_EX" "$SIZE" $LE
110
127
  if isok "$DRY_RUN"; then
111
128
  add_result "dry_run" "true" "Dry-run succeeded"
112
129
  else
113
- DRY_ERR=$(jq -r '.error.code // "unknown"' <<<"$DRY_RUN" 2>/dev/null || echo unknown)
130
+ DRY_ERR=$(jq -r '"\(.error.code): \(.error.message)"' <<<"$DRY_RUN" | tr -d '"\\')
114
131
  add_result "dry_run" "false" "Dry-run failed: $DRY_ERR"
115
132
  fi
116
133
 
@@ -1,46 +0,0 @@
1
- import type { Command } from "commander";
2
- interface ArgSpec {
3
- name: string;
4
- required: boolean;
5
- }
6
- interface OptionSpec {
7
- flags: string;
8
- description: string;
9
- default?: unknown;
10
- }
11
- interface CommandSpec {
12
- name: string;
13
- description: string;
14
- args: ArgSpec[];
15
- options: OptionSpec[];
16
- subcommands?: CommandSpec[];
17
- }
18
- interface CliSpec {
19
- name: string;
20
- version: string;
21
- description: string;
22
- globalOptions: OptionSpec[];
23
- commands: CommandSpec[];
24
- exchanges: string[];
25
- envelope: {
26
- success: string;
27
- error: string;
28
- example: {
29
- ok: boolean;
30
- data: {
31
- equity: string;
32
- };
33
- meta: {
34
- timestamp: string;
35
- };
36
- };
37
- };
38
- errorCodes: Record<string, {
39
- status: number;
40
- retryable: boolean;
41
- description: string;
42
- }>;
43
- tips: string[];
44
- }
45
- export declare function getCliSpec(program: Command): CliSpec;
46
- export {};