perp-cli 0.12.15 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +278 -31
  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 +74 -84
  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 +157 -682
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +14 -0
  287. package/dist/landing.js +36 -0
  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 +338 -134
  348. package/skills/perp-cli/references/agent-operations.md +181 -92
  349. package/skills/perp-cli/references/commands.md +475 -132
  350. package/skills/perp-cli/references/strategies.md +44 -38
  351. package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
  352. package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
  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 +33 -16
  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
@@ -1,11 +1,24 @@
1
+ import { parsePositiveDecimal } from "../errors.js";
2
+ import { isDryRun, networkOf, supportsDryRun } from "../cli-flags.js";
3
+ import { setQuarantineNetwork } from "../bot/quarantine.js";
4
+ import { assertStrategySimulatable } from "../bot/engine.js";
1
5
  import chalk from "chalk";
2
6
  import { printJson, jsonOk, jsonError } from "../utils.js";
3
- import { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, runBot, PRESETS, getPreset, getPresetsByStrategy } from "../bot/index.js";
7
+ import { loadBotConfig, parseStrategy, quickGridConfig, quickDCAConfig, runBot, PRESETS, getPreset, getPresetsByStrategy, withPresetSize } from "../bot/index.js";
8
+ import { resolveVenueId } from "../exchanges/venue.js";
9
+ import { PerpError } from "../errors.js";
4
10
  import { updateJobState } from "../jobs.js";
5
11
  import { runTWAP, runFundingArb, runGrid, runDCA, runTrailingStop } from "../strategies/index.js";
6
12
  import { registerStrategyPlanCommands } from "./plan.js";
7
13
  export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJson) {
8
14
  const bot = program.command("strategy").description("Trading strategies — long-running bot algorithms + one-shot scripted plans");
15
+ // Quarantine checks / entries of every strategy command (bots, plans, DCA / TWAP) are scoped to its -n network.
16
+ bot.hook("preAction", (_cmd, action) => setQuarantineNetwork(networkOf(action)));
17
+ /** The root `-e` selection: resolved id + whether it was given on the command line (not the default). */
18
+ const cliVenue = () => ({
19
+ id: resolveVenueId(String(program.opts().exchange)),
20
+ explicit: program.getOptionValueSource("exchange") === "cli",
21
+ });
9
22
  // ── bot start <config> ──
10
23
  bot
11
24
  .command("start <config>")
@@ -14,26 +27,26 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
14
27
  .option("--headless", "Run without TUI dashboard")
15
28
  .option("--job-id <id>", "Job ID (set by background runner)")
16
29
  .action(async (configPath, opts) => {
17
- const config = loadBotConfig(configPath);
30
+ // config.exchange (resolved) is the venue for EVERY strategy type; a contradicting explicit -e is refused.
31
+ const config = loadBotConfig(configPath, cliVenue());
18
32
  if (opts.background) {
19
- const { startJob } = await import("../jobs.js");
33
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
20
34
  const job = startJob({
21
35
  strategy: `bot:${config.strategy.type}`,
22
36
  exchange: config.exchange,
23
37
  params: { config: configPath, name: config.name },
24
- cliArgs: [`-e`, config.exchange, `start`, configPath],
38
+ venue: config.exchange, network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
39
+ cliArgs: [`strategy`, `start`, configPath],
25
40
  });
26
41
  if (isJson())
27
42
  return printJson(jsonOk(job));
28
43
  printBotJobStarted(config.name, job.id);
29
44
  return;
30
45
  }
31
- const adapter = config.strategy.type === "funding-arb"
32
- ? await getAdapterFor(config.exchange)
33
- : await getAdapter();
46
+ const adapter = await getAdapterFor(config.exchange);
34
47
  const mode = resolveOutputMode(isJson, opts.headless);
35
48
  const log = makeLog();
36
- await runBot(adapter, config, opts.jobId, log, undefined, mode);
49
+ await runBot(adapter, config, opts.jobId, log, undefined, mode, false);
37
50
  });
38
51
  // ── bot quick grid ──
39
52
  bot
@@ -63,9 +76,9 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
63
76
  leverage: opts.leverage ? parseInt(opts.leverage) : undefined,
64
77
  });
