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
@@ -16,11 +16,34 @@
16
16
  * - Saves 5-50+ bps vs raw market orders on thin books
17
17
  * - Predictable execution price
18
18
  *
19
+ * Order-outcome typing (design §12.3): every failure BEFORE the IOC is sent (orderbook read,
20
+ * empty book, unusable price), an explicit venue rejection embedded in the response, and an
21
+ * explicit IOC no-match (nothing filled, nothing rests) throw a definite ORDER_REJECTED
22
+ * (orderNotSent), so placePerpVerified does not quarantine a leg that never traded. Anything
23
+ * else the IOC call throws is rethrown unchanged (placePerpVerified measures it).
24
+ *
19
25
  * SSOT rule #2: silent fallback is forbidden. The opt-in `fallback: true` flag
20
26
  * is preserved for callers that explicitly want best-effort market substitution
21
27
  * after IOC reject, but the DEFAULT is `false` — failure must surface as a
22
28
  * thrown error, never a silent market sweep.
23
29
  */
30
+ import { alignComputedLimitPrice } from "./bot/quote-rules.js";
31
+ import { PerpError, orderNotSent } from "./errors.js";
32
+ import { resolveAdapterSlippage } from "./exchanges/slippage.js";
33
+ /**
34
+ * IOC "no match" signalled explicitly in the venue response (nothing filled, nothing rests):
35
+ * a Binance-style terminal status (Aster: EXPIRED / CANCELED / REJECTED) with executedQty 0.
36
+ * HL embedded errors (incl. "could not immediately match") are caught by hasOrderError.
37
+ */
38
+ function isIocNoMatch(result) {
39
+ if (!result || typeof result !== "object")
40
+ return false;
41
+ const r = result;
42
+ const status = typeof r.status === "string" ? r.status.toUpperCase() : "";
43
+ if (!["EXPIRED", "CANCELED", "CANCELLED", "REJECTED"].includes(status))
44
+ return false;
45
+ return r.executedQty !== undefined && Number(r.executedQty) === 0;
46
+ }
24
47
  /**
25
48
  * Infer tick size from orderbook prices.
26
49
  * Uses the minimum price increment visible in the book.
@@ -112,14 +135,25 @@ function describeOrderError(result) {
112
135
  */
