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
@@ -1,24 +1,38 @@
1
1
  /**
2
2
  * Arb utilities: settlement timing strategy, basis risk monitoring, and webhook notifications.
3
3
  */
4
- /** Settlement schedules per exchange (UTC hours when settlement occurs) */
4
+ /**
5
+ * Settlement schedules per venue (UTC hours when settlement occurs), generated
6
+ * from `VenueDefinition.settlementIntervalHours`, anchored at 00:00 UTC
7
+ * (design §10.1, §10.13 #1). Venues whose interval is per-market (Aster)
8
+ * have no entry — pass the market's interval to getLastSettlement().
9
+ */
5
10
  export declare const SETTLEMENT_SCHEDULES: Record<string, number[]>;
6
11
  export type SettleStrategy = "block" | "aggressive" | "off";
7
12
  /**
8
- * Get the most recent settlement time for an exchange.
9
- * @returns Date of the most recent past settlement
13
+ * Settlement interval in hours for a venue ref. `marketIntervalHours` (the
14
+ * market's `settlementIntervalHours`) takes precedence; it is required for
15
+ * per-market venues. Unknown venues throw UNKNOWN_VENUE.
16
+ */
17
+ export declare function settlementIntervalHours(exchange: string, marketIntervalHours?: number | null): number;
18
+ /**
19
+ * Get the most recent settlement time for an exchange (anchored at 00:00 UTC).
20
+ * @param marketIntervalHours market settlement interval; required for per-market venues
21
+ * @returns Date of the most recent past settlement (== now when exactly on a settlement)
10
22
  */
11
- export declare function getLastSettlement(exchange: string, now?: Date): Date;
23
+ export declare function getLastSettlement(exchange: string, now?: Date, marketIntervalHours?: number | null): Date;
12
24
  /**
13
25
  * Get minutes since the most recent settlement for an exchange.
14
26
  */
15
- export declare function getMinutesSinceSettlement(exchange: string, now?: Date): number;
27
+ export declare function getMinutesSinceSettlement(exchange: string, now?: Date, marketIntervalHours?: number | null): number;
16
28
  /**
17
29
  * Compute a score boost for aggressive settlement mode.
18
30
  * Returns a multiplier > 1.0 if within the post-settlement window, else 1.0.
19
31
  * The boost decays linearly from 1.5x (right after settlement) to 1.0x (at windowMinutes).
32
+ * `marketIntervalFor` returns the market's settlement interval per exchange;
33
+ * it must return a number for per-market venues (Aster).
20
34
  */
21
- export declare function aggressiveSettleBoost(longExchange: string, shortExchange: string, windowMinutes?: number, now?: Date): number;
35
+ export declare function aggressiveSettleBoost(longExchange: string, shortExchange: string, windowMinutes?: number, now?: Date, marketIntervalFor?: (exchange: string) => number | null): number;
22
36
  /**
23
37
  * Estimate cumulative funding between now and the next settlement.
24
38
  *
package/dist/arb/utils.js CHANGED
@@ -1,62 +1,66 @@
1
1
  /**
2
2
  * Arb utilities: settlement timing strategy, basis risk monitoring, and webhook notifications.
3
3
  */
4
+ import { PerpError } from "../errors.js";
5
+ import { listVenues, settlementIntervalOf } from "../exchanges/venue.js";
6
+ import { ensureVenuesRegistered } from "../exchanges/venues/index.js";
4
7
  // ── Settlement Timing Strategy ──
5
- /** Settlement schedules per exchange (UTC hours when settlement occurs) */
6
- export const SETTLEMENT_SCHEDULES = {
7
- hyperliquid: Array.from({ length: 24 }, (_, i) => i), // every hour
8
- pacifica: Array.from({ length: 24 }, (_, i) => i), // every hour
9
- lighter: Array.from({ length: 24 }, (_, i) => i), // every hour
10
- aster: [0, 8, 16], // every 8 hours (Binance convention)
11
- };
8
+ ensureVenuesRegistered();
12
9
  /**
13
- * Get the most recent settlement time for an exchange.
14
- * @returns Date of the most recent past settlement
10
+ * Settlement schedules per venue (UTC hours when settlement occurs), generated
11
+ * from `VenueDefinition.settlementIntervalHours`, anchored at 00:00 UTC
12
+ * (design §10.1, §10.13 #1). Venues whose interval is per-market (Aster)
13
+ * have no entry — pass the market's interval to getLastSettlement().
15
14
  */