65
78
  if (opts.background) {
66
- const { startJob } = await import("../jobs.js");
79
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
67
80
  const cliArgs = [
68
- `-e`, adapter.name, `quick-grid`, symbol.toUpperCase(),
81
+ `strategy`, `quick-grid`, symbol.toUpperCase(),
69
82
  `--range`, opts.range, `--grids`, opts.grids, `--size`, opts.size,
70
83
  `--side`, opts.side, `--max-drawdown`, opts.maxDrawdown,
71
84
  `--max-runtime`, opts.maxRuntime,
@@ -74,6 +87,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
74
87
  const job = startJob({
75
88
  strategy: `bot:grid`,
76
89
  exchange: adapter.name,
90
+ venue: adapter.name, network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
77
91
  params: { symbol: symbol.toUpperCase(), ...opts },
78
92
  cliArgs,
79
93
  });
@@ -84,7 +98,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
84
98
  }
85
99
  const mode = resolveOutputMode(isJson, opts.headless);
86
100
  const log = makeLog();
87
- await runBot(adapter, config, opts.jobId, log, undefined, mode);
101
+ await runBot(adapter, config, opts.jobId, log, undefined, mode, false);
88
102
  });
89
103
  // ── bot quick dca ──
90
104
  bot
@@ -103,7 +117,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
103
117
  exchange: adapter.name,
104
118
  symbol: symbol.toUpperCase(),
105
119
  side: side.toLowerCase(),
106
- amount: parseFloat(amount),
120
+ amount: Number(parsePositiveDecimal(amount, "amount")),
107
121
  intervalSec: parseInt(interval),
108
122
  orders: parseInt(opts.orders),
109
123
  triggerDrop: opts.triggerDrop ? parseFloat(opts.triggerDrop) : undefined,
@@ -111,9 +125,9 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
111
125
  maxDrawdown: parseFloat(opts.maxDrawdown),
112
126
  });
113
127
  if (opts.background) {
114
- const { startJob } = await import("../jobs.js");
128
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
115
129
  const cliArgs = [
116
- `-e`, adapter.name, `quick-dca`, symbol.toUpperCase(), side.toLowerCase(),
130
+ `strategy`, `quick-dca`, symbol.toUpperCase(), side.toLowerCase(),
117
131
  amount, interval, `--orders`, opts.orders, `--max-drawdown`, opts.maxDrawdown,
118
132
  ...(opts.priceLimit ? [`--price-limit`, opts.priceLimit] : []),
119
133
  ...(opts.triggerDrop ? [`--trigger-drop`, opts.triggerDrop] : []),
@@ -121,6 +135,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
121
135
  const job = startJob({
122
136
  strategy: `bot:dca`,
123
137
  exchange: adapter.name,
138
+ venue: adapter.name, network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
124
139
  params: { symbol: symbol.toUpperCase(), ...opts },
125
140
  cliArgs,
126
141
  });
@@ -131,7 +146,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
131
146
  }
132
147
  const mode = resolveOutputMode(isJson, opts.headless);
133
148
  const log = makeLog();
134
- await runBot(adapter, config, opts.jobId, log, undefined, mode);
149
+ await runBot(adapter, config, opts.jobId, log, undefined, mode, false);
135
150
  });
136
151
  // ── bot quick arb ──
137
152
  bot
@@ -187,9 +202,9 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
187
202
  monitor_interval_sec: parseInt(opts.interval),
188
203
  };
189
204
  if (opts.background) {
190
- const { startJob } = await import("../jobs.js");
205
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
191
206
  const cliArgs = [
192
- `quick-arb`,
207
+ `strategy`, `quick-arb`,
193
208
  `--min-spread`, opts.minSpread, `--close-spread`, opts.closeSpread,
194
209
  `--size`, opts.size, `--max-positions`, opts.maxPositions,
195
210
  `--exchanges`, opts.exchanges, `--interval`, opts.interval,
@@ -198,6 +213,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
198
213
  const job = startJob({
199
214
  strategy: `bot:funding-arb`,
200
215
  exchange: "multi",
216
+ network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
201
217
  params: { ...opts },
202
218
  cliArgs,
203
219
  });
@@ -208,7 +224,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
208
224
  }
209
225
  const mode = resolveOutputMode(isJson, opts.headless);
210
226
  const log = makeLog();
211
- await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode);
227
+ await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode, false);
212
228
  });
213
229
  // ── bot delta-neutral ──
214
230
  bot
@@ -266,9 +282,9 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
266
282
  monitor_interval_sec: parseInt(opts.interval),
267
283
  };