113
136
  export async function smartOrder(adapter, symbol, side, size, opts = {}) {
114
137
  const { tickTolerance = 1, fallback = false, reduceOnly = false } = opts;
115
- // 1. Fetch orderbook
116
- const book = await adapter.getOrderbook(symbol);
138
+ const venue = adapter.name;
139
+ const what = `smart ${side} ${size} ${symbol}${reduceOnly ? " (reduce-only)" : ""}`;
140
+ const marketFallback = () => adapter.marketOrder(symbol, side, size, {
141
+ reduceOnly, slippage: opts.slippage ?? resolveAdapterSlippage(adapter, null),
142
+ });
143
+ // 1. Fetch orderbook (failure = nothing sent)
144
+ let book;
145
+ try {
146
+ book = await adapter.getOrderbook(symbol);
147
+ }
148
+ catch (e) {
149
+ throw orderNotSent(venue, `${what} (orderbook read failed)`, e);
150
+ }
117
151
  // 2. Validate book has liquidity on the relevant side
118
152
  const relevantSide = side === "buy" ? book.asks : book.bids;
119
153
  if (relevantSide.length === 0) {
120
154
  if (!fallback)
121
- throw new Error(`No ${side === "buy" ? "asks" : "bids"} in orderbook for ${symbol}`);
122
- const result = await adapter.marketOrder(symbol, side, size);
155
+ throw new PerpError("ORDER_REJECTED", `${venue} ${what} not sent: No ${side === "buy" ? "asks" : "bids"} in orderbook for ${symbol}`, { exchange: venue });
156
+ const result = await marketFallback();
123
157
  return { result, method: "market_fallback", price: "0", bestBookPrice: "0", tickSize: "0" };
124
158
  }
125
159
  // 3. Get best price and tick size
@@ -131,7 +165,11 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
131
165
  const limitPrice = side === "buy"
132
166
  ? bestPrice + tick * tickTolerance // 1 tick above best ask
133
167
  : bestPrice - tick * tickTolerance; // 1 tick below best bid
134
- const formattedPrice = limitPrice.toFixed(decimals);
168
+ // Book-inferred tick; venues without a published tick (HL) are then put on their own grid (passive side).
169
+ const formattedPrice = await alignComputedLimitPrice(adapter, symbol, side, limitPrice.toFixed(decimals));
170
+ if (!(Number.isFinite(bestPrice) && bestPrice > 0 && Number(formattedPrice) > 0)) {
171
+ throw new PerpError("ORDER_REJECTED", `${venue} ${what} not sent: unusable limit price ${formattedPrice} (best ${bestEntry[0]}, tick ${tick})`, { exchange: venue });
172
+ }
135
173
  // 5. Place IOC limit order at calculated price
136
174
  try {
137
175
  const result = await adapter.limitOrder(symbol, side, formattedPrice, size, {
@@ -145,7 +183,7 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
145
183
  // result for a rejected order and treat the failure as success.
146
184
  if (hasOrderError(result)) {
147
185
  if (fallback) {
148
- const fallbackResult = await adapter.marketOrder(symbol, side, size);
186
+ const fallbackResult = await marketFallback();
149
187
  return {
150
188
  result: fallbackResult,
151
189
  method: "market_fallback",
@@ -154,7 +192,14 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
154
192
  tickSize: tick.toFixed(decimals),
155
193
  };
156
194
  }
157
- throw new Error(`smartOrder: exchange rejected IOC limit (${describeOrderError(result)})`);
195
+ throw new PerpError("ORDER_REJECTED", `${venue} ${what}: exchange rejected IOC limit (${describeOrderError(result)})`, { exchange: venue });
196
+ }
197
+ if (isIocNoMatch(result)) {
198
+ if (fallback) {
199
+ const fallbackResult = await marketFallback();
200
+ return { result: fallbackResult, method: "market_fallback", price: formattedPrice, bestBookPrice: bestEntry[0], tickSize: tick.toFixed(decimals) };
201
+ }
202
+ throw new PerpError("ORDER_REJECTED", `${venue} ${what}: IOC limit ${formattedPrice} matched nothing (nothing rests)`, { exchange: venue });
158
203
  }
159
204
  return {
160
205
  result,
@@ -168,7 +213,7 @@ export async function smartOrder(adapter, symbol, side, size, opts = {}) {
168
213
  if (!fallback)
169
214
  throw err;
170
215
  // Fallback to raw market order for reliability
171
- const result = await adapter.marketOrder(symbol, side, size);
216
+ const result = await marketFallback();
172
217
  return {
173
218
  result,
174
219
  method: "market_fallback",
@@ -12,7 +12,11 @@ export interface DCAState {
12
12
  ordersPlaced: number;
13
13
  totalFilled: number;
14
14
  totalCost: number;
15
- avgPrice: number;
15
+ /**
16
+ * Average fill price from the VENUE-reported average fill of every order (reportedAvgFillPrice),
17
+ * never the protection / limit price sent; null when any filled order reported none.
18
+ */
19
+ avgPrice: number | null;
16
20
  errors: number;
17
21
  startedAt: number;
18
22
  running: boolean;
@@ -20,6 +24,6 @@ export interface DCAState {
20
24
  export declare function runDCA(adapter: ExchangeAdapter, params: DCAParams, jobId?: string, log?: (msg: string) => void): Promise<{
21
25
  ordersPlaced: number;
22
26
  totalFilled: number;
23
- avgPrice: number;
27
+ avgPrice: number | null;
24
28
  runtime: number;
25
29
  }>;
@@ -1,12 +1,22 @@
1
+ import { symbolMatch } from "../utils.js";
2
+ import { assertCanClosePositions } from "../exchanges/trading-v2.js";
3
+ import { resolveAdapterSlippage } from "../exchanges/slippage.js";
1
4
  import { updateJobState } from "../jobs.js";
5
+ import { reportedAvgFillPrice } from "../exchanges/ioc-outcome.js";
6
+ import { filledOf } from "../bot/fill-size.js";
7
+ import { isOutcomeUnknown } from "../bot/perp-order.js";
8
+ import { pendingStop } from "./pending-stop.js";
9
+ import { formatComputedPrice } from "../exchanges/precision.js";
10
+ import { venueRateString } from "../utils/numeric.js";
2
11
  export async function runDCA(adapter, params, jobId, log = console.log) {
12
+ assertCanClosePositions(adapter, `DCA on ${adapter.name}`);
3
13
  const { symbol, side, amountPerOrder, intervalSec, totalOrders } = params;
4
14
  const maxRuntime = (params.maxRuntime ?? 0) * 1000;
5
15
  const state = {
6
16
  ordersPlaced: 0,
7
17
  totalFilled: 0,
8
18
  totalCost: 0,
9
- avgPrice: 0,
19
+ avgPrice: null,
10
20
  errors: 0,
11
21
  startedAt: Date.now(),
12
22
  running: true,
@@ -20,6 +30,9 @@ export async function runDCA(adapter, params, jobId, log = console.log) {
20
30
  const shutdown = () => { state.running = false; };
21
31
  process.on("SIGINT", shutdown);
22
32
  process.on("SIGTERM", shutdown);
33
+ let everyOrderPriced = true;
34
+ /** Set when an order's outcome / fill is unknown: the run stops and surfaces ORDER_PENDING. */
35
+ let pending = null;
23
36
  try {
24
37
  while (state.running) {
25
38
  // Check if we've reached order limit
@@ -32,41 +45,58 @@ export async function runDCA(adapter, params, jobId, log = console.log) {
32
45
  log(`[DCA] Max runtime reached. Stopping.`);
33
46
  break;
34
47
  }
35
- // Check price limit
48
+ // Check price limit — the order goes out only when the price is KNOWN to be within it:
49
+ // a failed read / missing market / non-numeric price skips this order (never sent blind).
36
50
  if (params.priceLimit) {
51
+ let price = null;
52
+ let why = "";
37
53
  try {
38
54
  const markets = await adapter.getMarkets();
39
- const market = markets.find(m => m.symbol.toUpperCase() === symbol.toUpperCase());
40
- if (market) {
41
- const price = parseFloat(market.markPrice);
42
- if (side === "buy" && price > params.priceLimit) {
43
- log(`[DCA] Price $${price.toFixed(2)} > limit $${params.priceLimit}. Skipping.`);
44
- await sleep(intervalSec * 1000);
45
- continue;
46
- }
47
- if (side === "sell" && price < params.priceLimit) {
48
- log(`[DCA] Price $${price.toFixed(2)} < limit $${params.priceLimit}. Skipping.`);
49
- await sleep(intervalSec * 1000);
50
- continue;
51
- }
55
+ const market = markets.find(m => symbolMatch(m.symbol, symbol));
56
+ if (market === undefined)
57
+ why = `${symbol} not listed on ${adapter.name}`;
58
+ else {
59
+ const p = parseFloat(market.markPrice);
60
+ if (Number.isFinite(p) && p > 0)
61
+ price = p;
62
+ else
63
+ why = `mark price "${market.markPrice}" is not a price`;
52
64
  }
53
65
  }
54
- catch {
55
- // non-critical, proceed with order
66
+ catch (e) {
67
+ why = `price read failed: ${e instanceof Error ? e.message : String(e)}`;
68
+ }
69
+ const skip = price === null
70
+ ? `[DCA] Price unknown (${why}) — cannot check the $${params.priceLimit} limit. Skipping this order.`
71
+ : side === "buy" && price > params.priceLimit ? `[DCA] Price $${price.toFixed(2)} > limit $${params.priceLimit}. Skipping.`
72
+ : side === "sell" && price < params.priceLimit ? `[DCA] Price $${price.toFixed(2)} < limit $${params.priceLimit}. Skipping.`
73
+ : null;
74
+ if (skip !== null) {
75
+ log(skip);
76
+ await sleep(intervalSec * 1000);
77
+ continue;
56
78
  }
57
79
  }
58
80
  // Place market order
59
81
  try {
60
- const result = await adapter.marketOrder(symbol, side, String(amountPerOrder));
82
+ const result = await adapter.marketOrder(symbol, side, String(amountPerOrder), { reduceOnly: false, slippage: resolveAdapterSlippage(adapter, null) });
61
83
  state.ordersPlaced++;
62
- state.totalFilled += amountPerOrder;
63
- const fillPrice = Number(result?.price ?? result?.avg_price ?? result?.fill_price ?? 0);
64
- if (fillPrice > 0) {
65
- state.totalCost += amountPerOrder * fillPrice;
66
- state.avgPrice = state.totalCost / state.totalFilled;
84
+ // Filled size: the venue-reported fill only (IOC outcome / OrderResult.filledSize) — never the size sent.
85
+ const filled = filledOf(adapter, result);
86
+ if (filled === null) {
87
+ pending = pendingStop("dca", adapter, symbol, `DCA ${side} ${amountPerOrder} ${symbol} on ${adapter.name}: accepted but the venue reported no fill size`, log);
88
+ break;
67
89
  }
90
+ state.totalFilled = Number(venueRateString(state.totalFilled + filled, "DCA filled total")); // sum without float noise
91
+ // The venue-reported average fill only: the result's `price` is the protection price that was sent.
92
+ const fillPrice = reportedAvgFillPrice(result);
93
+ if (fillPrice === null)
94
+ everyOrderPriced = false;
95
+ else
96
+ state.totalCost += filled * Number(fillPrice);
97
+ state.avgPrice = everyOrderPriced && state.totalFilled > 0 ? Number(formatComputedPrice(state.totalCost / state.totalFilled)) : null;
68
98
  const progress = totalOrders > 0 ? ` (${state.ordersPlaced}/${totalOrders})` : "";
69
- log(`[DCA] Order #${state.ordersPlaced}${progress}: ${side} ${amountPerOrder} ${symbol}${state.avgPrice > 0 ? ` @ $${state.avgPrice.toFixed(2)} avg` : ""}`);
99
+ log(`[DCA] Order #${state.ordersPlaced}${progress}: ${side} ${filled} ${symbol}${fillPrice !== null ? ` @ $${fillPrice}` : " (fill price not reported)"}${state.avgPrice !== null ? `, avg $${state.avgPrice}` : ""}`);
70
100
  // Update job state
71
101
  if (jobId) {
72
102
  updateJobState(jobId, {
@@ -81,8 +111,13 @@ export async function runDCA(adapter, params, jobId, log = console.log) {
81
111
  }
82
112
  }
83
113
  catch (err) {
84
- state.errors++;
85
114
  const msg = err instanceof Error ? err.message : String(err);
115
+ if (isOutcomeUnknown(err)) {
116
+ // Unknown outcome: never resubmitted — the run stops and the symbol is quarantined.
117
+ pending = pendingStop("dca", adapter, symbol, `DCA ${side} ${amountPerOrder} ${symbol} on ${adapter.name}: outcome unknown (${msg})`, log);
118
+ break;
119
+ }
120
+ state.errors++;
86
121
  log(`[DCA] Order error: ${msg}`);
87
122
  if (state.errors > 10 && state.errors > state.ordersPlaced) {
88
123
  log(`[DCA] Too many errors. Stopping.`);
@@ -100,7 +135,12 @@ export async function runDCA(adapter, params, jobId, log = console.log) {
100
135
  process.removeListener("SIGTERM", shutdown);
101
136
  }
102
137
  const runtime = Math.floor((Date.now() - state.startedAt) / 1000);
103
- log(`[DCA] Done. ${state.ordersPlaced} orders, ${state.totalFilled} filled, avg $${state.avgPrice.toFixed(2)}, ${state.errors} errors, ${runtime}s`);
138
+ log(`[DCA] Done. ${state.ordersPlaced} orders, ${state.totalFilled} filled, avg ${state.avgPrice !== null ? `$${state.avgPrice}` : "not reported by the venue"}, ${state.errors} errors, ${runtime}s`);
139
+ if (pending !== null) {
140
+ if (jobId)
141
+ updateJobState(jobId, { status: "error", result: { error: pending.message, ordersPlaced: state.ordersPlaced, totalFilled: state.totalFilled, avgPrice: state.avgPrice, runtime } });
142
+ throw pending;
143
+ }
104
144
  if (jobId) {
105
145
  updateJobState(jobId, {
106
146
  status: "done",
@@ -1,4 +1,4 @@
1
- import type { ExchangeAdapter } from "../exchanges/index.js";
1
+ import { type ExchangeAdapter } from "../exchanges/index.js";
2
2
  export interface FundingArbParams {
3
3
  minSpread: number;
4
4
  closeSpread: number;
@@ -12,4 +12,14 @@ export interface FundingArbParams {
12
12
  rebalanceThreshold?: number;
13
13
  maxDrawdown?: number;
14
14
  }
15
+ /**
16
+ * `perp strategy funding-arb` loop. With autoExecute (design §12.3):
17
+ * - entry = openPerpPairVerified (long, then the short sized to the long's VERIFIED fill;
18
+ * definite short failure → reduce-only rollback; unknown outcome → quarantine). A lot-size
19
+ * rejection is never resubmitted blindly: the long is re-sized only when it was definitely
20
+ * not placed; a rejected short is placed once at the lot-floored verified long fill;
21
+ * - close / max-drawdown close = closePerpPairVerified with the venue position size strings
22
+ * (reduce-only, verified, one residual close, quarantine on unknown / unhedged);
23
+ * - quarantined symbols (src/bot/quarantine.ts) are never entered or closed.
24
+ */
15
25
  export declare function runFundingArb(adapters: Map<string, ExchangeAdapter>, params: FundingArbParams, jobId?: string, log?: (msg: string) => void): Promise<void>;