16
- export function getLastSettlement(exchange, now = new Date()) {
17
- const schedule = SETTLEMENT_SCHEDULES[exchange.toLowerCase()];
18
- if (!schedule || schedule.length === 0) {
19
- return getLastSettlement("pacifica", now);
15
+ export const SETTLEMENT_SCHEDULES = Object.fromEntries(listVenues()
16
+ .filter(v => v.settlementIntervalHours !== "perMarket")
17
+ .map(v => [v.id, scheduleFor(v.settlementIntervalHours)]));
18
+ function scheduleFor(intervalHours) {
19
+ if (!(intervalHours > 0) || 24 % intervalHours !== 0) {
20
+ throw new PerpError("INVALID_PARAMS", `Settlement interval ${intervalHours}h does not divide a UTC day`);
20
21
  }
21
- const currentHour = now.getUTCHours();
22
- const currentMinutes = now.getUTCMinutes();
23
- const currentSeconds = now.getUTCSeconds();
24
- // Find the most recent settlement hour at or before now
25
- for (let i = schedule.length - 1; i >= 0; i--) {
26
- const hour = schedule[i];
27
- if (hour < currentHour || (hour === currentHour && (currentMinutes > 0 || currentSeconds > 0))) {
28
- const last = new Date(now);
29
- last.setUTCHours(hour, 0, 0, 0);
30
- return last;
31
- }
32
- // Exactly at settlement time
33
- if (hour === currentHour && currentMinutes === 0 && currentSeconds === 0) {
34
- const last = new Date(now);
35
- last.setUTCHours(hour, 0, 0, 0);
36
- return last;
22
+ return Array.from({ length: 24 / intervalHours }, (_, i) => i * intervalHours);
23
+ }
24
+ /**
25
+ * Settlement interval in hours for a venue ref. `marketIntervalHours` (the
26
+ * market's `settlementIntervalHours`) takes precedence; it is required for
27
+ * per-market venues. Unknown venues throw UNKNOWN_VENUE.
28
+ */
29
+ export function settlementIntervalHours(exchange, marketIntervalHours = null) {
30
+ if (marketIntervalHours !== null) {
31
+ if (!(marketIntervalHours > 0)) {
32
+ throw new PerpError("INVALID_PARAMS", `Invalid settlement interval ${marketIntervalHours}h for ${exchange}`);
37
33
  }
34
+ return marketIntervalHours;
38
35
  }
39
- // Wrap to previous day's last settlement
40
- const last = new Date(now);
41
- last.setUTCDate(last.getUTCDate() - 1);
42
- last.setUTCHours(schedule[schedule.length - 1], 0, 0, 0);
43
- return last;
36
+ return settlementIntervalOf(exchange);
37
+ }
38
+ /**
39
+ * Get the most recent settlement time for an exchange (anchored at 00:00 UTC).
40
+ * @param marketIntervalHours market settlement interval; required for per-market venues
41
+ * @returns Date of the most recent past settlement (== now when exactly on a settlement)
42
+ */
43
+ export function getLastSettlement(exchange, now = new Date(), marketIntervalHours = null) {
44
+ const intervalMs = settlementIntervalHours(exchange, marketIntervalHours) * 3_600_000;
45
+ return new Date(Math.floor(now.getTime() / intervalMs) * intervalMs);
44
46
  }
45
47
  /**
46
48
  * Get minutes since the most recent settlement for an exchange.
47
49
  */
48
- export function getMinutesSinceSettlement(exchange, now = new Date()) {
49
- const lastSettle = getLastSettlement(exchange, now);
50
+ export function getMinutesSinceSettlement(exchange, now = new Date(), marketIntervalHours = null) {
51
+ const lastSettle = getLastSettlement(exchange, now, marketIntervalHours);
50
52
  return (now.getTime() - lastSettle.getTime()) / (1000 * 60);
51
53
  }
52
54
  /**
53
55
  * Compute a score boost for aggressive settlement mode.
54
56
  * Returns a multiplier > 1.0 if within the post-settlement window, else 1.0.
55
57
  * The boost decays linearly from 1.5x (right after settlement) to 1.0x (at windowMinutes).
58
+ * `marketIntervalFor` returns the market's settlement interval per exchange;
59
+ * it must return a number for per-market venues (Aster).
56
60
  */
57
- export function aggressiveSettleBoost(longExchange, shortExchange, windowMinutes = 10, now = new Date()) {
58
- const longMinutes = getMinutesSinceSettlement(longExchange, now);
59
- const shortMinutes = getMinutesSinceSettlement(shortExchange, now);
61
+ export function aggressiveSettleBoost(longExchange, shortExchange, windowMinutes = 10, now = new Date(), marketIntervalFor = () => null) {
62
+ const longMinutes = getMinutesSinceSettlement(longExchange, now, marketIntervalFor(longExchange));
63
+ const shortMinutes = getMinutesSinceSettlement(shortExchange, now, marketIntervalFor(shortExchange));
60
64
  // Use the minimum of the two — we want BOTH exchanges to have recently settled
61
65
  const minMinutes = Math.min(longMinutes, shortMinutes);
62
66
  if (minMinutes <= windowMinutes) {
@@ -19,14 +19,14 @@ export interface MarketSnapshot {
19
19
  export declare function calculateRSI(closes: number[], period?: number): number;
20
20
  export declare function getMarketSnapshot(adapter: ExchangeAdapter, symbol: string): Promise<MarketSnapshot>;
21
21
  export declare function evaluateCondition(condition: Condition, snapshot: MarketSnapshot, context: {
22
- equity: number;
22
+ equity: number | null;
23
23
  startTime: number;
24
- peakEquity: number;
24
+ peakEquity: number | null;
25
25
  dailyPnl: number;
26
26
  }): boolean;
27
27
  export declare function evaluateAllConditions(conditions: Condition[], snapshot: MarketSnapshot, context: {
28
- equity: number;
28
+ equity: number | null;
29
29
  startTime: number;
30
- peakEquity: number;
30
+ peakEquity: number | null;
31
31
  dailyPnl: number;
32
32
  }, mode?: "all" | "any"): boolean;
@@ -1,3 +1,4 @@
1
+ import { symbolMatch } from "../utils.js";
1
2
  // ── RSI Calculation (Wilder smoothing) ──
2
3
  /**
3
4
  * Calculate RSI from an array of closing prices using Wilder's smoothing method.
@@ -39,13 +40,16 @@ export function calculateRSI(closes, period = 14) {
39
40
  }
40
41
  export async function getMarketSnapshot(adapter, symbol) {
41
42
  const markets = await adapter.getMarkets();
42
- const m = markets.find(mk => mk.symbol.toUpperCase() === symbol.toUpperCase());
43
+ const m = markets.find(mk => symbolMatch(mk.symbol, symbol));
43
44
  if (!m) {
44
45
  throw new Error(`Market "${symbol}" not found on ${adapter.name}`);
45
46
  }
46
47
  const price = parseFloat(m.markPrice);
47
- const fundingRate = parseFloat(m.fundingRate ?? "0");
48
- const volume24h = parseFloat(m.volume24h);
48
+ // Unknown funding (venue publishes none, e.g. QFEX) → NaN, never 0: a 0 would satisfy
49
+ // "funding below X" conditions on a rate nobody reported. NaN makes every comparison false.
50
+ const fundingRate = m.fundingRate === null ? NaN : parseFloat(m.fundingRate);
51
+ // Unknown volume → NaN, never 0: a 0 would read as "no liquidity" to volume conditions.
52
+ const volume24h = m.volume24h === null ? NaN : parseFloat(m.volume24h);
49
53
  // Try to get kline data for 24h high/low + RSI
50
54
  let high24h = price;
51
55
  let low24h = price;
@@ -113,10 +117,11 @@ export function evaluateCondition(condition, snapshot, context) {
113
117
  return !isNaN(snapshot.rsi) && snapshot.rsi > val;
114
118
  case "rsi_below":
115
119
  return !isNaN(snapshot.rsi) && snapshot.rsi < val;
120
+ // Unknown equity (null) never meets an equity condition — neither a fake stop nor a fake entry.
116
121
  case "balance_above":
117
- return context.equity > val;
122
+ return context.equity !== null && context.equity > val;
118
123
  case "balance_below":
119
- return context.equity < val;
124
+ return context.equity !== null && context.equity < val;
120
125
  case "spread_above":
121
126
  // Bid-ask spread percentage, pre-computed from orderbook in snapshot.
122
127
  return snapshot.spreadPct > val;
@@ -125,7 +130,7 @@ export function evaluateCondition(condition, snapshot, context) {
125
130
  return elapsed > val;
126
131
  }
127
132
  case "max_drawdown":
128
- return (context.peakEquity - context.equity) > val;
133
+ return context.equity !== null && context.peakEquity !== null && (context.peakEquity - context.equity) > val;
129
134
  default:
130
135
  return false;
131
136
  }
@@ -1,3 +1,4 @@
1
+ import { type VenueId } from "../exchanges/venue.js";
1
2
  export type ConditionType = "price_above" | "price_below" | "volatility_above" | "volatility_below" | "funding_rate_above" | "funding_rate_below" | "spread_above" | "rsi_above" | "rsi_below" | "balance_above" | "balance_below" | "time_after" | "max_drawdown" | "always";
2
3
  export interface Condition {
3
4
  type: ConditionType;
@@ -59,7 +60,22 @@ export interface BotConfig {
59
60
  risk: RiskConfig;
60
61
  monitor_interval_sec: number;
61
62
  }
62
- export declare function loadBotConfig(pathOrInline: string): BotConfig;
63
+ /**
64
+ * The venue selected on the command line: the root `-e` value (resolved id) and whether it was
65
+ * given explicitly (commander option source "cli") or is the CLI default.
66
+ */
67
+ export interface CliVenueSelection {
68
+ id: VenueId;
69
+ explicit: boolean;
70
+ }
71
+ /**
72
+ * The venue a config runs on. `config.exchange` (alias resolved; unknown → UNKNOWN_VENUE) wins
73
+ * over the CLI default; an explicit root `-e` naming a different venue is a contradiction →
74
+ * INVALID_PARAMS (never one of them silently). No `exchange` in the config → the CLI selection.
75
+ */
76
+ export declare function resolveConfigVenue(configExchange: string | null, cli: CliVenueSelection): VenueId;
77
+ /** Load a bot config; its venue is resolved against the CLI selection (resolveConfigVenue). */
78
+ export declare function loadBotConfig(pathOrInline: string, cli: CliVenueSelection): BotConfig;
63
79
  export declare function parseStrategy(type: string, raw: Record<string, unknown>): StrategyParams;
64
80
  export declare function quickGridConfig(opts: {
65
81
  exchange: string;
@@ -1,5 +1,7 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
2
  import { parse as parseYaml } from "yaml";
3
+ import { PerpError, extractErrorMessage } from "../errors.js";
4
+ import { resolveVenueId } from "../exchanges/venue.js";
3
5
  // ── Defaults ──
4
6
  const DEFAULT_RISK = {
5
7
  max_position_usd: 1000,
@@ -8,27 +10,61 @@ const DEFAULT_RISK = {
8
10
  pause_after_loss_sec: 300,
9
11
  max_open_bots: 5,
10
12
  };
11
- // ── Loader ──
12
- export function loadBotConfig(pathOrInline) {
13
+ /**
14
+ * The venue a config runs on. `config.exchange` (alias resolved; unknown → UNKNOWN_VENUE) wins
15
+ * over the CLI default; an explicit root `-e` naming a different venue is a contradiction →
16
+ * INVALID_PARAMS (never one of them silently). No `exchange` in the config → the CLI selection.
17
+ */
18
+ export function resolveConfigVenue(configExchange, cli) {
19
+ if (configExchange === null)
20
+ return cli.id;
21
+ const id = resolveVenueId(configExchange);
22
+ if (cli.explicit && cli.id !== id) {
23
+ throw new PerpError("INVALID_PARAMS", `The config runs on "${configExchange}" (${id}) but -e selects ${cli.id}`, {
24
+ remediation: `Drop -e, or pass -e ${id}, or change "exchange" in the config`,
25
+ });
26
+ }
27
+ return id;
28
+ }
29
+ /** Load a bot config; its venue is resolved against the CLI selection (resolveConfigVenue). */
30
+ export function loadBotConfig(pathOrInline, cli) {
13
31
  let raw;
32
+ const remediation = "Pass a readable YAML/JSON config file path (or inline JSON). See examples with: perp strategy example";
14
33
  if (existsSync(pathOrInline)) {
15
- const content = readFileSync(pathOrInline, "utf-8");
16
- if (pathOrInline.endsWith(".json")) {
17
- raw = JSON.parse(content);
34
+ let content;
35
+ try {
36
+ content = readFileSync(pathOrInline, "utf-8");
37
+ }
38
+ catch (err) {
39
+ throw new PerpError("INVALID_PARAMS", `Cannot read bot config file '${pathOrInline}': ${extractErrorMessage(err)}`, { remediation });
40
+ }
41
+ try {
42
+ raw = pathOrInline.endsWith(".json") ? JSON.parse(content) : parseYaml(content);
18
43
  }
19
- else {
20
- raw = parseYaml(content);
44
+ catch (err) {
45
+ throw new PerpError("INVALID_PARAMS", `Bot config file '${pathOrInline}' is not valid ${pathOrInline.endsWith(".json") ? "JSON" : "YAML"}: ${extractErrorMessage(err)}`, { remediation });
46
+ }
47
+ }
48
+ else if (pathOrInline.trimStart().startsWith("{")) {
49
+ // Inline JSON config
50
+ try {
51
+ raw = JSON.parse(pathOrInline);
52
+ }
53
+ catch (err) {
54
+ throw new PerpError("INVALID_PARAMS", `Inline bot config is not valid JSON: ${extractErrorMessage(err)}`, { remediation });
21
55
  }
22
56
  }
23
57
  else {
24
- // Try parsing as inline JSON
25
- raw = JSON.parse(pathOrInline);
58
+ throw new PerpError("INVALID_PARAMS", `Bot config file not found: '${pathOrInline}'`, { remediation });
26
59
  }
27
- return parseBotConfig(raw);
60
+ return parseBotConfig(raw, cli);
28
61
  }
29
- function parseBotConfig(raw) {
62
+ function parseBotConfig(raw, cli) {
30
63
  const name = String(raw.name ?? `bot-${Date.now()}`);
31
- const exchange = String(raw.exchange ?? "hyperliquid");
64
+ if (raw.exchange !== undefined && (typeof raw.exchange !== "string" || raw.exchange.trim() === "")) {
65
+ throw new PerpError("INVALID_PARAMS", `Bot config "exchange" must be a venue name (got ${JSON.stringify(raw.exchange)})`);
66
+ }
67
+ const exchange = resolveConfigVenue(typeof raw.exchange === "string" ? raw.exchange : null, cli);
32
68
  const symbol = String(raw.symbol ?? "ETH");
33
69
  const monitorInterval = Number(raw.monitor_interval_sec ?? raw.interval ?? 30);
34
70
  // Strategy
@@ -1,5 +1,8 @@
1
1
  import type { ExchangeAdapter } from "../exchanges/index.js";
2
+ import { filledOf } from "./fill-size.js";
2
3
  import type { BotConfig } from "./config.js";
4
+ import type { StrategyAction } from "./strategy-types.js";
5
+ import { type QuoteRules } from "./quote-rules.js";
3
6
  import "./strategies/grid-strategy.js";
4
7
  import "./strategies/dca-strategy.js";
5
8
  import "./strategies/funding-arb-strategy.js";
@@ -22,9 +25,65 @@ import "./strategies/funding-auto.js";
22
25
  import "./strategies/spot-perp-arb-strategy.js";
23
26
  export type BotLog = (msg: string) => void;
24
27
  export type BotOutputMode = "tui" | "json" | "headless";
25
- export declare function runBot(adapter: ExchangeAdapter, config: BotConfig, jobId?: string, log?: BotLog, extraAdapters?: Map<string, ExchangeAdapter>, // for funding-arb (multi-exchange)
26
- mode?: BotOutputMode): Promise<{
28
+ /**
29
+ * Strategies that place/close orders themselves (multi-leg executors, spot legs) instead of
30
+ * returning StrategyActions — the engine cannot simulate them, so --dry-run is refused.
31
+ */
32
+ export declare const NON_SIMULATABLE_STRATEGIES: ReadonlySet<string>;
33
+ /**
34
+ * ExchangeAdapter methods a --dry-run bot may call: pure reads (plus resolveSymbol, a local
35
+ * lookup). Allowlist, not blocklist (review L4): any other method — every venue write, and any
36
+ * write added to an adapter later — throws, so a dry-run can never reach the venue's write path.
37
+ */
38
+ export declare const ADAPTER_READ_METHODS: ReadonlySet<string>;
39
+ /** Throw INVALID_PARAMS if `strategyType` cannot run under --dry-run (checked before any adapter call). */
40
+ export declare function assertStrategySimulatable(strategyType: string): void;
41
+ /** Wrap an adapter so only allowlisted reads run; every other method throws (defense in depth for --dry-run bots). */
42
+ export declare function dryRunAdapter(adapter: ExchangeAdapter): ExchangeAdapter;
43
+ export declare function runBot(adapter: ExchangeAdapter, config: BotConfig, jobId: string | undefined, log: BotLog, extraAdapters: Map<string, ExchangeAdapter> | undefined, // for funding-arb (multi-exchange)
44
+ mode: BotOutputMode,
45
+ /** Global --dry-run: strategy actions are logged, never sent; non-simulatable strategies are refused. */
46
+ isDryRun: boolean): Promise<{
27
47
  fills: number;
48
+ ordersPlaced: number;
28
49
  totalPnl: number;
29
50
  runtime: number;
30
51
  }>;
52
+ /** Consecutive ticks with unknown equity after which the bot stops (an error, never a silent run). */
53
+ export declare const UNKNOWN_EQUITY_STOP_TICKS = 10;
54
+ /** Strategies that read the snapshot's orderbook / klines: a tick whose read failed is skipped. */
55
+ export declare const MARKET_DATA_STRATEGIES: ReadonlySet<string>;
56
+ /** Strategies that hold spot legs (spot-perp): their stop summary lists the spot holdings too. */
57
+ export declare const SPOT_LEG_STRATEGIES: ReadonlySet<string>;
58
+ /** A position left open at stop, with the command that closes it (a pair's legs share one command). */
59
+ export interface OpenPositionAtStop {
60
+ exchange: string;
61
+ symbol: string;
62
+ side: string;
63
+ size: string;
64
+ close: string;
65
+ }
66
+ /**
67
+ * Open positions at stop (logged with the close command); a failed read is logged, never "none".
68
+ * `spotLegs` (a multi-venue spot-perp bot): the bot spot venues' holdings of the traded symbol(s)
69
+ * are listed too — a spot holding + a perp short of one symbol is closed as a pair
70
+ * (`arb close --spot-exch --perp-exch`), never the short alone (that leaves the spot unhedged).
71
+ */
72
+ export declare function openPositionsAtStop(primary: ExchangeAdapter, symbol: string, extra: Map<string, ExchangeAdapter> | undefined, log: BotLog, spotLegs: boolean): Promise<OpenPositionAtStop[]>;
73
+ /**
74
+ * Send strategy actions. Orders go out on the venue grid (quote-rules.ts): limit prices aligned
75
+ * passively to the tick (bid ↓, ask ↑), sizes floored to the lot, venue minimums refused before
76
+ * sending. `refPrice` = the tick's snapshot price (minimum order value of market orders).
77
+ * One order under the venue minimum (e.g. the inventory-skewed side of a market maker) is
78
+ * skipped for this tick with a log line — the other orders still go out; when EVERY order of
79
+ * the batch is under the minimum the last SIZE_TOO_SMALL is thrown (the tick fails: the
80
+ * strategy's base size itself is too small).
81
+ * Returns the orders sent (`placed`) and those the venue reported as (partly) filled (`filled`,
82
+ * filledOf) — an accepted resting limit order is a placement, not a fill. Under --dry-run every
83
+ * place_order is a simulated placement and nothing fills.
84
+ */
85
+ export declare function executeActions(adapter: ExchangeAdapter, symbol: string, actions: StrategyAction[], log: BotLog, dryRun: boolean, rulesOf: () => Promise<QuoteRules>, refPrice: number): Promise<{
86
+ placed: number;
87
+ filled: number;
88
+ }>;
89
+ export { filledOf };