268
284
  if (opts.background) {
269
- const { startJob } = await import("../jobs.js");
285
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
270
286
  const cliArgs = [
271
- `delta-neutral`,
287
+ `strategy`, `delta-neutral`,
272
288
  `--min-rate`, opts.minRate, `--close-rate`, opts.closeRate,
273
289
  `--size`, opts.size, `--max-positions`, opts.maxPositions,
274
290
  `--leverage`, opts.leverage, `--exchanges`, opts.exchanges,
@@ -277,6 +293,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
277
293
  const job = startJob({
278
294
  strategy: `bot:delta-neutral`,
279
295
  exchange: "multi",
296
+ network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
280
297
  params: { ...opts },
281
298
  cliArgs,
282
299
  });
@@ -287,7 +304,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
287
304
  }
288
305
  const mode = resolveOutputMode(isJson, opts.headless);
289
306
  const log = makeLog();
290
- await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode);
307
+ await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode, false);
291
308
  });
292
309
  // ── bot preset list ──
293
310
  bot
@@ -324,6 +341,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
324
341
  .option("--background", "Run in background (tmux)")
325
342
  .option("--headless", "Run without TUI dashboard")
326
343
  .option("--job-id <id>", "Job ID")
344
+ .option("--size <n>", "Override the preset size: grid total base size, DCA base size per order, arb $ per leg")
327
345
  .action(async (name, symbol, opts) => {
328
346
  const preset = getPreset(name);
329
347
  if (!preset) {
@@ -334,7 +352,8 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
334
352
  const sym = symbol?.toUpperCase() ?? "ETH";
335
353
  // For arb presets, need multiple adapters
336
354
  if (preset.strategy === "funding-arb") {
337
- const config = preset.buildConfig("multi", sym);
355
+ const built = preset.buildConfig("multi", sym);
356
+ const config = opts.size !== undefined ? withPresetSize(built, opts.size) : built;
338
357
  const arbStrategy = config.strategy;
339
358
  const adapters = new Map();
340
359
  for (const exName of arbStrategy.exchanges) {
@@ -352,11 +371,12 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
352
371
  const primaryAdapter = adapters.values().next().value;
353
372
  config.exchange = primaryAdapter.name;
354
373
  if (opts.background) {
355
- const { startJob } = await import("../jobs.js");
356
- const cliArgs = [`preset`, name, sym];
374
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
375
+ const cliArgs = [`strategy`, `preset`, name, sym, ...(opts.size !== undefined ? [`--size`, opts.size] : [])];
357
376
  const job = startJob({
358
377
  strategy: `bot:${preset.strategy}`,
359
378
  exchange: "multi",
379
+ network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
360
380
  params: { preset: name, symbol: sym },
361
381
  cliArgs,
362
382
  });
@@ -371,18 +391,20 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
371
391
  console.log(chalk.gray(` ${preset.description}\n`));
372
392
  }
373
393
  const mode = resolveOutputMode(isJson, opts.headless);
374
- await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode);
394
+ await runBot(primaryAdapter, config, opts.jobId, log, adapters, mode, false);
375
395
  return;
376
396
  }
377
397
  // Grid / DCA presets
378
398
  const adapter = await getAdapter();
379
- const config = preset.buildConfig(adapter.name, sym);
399
+ const built = preset.buildConfig(adapter.name, sym);
400
+ const config = opts.size !== undefined ? withPresetSize(built, opts.size) : built;
380
401
  if (opts.background) {
381
- const { startJob } = await import("../jobs.js");
382
- const cliArgs = [`preset`, name, sym];
402
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
403
+ const cliArgs = [`strategy`, `preset`, name, sym, ...(opts.size !== undefined ? [`--size`, opts.size] : [])];
383
404
  const job = startJob({
384
405
  strategy: `bot:${preset.strategy}`,
385
406
  exchange: adapter.name,
407
+ venue: adapter.name, network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
386
408
  params: { preset: name, symbol: sym },
387
409
  cliArgs,
388
410
  });
@@ -397,7 +419,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
397
419
  console.log(chalk.gray(` ${preset.description}\n`));
398
420
  }
399
421
  const mode = resolveOutputMode(isJson, opts.headless);
400
- await runBot(adapter, config, opts.jobId, log, undefined, mode);
422
+ await runBot(adapter, config, opts.jobId, log, undefined, mode, false);
401
423
  });
402
424
  // ── bot list-strategies ──
403
425
  bot
@@ -416,6 +438,44 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
416
438
  }
417
439
  console.log();
418
440
  });
441
+ // ── quarantine (design §12.3): positions the bot refuses to touch until a human clears them ──
442
+ bot
443
+ .command("quarantine")
444
+ .description("List quarantined symbols (unknown / unverified order outcome or unhedged leg) — blocked for every bot strategy, arb exec and arb auto (~/.perp/bot-quarantine.json)")
445
+ .action(async () => {
446
+ const { listQuarantine, QUARANTINE_FILE } = await import("../bot/quarantine.js");
447
+ const entries = listQuarantine();
448
+ if (isJson())
449
+ return printJson(jsonOk({ file: QUARANTINE_FILE, entries }));
450
+ if (entries.length === 0) {
451
+ console.log(chalk.gray("\n No quarantined bot positions.\n"));
452
+ return;
453
+ }
454
+ console.log(chalk.red.bold(`\n Quarantined symbols — blocked for all bots and arb paths (${QUARANTINE_FILE}):\n`));
455
+ for (const e of entries) {
456
+ console.log(` ${chalk.white(`${e.strategy} ${e.symbol}`)} ${chalk.gray(`${e.time} · ${e.network ?? "any network (set by a process that did not declare one)"}`)}`);
457
+ console.log(` ${e.reason}`);
458
+ console.log(chalk.yellow(` ${e.remediation}`));
459
+ }
460
+ console.log(chalk.gray("\n After resolving on the venue: perp strategy quarantine-clear <symbol> [--strategy <name>]\n"));
461
+ });
462
+ bot
463
+ .command("quarantine-clear <symbol>")
464
+ .description("Clear a quarantine after verifying / resolving the position on the venues manually (running bots pick it up on their next tick)")
465
+ .option("--strategy <name>", "Only entries recorded by this source (funding-arb, funding-arb-v2, spot-perp-arb, arb-exec, arb-close); default: all — the symbol stays blocked while any entry remains")
466
+ .action(async (symbol, opts) => {
467
+ const { clearQuarantine } = await import("../bot/quarantine.js");
468
+ const removed = clearQuarantine(symbol, opts.strategy ?? null);
469
+ if (isJson())
470
+ return printJson(jsonOk({ symbol: symbol.toUpperCase(), removed }));
471
+ if (removed.length === 0) {
472
+ console.log(chalk.yellow(`\n No quarantine entry for ${symbol.toUpperCase()}${opts.strategy ? ` (${opts.strategy})` : ""}.\n`));
473
+ return;
474
+ }
475
+ for (const e of removed)
476
+ console.log(chalk.green(`\n Cleared ${e.strategy} ${e.symbol}`));
477
+ console.log();
478
+ });
419
479
  // ── bot apex ──
420
480
  bot
421
481
  .command("apex [symbol]")
@@ -450,15 +510,16 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
450
510
  monitor_interval_sec: 10,
451
511
  };
452
512
  if (opts.background) {
453
- const { startJob } = await import("../jobs.js");
513
+ const { startJob, jobSelectorsOf } = await import("../jobs.js");
454
514
  const cliArgs = [
455
- `-e`, adapter.name, `apex`, sym,
515
+ `strategy`, `apex`, sym,
456
516
  `--preset`, opts.preset, `--max-slots`, opts.maxSlots,
457
517
  `--daily-limit`, opts.dailyLimit,
458
518
  ];
459
519
  const job = startJob({
460
520
  strategy: `bot:apex`,
461
521
  exchange: adapter.name,
522
+ venue: adapter.name, network: networkOf(program), dryRun: isDryRun(program), selectors: jobSelectorsOf(program),
462
523
  params: { symbol: sym, ...opts },
463
524
  cliArgs,
464
525
  });
@@ -469,7 +530,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
469
530
  }
470
531
  const mode = resolveOutputMode(isJson, opts.headless);
471
532
  const log = makeLog();
472
- await runBot(adapter, config, opts.jobId, log, undefined, mode);
533
+ await runBot(adapter, config, opts.jobId, log, undefined, mode, false);
473
534
  });
474
535
  // ── bot reflect ──
475
536
  bot
@@ -510,9 +571,8 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
510
571
  console.log();
511
572
  });
512
573
  // ── bot run <strategy> <symbol> ──
513
- bot
514
- .command("run [strategy] [symbol]")
515
- .description("Run a strategy (use 'perp strategy list-strategies' to see all). Symbol optional for multi-symbol strategies.")
574
+ supportsDryRun(bot.command("run [strategy] [symbol]"))
575
+ .description("Run a strategy (use 'perp strategy list-strategies' to see all). Symbol optional for multi-symbol strategies. Supports --dry-run (actions logged, never sent; strategies that execute directly refuse it).")
516
576
  .option("--config <path>", "YAML/JSON config file")
517
577
  .option("--headless", "Run without TUI dashboard")
518
578
  .option("--param <key=value>", "Strategy parameter (repeatable)", (val, acc) => [...acc, val], [])
@@ -521,7 +581,8 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
521
581
  .option("--min-spread <pct>", "Min annual spread to enter (%)")
522
582
  .option("--close-spread <pct>", "Close when spread drops below (%)")
523
583
  .option("--exchanges <list>", "Comma-separated exchanges")
524
- .action(async (strategyName, symbol, opts) => {
584
+ .action(async (strategyName, symbol, opts, cmd) => {
585
+ const dryRun = isDryRun(cmd);
525
586
  if (!strategyName) {
526
587
  await import("../bot/engine.js");
527
588
  const { listStrategies: listStrats } = await import("../bot/strategy-registry.js");
@@ -537,6 +598,9 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
537
598
  return;
538
599
  }
539
600
  const sym = symbol?.toUpperCase() || "ALL";
601
+ // Refuse --dry-run for strategies that cannot simulate before any adapter is built.
602
+ if (dryRun)
603
+ assertStrategySimulatable(strategyName);
540
604
  // Strategies that require an explicit symbol
541
605
  const symbolRequiredStrategies = [
542
606
  "grid", "dca", "simple-mm", "engine-mm", "avellaneda-mm",
@@ -555,7 +619,7 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
555
619
  // Load or build config
556
620
  let config;
557
621
  if (opts.config) {
558
- config = loadBotConfig(opts.config);
622
+ config = loadBotConfig(opts.config, cliVenue());
559
623
  // Override strategy type and symbol from CLI args
560
624
  config.strategy = { ...config.strategy, type: strategyName };
561
625
  config.symbol = sym;
@@ -625,8 +689,8 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
625
689
  console.error();
626
690
  return process.exit(1);
627
691
  }
628
- const adapter = await getAdapter();
629
- config.exchange = adapter.name;
692
+ // --config: the config's venue (resolved against -e above); otherwise the -e venue.
693
+ const adapter = await getAdapterFor(config.exchange);
630
694
  const mode = resolveOutputMode(isJson, opts.headless);
631
695
  const log = makeLog();
632
696
  // Multi-exchange strategies need all adapters
@@ -634,17 +698,29 @@ export function registerStrategyCommands(program, getAdapter, getAdapterFor, isJ
634
698
  const multiExchangeStrategies = ["funding-auto", "funding-arb", "funding-arb-v2", "spot-perp-arb", "basis-arb", "hedge-agent"];
635
699
  if (multiExchangeStrategies.includes(strategyName) && getAdapterFor) {
636
700
  extraAdapters = new Map();
637
- for (const ex of ["pacifica", "hyperliquid", "lighter", "aster"]) {
701
+ // --exchanges restricts the venue set (each listed venue must load); the default set
702
+ // skips a venue that cannot load, with a warning.
703
+ const explicit = opts.exchanges !== undefined;
704
+ const venues = explicit ? opts.exchanges.split(",").map(e => resolveVenueId(e.trim())) : ["pacifica", "hyperliquid", "lighter", "aster"];
705
+ if (explicit && !venues.includes(adapter.name)) {
706
+ throw new PerpError("INVALID_PARAMS", `--exchanges ${opts.exchanges} does not include the bot's own venue ${adapter.name}`, {
707
+ remediation: `Add ${adapter.name} to --exchanges, or select another venue with -e`,
708
+ });
709
+ }
710
+ for (const ex of venues) {
638
711
  if (ex === adapter.name)
639
712
  continue;
640
713
  try {
641
- const a = await getAdapterFor(ex);
642
- extraAdapters.set(ex, a);
714
+ extraAdapters.set(ex, await getAdapterFor(ex));
715
+ }
716
+ catch (e) {
717
+ if (explicit)
718
+ throw e;
719
+ log(` WARNING: ${ex} unavailable (${e instanceof Error ? e.message : String(e)}) — running without it`);
643
720
  }
644
- catch { /* skip unavailable exchanges */ }
645
721
  }
646
722
  }
647
- await runBot(adapter, config, undefined, log, extraAdapters, mode);
723
+ await runBot(adapter, config, undefined, log, extraAdapters, mode, dryRun);
648
724
  });
649
725
  // ── bot example ──
650
726
  bot
@@ -778,6 +854,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
778
854
  .description("Run client-side TWAP (splits market orders over time)");
779
855
  twapCmd
780
856
  .option("-s, --slices <n>", "Number of slices (default: auto)")
857
+ .option("--reduce-only", "Reduce only (every slice)")
781
858
  .option("--job-id <id>", "Job ID (set automatically for background jobs)")
782
859
  .action(async (symbol, side, size, duration, opts) => {
783
860
  const s = side.toLowerCase();
@@ -792,6 +869,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
792
869
  totalSize: parseFloat(size),
793
870
  durationSec: parseInt(duration),
794
871
  slices: opts.slices ? parseInt(opts.slices) : undefined,
872
+ reduceOnly: opts.reduceOnly === true,
795
873
  };
796
874
  const log = (msg) => {
797
875
  const ts = new Date().toLocaleTimeString();
@@ -813,7 +891,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
813
891
  .option("--size-usd <usd>", "Position size per leg in USD (alternative)")
814
892
  .option("--symbols <list>", "Comma-separated symbol filter")
815
893
  .option("--interval <sec>", "Check interval in seconds", "60")
816
- .option("--auto-execute", "Actually place trades (default: monitor only)")
894
+ .option("--auto-execute", "Actually place trades (default: monitor only). Legs are sequential and verified (long, then short sized to the long's fill); closes are reduce-only from the venue position size; an unknown outcome quarantines the symbol")
817
895
  .option("--max-positions <n>", "Max simultaneous positions", "3")
818
896
  .option("--exchanges <list>", "Comma-separated exchanges to use", "pacifica,hyperliquid,lighter,aster")
819
897
  .option("--auto-rebalance", "Trigger rebalance alerts when exchange balance is low")
@@ -859,7 +937,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
859
937
  // ── Grid Bot ──
860
938
  const gridCmd = run
861
939
  .command("grid <symbol>")
862
- .description("Run grid trading bot (places limit orders across a price range)");
940
+ .description("Run grid trading bot (places limit orders across a price range). At stop the remaining orders are cancelled; the grid's inventory (position change since start) is left open with a warning unless --close-on-stop");
863
941
  gridCmd
864
942
  .requiredOption("--upper <price>", "Upper price bound")
865
943
  .requiredOption("--lower <price>", "Lower price bound")
@@ -870,6 +948,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
870
948
  .option("--interval <sec>", "Check interval in seconds", "10")
871
949
  .option("--max-runtime <sec>", "Max runtime in seconds (0 = forever)", "0")
872
950
  .option("--trailing-stop <pct>", "Stop if equity drops by this % from peak")
951
+ .option("--close-on-stop", "At stop, close the grid's own inventory (position change since start) reduce-only; default: leave it open and warn")
873
952
  .option("--job-id <id>", "Job ID (set automatically for background jobs)")
874
953
  .action(async (symbol, opts) => {
875
954
  const side = opts.side.toLowerCase();
@@ -889,6 +968,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
889
968
  intervalSec: parseInt(opts.interval),
890
969
  maxRuntime: parseInt(opts.maxRuntime),
891
970
  trailingStop: opts.trailingStop ? parseFloat(opts.trailingStop) : undefined,
971
+ closeOnStop: opts.closeOnStop === true,
892
972
  };
893
973
  const log = (msg) => {
894
974
  const ts = new Date().toLocaleTimeString();
@@ -920,7 +1000,7 @@ function registerRunSubcommands(parent, getAdapter, getAdapterFor, isJson) {
920
1000
  const params = {
921
1001
  symbol: symbol.toUpperCase(),
922
1002
  side: s,
923
- amountPerOrder: parseFloat(amount),
1003
+ amountPerOrder: Number(parsePositiveDecimal(amount, "amount")),
924
1004
  intervalSec: parseInt(interval),
925
1005
  totalOrders: parseInt(opts.orders),
926
1006
  priceLimit: opts.priceLimit ? parseFloat(opts.priceLimit) : undefined,
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
- export declare function registerFundsBridgeCommands(parent: Command, isJson: () => boolean): void;
2
+ import type { Network } from "../exchanges/venue-types.js";
3
+ export declare function registerFundsBridgeCommands(parent: Command, isJson: () => boolean, getNetwork: () => Network): void;