perp-cli 0.3.3

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,305 @@
1
+ import chalk from "chalk";
2
+ import { printJson, jsonOk } from "../utils.js";
3
+ import { fetchPacificaPricesRaw, parsePacificaRaw, fetchHyperliquidAllMidsRaw, fetchLighterOrderBookDetailsRaw, } from "../shared-api.js";
4
+ async function fetchAllPrices() {
5
+ const [pacRes, hlRes, ltRes] = await Promise.all([
6
+ fetchPacificaPricesRaw(),
7
+ fetchHyperliquidAllMidsRaw(),
8
+ fetchLighterOrderBookDetailsRaw(),
9
+ ]);
10
+ const { prices: pacPrices } = parsePacificaRaw(pacRes);
11
+ const hlPrices = new Map();
12
+ if (hlRes && typeof hlRes === "object" && !Array.isArray(hlRes)) {
13
+ for (const [symbol, price] of Object.entries(hlRes)) {
14
+ const p = Number(price);
15
+ if (p > 0)
16
+ hlPrices.set(symbol, p);
17
+ }
18
+ }
19
+ const ltPrices = new Map();
20
+ if (ltRes) {
21
+ const details = (ltRes.order_book_details ?? []);
22
+ for (const m of details) {
23
+ const sym = String(m.symbol ?? "").replace(/_USDC$/, "");
24
+ const price = Number(m.last_trade_price ?? m.mark_price ?? 0);
25
+ if (sym && price > 0)
26
+ ltPrices.set(sym, price);
27
+ }
28
+ }
29
+ const allSymbols = new Set([...pacPrices.keys(), ...hlPrices.keys(), ...ltPrices.keys()]);
30
+ const snapshots = [];
31
+ for (const sym of allSymbols) {
32
+ const pac = pacPrices.get(sym) ?? null;
33
+ const hl = hlPrices.get(sym) ?? null;
34
+ const lt = ltPrices.get(sym) ?? null;
35
+ // Need at least 2 exchanges
36
+ const available = [];
37
+ if (pac !== null)
38
+ available.push({ ex: "PAC", price: pac });
39
+ if (hl !== null)
40
+ available.push({ ex: "HL", price: hl });
41
+ if (lt !== null)
42
+ available.push({ ex: "LT", price: lt });
43
+ if (available.length < 2)
44
+ continue;
45
+ available.sort((a, b) => a.price - b.price);
46
+ const cheapest = available[0];
47
+ const expensive = available[available.length - 1];
48
+ const maxGap = expensive.price - cheapest.price;
49
+ const mid = (cheapest.price + expensive.price) / 2;
50
+ const maxGapPct = mid > 0 ? (maxGap / mid) * 100 : 0;
51
+ snapshots.push({
52
+ symbol: sym,
53
+ pacPrice: pac,
54
+ hlPrice: hl,
55
+ ltPrice: lt,
56
+ maxGap,
57
+ maxGapPct,
58
+ cheapest: cheapest.ex,
59
+ expensive: expensive.ex,
60
+ });
61
+ }
62
+ return snapshots.sort((a, b) => b.maxGapPct - a.maxGapPct);
63
+ }
64
+ function formatPrice(p) {
65
+ if (p >= 1000)
66
+ return p.toFixed(2);
67
+ if (p >= 1)
68
+ return p.toFixed(4);
69
+ return p.toFixed(6);
70
+ }
71
+ function printGapTable(snapshots, minGap) {
72
+ const filtered = snapshots.filter((s) => s.maxGapPct >= minGap);
73
+ if (filtered.length === 0) {
74
+ console.log(chalk.gray(`\n No gaps above ${minGap}%\n`));
75
+ return;
76
+ }
77
+ console.log(chalk.cyan.bold("\n Symbol Pacifica Hyperliquid Lighter Gap($) Gap(%) Buy/Sell\n"));
78
+ for (const s of filtered) {
79
+ const gapColor = s.maxGapPct >= 0.5
80
+ ? chalk.red.bold
81
+ : s.maxGapPct >= 0.1
82
+ ? chalk.yellow
83
+ : chalk.gray;
84
+ const fmtP = (p) => p !== null ? `$${formatPrice(p).padEnd(16)}` : chalk.gray("-".padEnd(17));
85
+ console.log(` ${chalk.white.bold(s.symbol.padEnd(10))} ` +
86
+ `${fmtP(s.pacPrice)} ` +
87
+ `${fmtP(s.hlPrice)} ` +
88
+ `${fmtP(s.ltPrice)} ` +
89
+ `${gapColor("$" + s.maxGap.toFixed(4).padEnd(10))} ` +
90
+ `${gapColor(s.maxGapPct.toFixed(4).padEnd(8) + "%")} ` +
91
+ `${chalk.green(s.cheapest)}→${chalk.red(s.expensive)}`);
92
+ }
93
+ const avgGap = filtered.reduce((sum, s) => sum + s.maxGapPct, 0) / filtered.length;
94
+ const top = filtered[0];
95
+ console.log(chalk.gray(`\n ${filtered.length} pairs | Avg gap: ${avgGap.toFixed(4)}% | Max: ${top.symbol} ${top.maxGapPct.toFixed(4)}%\n`));
96
+ }
97
+ export function registerGapCommands(program, isJson) {
98
+ const gap = program
99
+ .command("gap")
100
+ .description("Cross-exchange price gap monitoring (Pacifica vs Hyperliquid vs Lighter)");
101
+ // ── gap show (default) ──
102
+ gap
103
+ .command("show", { isDefault: true })
104
+ .description("Show current price gaps between exchanges")
105
+ .option("--min <pct>", "Minimum gap % to display", "0.01")
106
+ .option("--symbol <sym>", "Filter by symbol (e.g. BTC, ETH)")
107
+ .option("--top <n>", "Show top N gaps only")
108
+ .action(async (opts) => {
109
+ const minGap = parseFloat(opts.min);
110
+ if (!isJson())
111
+ console.log(chalk.cyan("\n Fetching prices from Pacifica, Hyperliquid & Lighter...\n"));
112
+ let snapshots = await fetchAllPrices();
113
+ if (opts.symbol) {
114
+ const sym = opts.symbol.toUpperCase();
115
+ snapshots = snapshots.filter((s) => s.symbol.includes(sym));
116
+ }
117
+ if (opts.top) {
118
+ snapshots = snapshots.slice(0, parseInt(opts.top));
119
+ }
120
+ if (isJson())
121
+ return printJson(jsonOk(snapshots.filter((s) => s.maxGapPct >= minGap)));
122
+ printGapTable(snapshots, minGap);
123
+ });
124
+ // ── gap watch ── (live monitoring)
125
+ gap
126
+ .command("watch")
127
+ .description("Live-monitor price gaps (auto-refresh)")
128
+ .option("--min <pct>", "Minimum gap % to display", "0.05")
129
+ .option("--interval <seconds>", "Refresh interval", "5")
130
+ .option("--symbol <sym>", "Filter by symbol")
131
+ .option("--beep", "Beep on gaps above 0.5%")
132
+ .action(async (opts) => {
133
+ const minGap = parseFloat(opts.min);
134
+ const intervalMs = parseInt(opts.interval) * 1000;
135
+ const beep = !!opts.beep;
136
+ if (!isJson()) {
137
+ console.log(chalk.cyan.bold("\n Price Gap Monitor"));
138
+ console.log(` Min gap: ${minGap}%`);
139
+ console.log(` Interval: ${opts.interval}s`);
140
+ if (opts.symbol)
141
+ console.log(` Symbol: ${opts.symbol.toUpperCase()}`);
142
+ console.log(chalk.gray(" Ctrl+C to stop\n"));
143
+ }
144
+ const cycle = async () => {
145
+ try {
146
+ let snapshots = await fetchAllPrices();
147
+ if (opts.symbol) {
148
+ const sym = opts.symbol.toUpperCase();
149
+ snapshots = snapshots.filter((s) => s.symbol.includes(sym));
150
+ }
151
+ // Clear screen for live view
152
+ process.stdout.write("\x1B[2J\x1B[0f");
153
+ const now = new Date().toLocaleTimeString();
154
+ console.log(chalk.cyan.bold(` Price Gap Monitor`) +
155
+ chalk.gray(` ${now} (refresh: ${opts.interval}s)\n`));
156
+ printGapTable(snapshots, minGap);
157
+ // Alert on large gaps
158
+ const bigGaps = snapshots.filter((s) => s.maxGapPct >= 0.5);
159
+ if (bigGaps.length > 0) {
160
+ console.log(chalk.red.bold(` !! ${bigGaps.length} large gap(s): ` +
161
+ bigGaps
162
+ .map((s) => `${s.symbol}(${s.maxGapPct.toFixed(3)}%)`)
163
+ .join(", ")));
164
+ if (beep)
165
+ process.stdout.write("\x07");
166
+ }
167
+ }
168
+ catch (err) {
169
+ console.error(chalk.gray(` Error: ${err instanceof Error ? err.message : String(err)}`));
170
+ }
171
+ };
172
+ await cycle();
173
+ setInterval(cycle, intervalMs);
174
+ await new Promise(() => { }); // keep alive
175
+ });
176
+ // ── gap history ── (track gap over time for a symbol)
177
+ gap
178
+ .command("track")
179
+ .description("Track a symbol's gap over time and print stats")
180
+ .requiredOption("-s, --symbol <sym>", "Symbol to track (e.g. BTC)")
181
+ .option("--duration <minutes>", "How long to track (minutes)", "10")
182
+ .option("--interval <seconds>", "Sample interval", "10")
183
+ .action(async (opts) => {
184
+ const symbol = opts.symbol.toUpperCase();
185
+ const durationMs = parseInt(opts.duration) * 60 * 1000;
186
+ const intervalMs = parseInt(opts.interval) * 1000;
187
+ const endTime = Date.now() + durationMs;
188
+ const samples = [];
189
+ if (!isJson()) {
190
+ console.log(chalk.cyan.bold(`\n Tracking ${symbol} price gap\n`));
191
+ console.log(` Duration: ${opts.duration} min`);
192
+ console.log(` Interval: ${opts.interval}s`);
193
+ console.log(chalk.gray(" Collecting samples...\n"));
194
+ }
195
+ const sample = async () => {
196
+ const snapshots = await fetchAllPrices();
197
+ const s = snapshots.find((x) => x.symbol === symbol);
198
+ if (!s) {
199
+ console.log(chalk.gray(` ${new Date().toLocaleTimeString()} — ${symbol} not found on both exchanges`));
200
+ return;
201
+ }
202
+ samples.push({
203
+ time: new Date().toLocaleTimeString(),
204
+ gap: s.maxGap,
205
+ gapPct: s.maxGapPct,
206
+ direction: `${s.cheapest}→${s.expensive}`,
207
+ });
208
+ const gapColor = s.maxGapPct >= 0.1 ? chalk.yellow : chalk.gray;
209
+ const parts = [`PAC: ${s.pacPrice !== null ? "$" + formatPrice(s.pacPrice) : "-"}`];
210
+ parts.push(`HL: ${s.hlPrice !== null ? "$" + formatPrice(s.hlPrice) : "-"}`);
211
+ parts.push(`LT: ${s.ltPrice !== null ? "$" + formatPrice(s.ltPrice) : "-"}`);
212
+ console.log(` ${chalk.white(s.symbol.padEnd(6))} ` +
213
+ `${parts.join(" ")} ` +
214
+ `${gapColor(`${s.maxGapPct.toFixed(4)}%`)} ${s.cheapest}→${s.expensive}`);
215
+ };
216
+ await sample();
217
+ const timer = setInterval(async () => {
218
+ if (Date.now() >= endTime) {
219
+ clearInterval(timer);
220
+ printTrackSummary(symbol, samples);
221
+ return;
222
+ }
223
+ await sample();
224
+ }, intervalMs);
225
+ await new Promise((resolve) => {
226
+ setTimeout(resolve, durationMs + 1000);
227
+ });
228
+ });
229
+ // ── gap alert ── (one-shot: notify when gap exceeds threshold)
230
+ gap
231
+ .command("alert")
232
+ .description("Wait until a symbol's gap exceeds a threshold, then exit")
233
+ .requiredOption("-s, --symbol <sym>", "Symbol to watch")
234
+ .requiredOption("--above <pct>", "Gap % threshold to trigger")
235
+ .option("--interval <seconds>", "Check interval", "5")
236
+ .action(async (opts) => {
237
+ const symbol = opts.symbol.toUpperCase();
238
+ const threshold = parseFloat(opts.above);
239
+ const intervalMs = parseInt(opts.interval) * 1000;
240
+ if (!isJson())
241
+ console.log(chalk.cyan(`\n Waiting for ${symbol} gap > ${threshold}%...\n`));
242
+ const check = async () => {
243
+ const snapshots = await fetchAllPrices();
244
+ const s = snapshots.find((x) => x.symbol === symbol);
245
+ if (!s)
246
+ return false;
247
+ const now = new Date().toLocaleTimeString();
248
+ if (!isJson())
249
+ console.log(chalk.gray(` ${now} ${symbol} gap: ${s.maxGapPct.toFixed(4)}%`));
250
+ if (s.maxGapPct >= threshold) {
251
+ if (isJson()) {
252
+ printJson(jsonOk(s));
253
+ }
254
+ else {
255
+ console.log(chalk.green.bold(`\n TRIGGERED! ${symbol} gap: ${s.maxGapPct.toFixed(4)}% (> ${threshold}%)`));
256
+ const lines = [];
257
+ if (s.pacPrice !== null)
258
+ lines.push(` Pacifica: $${formatPrice(s.pacPrice)}`);
259
+ if (s.hlPrice !== null)
260
+ lines.push(` Hyperliquid: $${formatPrice(s.hlPrice)}`);
261
+ if (s.ltPrice !== null)
262
+ lines.push(` Lighter: $${formatPrice(s.ltPrice)}`);
263
+ lines.push(` Gap: $${s.maxGap.toFixed(4)} (${s.cheapest}→${s.expensive})`);
264
+ console.log(lines.join("\n") + "\n");
265
+ }
266
+ return true;
267
+ }
268
+ return false;
269
+ };
270
+ if (await check())
271
+ return;
272
+ await new Promise((resolve) => {
273
+ const timer = setInterval(async () => {
274
+ if (await check()) {
275
+ clearInterval(timer);
276
+ resolve();
277
+ }
278
+ }, intervalMs);
279
+ });
280
+ });
281
+ }
282
+ function printTrackSummary(symbol, samples) {
283
+ if (samples.length === 0) {
284
+ console.log(chalk.gray("\n No samples collected.\n"));
285
+ return;
286
+ }
287
+ const gaps = samples.map((s) => s.gapPct);
288
+ const avg = gaps.reduce((a, b) => a + b, 0) / gaps.length;
289
+ const max = Math.max(...gaps);
290
+ const min = Math.min(...gaps);
291
+ const maxSample = samples[gaps.indexOf(max)];
292
+ // Count direction frequencies
293
+ const dirCounts = new Map();
294
+ for (const s of samples) {
295
+ dirCounts.set(s.direction, (dirCounts.get(s.direction) ?? 0) + 1);
296
+ }
297
+ console.log(chalk.cyan.bold(`\n ${symbol} Gap Summary (${samples.length} samples)\n`));
298
+ console.log(` Avg gap: ${avg.toFixed(4)}%`);
299
+ console.log(` Max gap: ${max.toFixed(4)}% at ${maxSample.time}`);
300
+ console.log(` Min gap: ${min.toFixed(4)}%`);
301
+ for (const [dir, count] of [...dirCounts.entries()].sort((a, b) => b[1] - a[1])) {
302
+ console.log(` ${dir.padEnd(10)} ${count} times (${((count / samples.length) * 100).toFixed(0)}%)`);
303
+ }
304
+ console.log();
305
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerHealthCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,67 @@
1
+ import chalk from "chalk";
2
+ import { printJson, jsonOk, makeTable } from "../utils.js";
3
+ import { pingPacifica, pingHyperliquid, pingLighter } from "../shared-api.js";
4
+ async function checkExchangeHealth(name, fn) {
5
+ const start = Date.now();
6
+ try {
7
+ await fn();
8
+ return { exchange: name, status: "ok", latency_ms: Date.now() - start };
9
+ }
10
+ catch (err) {
11
+ return {
12
+ exchange: name,
13
+ status: "down",
14
+ latency_ms: Date.now() - start,
15
+ error: err instanceof Error ? err.message : String(err),
16
+ };
17
+ }
18
+ }
19
+ export function registerHealthCommands(program, isJson) {
20
+ program
21
+ .command("health")
22
+ .description("Check exchange API connectivity and latency")
23
+ .action(async () => {
24
+ const [pacPing, hlPing, ltPing] = await Promise.all([
25
+ pingPacifica(),
26
+ pingHyperliquid(),
27
+ pingLighter(),
28
+ ]);
29
+ const toPingResult = (name, p) => ({
30
+ exchange: name,
31
+ status: p.ok ? "ok" : "down",
32
+ latency_ms: p.latencyMs,
33
+ error: p.ok ? undefined : `HTTP ${p.status}`,
34
+ });
35
+ const checks = [
36
+ toPingResult("pacifica", pacPing),
37
+ toPingResult("hyperliquid", hlPing),
38
+ toPingResult("lighter", ltPing),
39
+ ];
40
+ const allOk = checks.every(c => c.status === "ok");
41
+ if (isJson()) {
42
+ return printJson(jsonOk({ healthy: allOk, exchanges: checks }));
43
+ }
44
+ console.log(chalk.cyan.bold("\n Exchange Health Check\n"));
45
+ const rows = checks.map(c => {
46
+ const statusIcon = c.status === "ok"
47
+ ? chalk.green("OK")
48
+ : c.status === "degraded"
49
+ ? chalk.yellow("DEGRADED")
50
+ : chalk.red("DOWN");
51
+ const latency = c.latency_ms < 500
52
+ ? chalk.green(`${c.latency_ms}ms`)
53
+ : c.latency_ms < 2000
54
+ ? chalk.yellow(`${c.latency_ms}ms`)
55
+ : chalk.red(`${c.latency_ms}ms`);
56
+ return [
57
+ chalk.white.bold(c.exchange),
58
+ statusIcon,
59
+ latency,
60
+ c.error ? chalk.red(c.error) : chalk.gray("-"),
61
+ ];
62
+ });
63
+ console.log(makeTable(["Exchange", "Status", "Latency", "Error"], rows));
64
+ const overall = allOk ? chalk.green("ALL HEALTHY") : chalk.red("ISSUES DETECTED");
65
+ console.log(`\n Overall: ${overall}\n`);
66
+ });
67
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerHistoryCommands(program: Command, isJson: () => boolean): void;
@@ -0,0 +1,235 @@
1
+ import chalk from "chalk";
2
+ import { printJson, jsonOk, makeTable, formatUsd, formatPnl, withJsonErrors } from "../utils.js";
3
+ import { readExecutionLog, getExecutionStats, pruneExecutionLog } from "../execution-log.js";
4
+ import { readPositionHistory, getPositionStats } from "../position-history.js";
5
+ export function registerHistoryCommands(program, isJson) {
6
+ const history = program.command("history").description("Execution log & audit trail");
7
+ // ── history list ──
8
+ history
9
+ .command("list")
10
+ .description("Show recent executions")
11
+ .option("-n, --limit <n>", "Number of records to show", "20")
12
+ .option("-e, --exchange <exchange>", "Filter by exchange")
13
+ .option("-s, --symbol <symbol>", "Filter by symbol")
14
+ .option("-t, --type <type>", "Filter by type (market_order, limit_order, arb_entry, etc.)")
15
+ .option("--since <date>", "Show records since date (ISO format or relative like '24h', '7d')")
16
+ .option("--dry-run-only", "Show only dry-run simulations")
17
+ .action(async (opts) => {
18
+ await withJsonErrors(isJson(), async () => {
19
+ // Parse relative dates
20
+ let since = opts.since;
21
+ if (since) {
22
+ const match = since.match(/^(\d+)(h|d|w)$/);
23
+ if (match) {
24
+ const [, num, unit] = match;
25
+ const ms = { h: 3600000, d: 86400000, w: 604800000 }[unit] ?? 86400000;
26
+ since = new Date(Date.now() - parseInt(num) * ms).toISOString();
27
+ }
28
+ }
29
+ const records = readExecutionLog({
30
+ limit: parseInt(opts.limit),
31
+ exchange: opts.exchange,
32
+ symbol: opts.symbol,
33
+ type: opts.type,
34
+ since,
35
+ dryRunOnly: opts.dryRunOnly,
36
+ });
37
+ if (isJson()) {
38
+ return printJson(jsonOk(records));
39
+ }
40
+ if (records.length === 0) {
41
+ console.log(chalk.gray("\n No execution records found.\n"));
42
+ return;
43
+ }
44
+ console.log(chalk.cyan.bold(`\n Execution History (${records.length} records)\n`));
45
+ const rows = records.map(r => {
46
+ const statusColor = r.status === "success"
47
+ ? chalk.green
48
+ : r.status === "simulated"
49
+ ? chalk.blue
50
+ : chalk.red;
51
+ const sideColor = r.side === "buy" || r.side === "long" ? chalk.green : chalk.red;
52
+ const time = new Date(r.timestamp).toLocaleString();
53
+ const notional = r.notional ? `$${formatUsd(r.notional)}` : r.price ? `$${formatUsd(Number(r.price) * Number(r.size))}` : "-";
54
+ return [
55
+ chalk.gray(time),
56
+ chalk.white(r.exchange),
57
+ r.type.replace(/_/g, " "),
58
+ chalk.white.bold(r.symbol),
59
+ sideColor(r.side),
60
+ r.size,
61
+ notional,
62
+ statusColor(r.status),
63
+ r.dryRun ? chalk.blue("DRY") : "",
64
+ ];
65
+ });
66
+ console.log(makeTable(["Time", "Exchange", "Type", "Symbol", "Side", "Size", "Notional", "Status", ""], rows));
67
+ });
68
+ });
69
+ // ── history stats ──
70
+ history
71
+ .command("stats")
72
+ .description("Execution statistics summary")
73
+ .option("--since <date>", "Stats since date (ISO or relative: 24h, 7d, 30d)")
74
+ .action(async (opts) => {
75
+ await withJsonErrors(isJson(), async () => {
76
+ let since = opts.since;
77
+ if (since) {
78
+ const match = since.match(/^(\d+)(h|d|w)$/);
79
+ if (match) {
80
+ const [, num, unit] = match;
81
+ const ms = { h: 3600000, d: 86400000, w: 604800000 }[unit] ?? 86400000;
82
+ since = new Date(Date.now() - parseInt(num) * ms).toISOString();
83
+ }
84
+ }
85
+ const stats = getExecutionStats(since);
86
+ if (isJson()) {
87
+ return printJson(jsonOk(stats));
88
+ }
89
+ console.log(chalk.cyan.bold("\n Execution Stats\n"));
90
+ console.log(` Total Trades: ${stats.totalTrades}`);
91
+ console.log(` Success Rate: ${stats.successRate.toFixed(1)}%`);
92
+ if (Object.keys(stats.byExchange).length > 0) {
93
+ console.log(chalk.white.bold("\n By Exchange:"));
94
+ for (const [ex, count] of Object.entries(stats.byExchange)) {
95
+ console.log(` ${ex.padEnd(14)} ${count}`);
96
+ }
97
+ }
98
+ if (Object.keys(stats.byType).length > 0) {
99
+ console.log(chalk.white.bold("\n By Type:"));
100
+ for (const [type, count] of Object.entries(stats.byType)) {
101
+ console.log(` ${type.replace(/_/g, " ").padEnd(14)} ${count}`);
102
+ }
103
+ }
104
+ if (stats.recentErrors.length > 0) {
105
+ console.log(chalk.red.bold("\n Recent Errors:"));
106
+ for (const err of stats.recentErrors) {
107
+ console.log(` ${chalk.red(err)}`);
108
+ }
109
+ }
110
+ console.log();
111
+ });
112
+ });
113
+ // ── history positions ──
114
+ history
115
+ .command("positions")
116
+ .description("Show position history (from event stream logging)")
117
+ .option("-n, --limit <n>", "Number of records to show", "20")
118
+ .option("-e, --exchange <exchange>", "Filter by exchange")
119
+ .option("-s, --symbol <symbol>", "Filter by symbol")
120
+ .option("--status <status>", "Filter by status (open, closed, updated)")
121
+ .option("--since <date>", "Show records since date (ISO format or relative like '24h', '7d')")
122
+ .option("--stats", "Show aggregate position stats instead of list")
123
+ .action(async (opts) => {
124
+ await withJsonErrors(isJson(), async () => {
125
+ // Parse relative dates
126
+ let since = opts.since;
127
+ if (since) {
128
+ const match = since.match(/^(\d+)(h|d|w)$/);
129
+ if (match) {
130
+ const [, num, unit] = match;
131
+ const ms = { h: 3600000, d: 86400000, w: 604800000 }[unit] ?? 86400000;
132
+ since = new Date(Date.now() - parseInt(num) * ms).toISOString();
133
+ }
134
+ }
135
+ // Stats mode
136
+ if (opts.stats) {
137
+ const stats = getPositionStats({ exchange: opts.exchange, since });
138
+ if (isJson()) {
139
+ return printJson(jsonOk(stats));
140
+ }
141
+ console.log(chalk.cyan.bold("\n Position Stats\n"));
142
+ console.log(` Total Trades: ${stats.totalTrades}`);
143
+ console.log(` Wins / Losses: ${chalk.green(String(stats.wins))} / ${chalk.red(String(stats.losses))}`);
144
+ console.log(` Win Rate: ${stats.winRate.toFixed(1)}%`);
145
+ console.log(` Total P&L: ${formatPnl(stats.totalPnl)}`);
146
+ console.log(` Avg P&L: ${formatPnl(stats.avgPnl)}`);
147
+ console.log(` Best Trade: ${formatPnl(stats.bestTrade)}`);
148
+ console.log(` Worst Trade: ${formatPnl(stats.worstTrade)}`);
149
+ if (stats.avgDuration > 0) {
150
+ const fmtDur = (ms) => {
151
+ if (ms < 60000)
152
+ return `${(ms / 1000).toFixed(0)}s`;
153
+ if (ms < 3600000)
154
+ return `${(ms / 60000).toFixed(1)}m`;
155
+ return `${(ms / 3600000).toFixed(1)}h`;
156
+ };
157
+ console.log(` Avg Duration: ${fmtDur(stats.avgDuration)}`);
158
+ console.log(` Longest Trade: ${fmtDur(stats.longestTrade)}`);
159
+ console.log(` Shortest Trade: ${fmtDur(stats.shortestTrade)}`);
160
+ }
161
+ if (Object.keys(stats.bySymbol).length > 0) {
162
+ console.log(chalk.white.bold("\n By Symbol:"));
163
+ for (const [sym, s] of Object.entries(stats.bySymbol)) {
164
+ console.log(` ${sym.padEnd(10)} ${s.trades} trades ${formatPnl(s.pnl)} ${s.winRate.toFixed(0)}% win`);
165
+ }
166
+ }
167
+ if (Object.keys(stats.byExchange).length > 0) {
168
+ console.log(chalk.white.bold("\n By Exchange:"));
169
+ for (const [ex, s] of Object.entries(stats.byExchange)) {
170
+ console.log(` ${ex.padEnd(14)} ${s.trades} trades ${formatPnl(s.pnl)}`);
171
+ }
172
+ }
173
+ console.log();
174
+ return;
175
+ }
176
+ // List mode
177
+ const records = readPositionHistory({
178
+ limit: parseInt(opts.limit),
179
+ exchange: opts.exchange,
180
+ symbol: opts.symbol,
181
+ status: opts.status,
182
+ since,
183
+ });
184
+ if (isJson()) {
185
+ return printJson(jsonOk(records));
186
+ }
187
+ if (records.length === 0) {
188
+ console.log(chalk.gray("\n No position records found. Use `perp stream events --log-positions` to start logging.\n"));
189
+ return;
190
+ }
191
+ console.log(chalk.cyan.bold(`\n Position History (${records.length} records)\n`));
192
+ const rows = records.map(r => {
193
+ const statusColor = r.status === "open"
194
+ ? chalk.yellow
195
+ : r.status === "closed"
196
+ ? chalk.green
197
+ : chalk.blue;
198
+ const sideColor = r.side === "long" ? chalk.green : chalk.red;
199
+ const time = new Date(r.updatedAt).toLocaleString();
200
+ const pnl = r.realizedPnl ? formatPnl(Number(r.realizedPnl)) : r.unrealizedPnl ? chalk.gray(formatPnl(Number(r.unrealizedPnl))) : "-";
201
+ const dur = r.duration
202
+ ? r.duration < 60000
203
+ ? `${(r.duration / 1000).toFixed(0)}s`
204
+ : r.duration < 3600000
205
+ ? `${(r.duration / 60000).toFixed(1)}m`
206
+ : `${(r.duration / 3600000).toFixed(1)}h`
207
+ : "-";
208
+ return [
209
+ chalk.gray(time),
210
+ chalk.white(r.exchange),
211
+ chalk.white.bold(r.symbol),
212
+ sideColor(r.side),
213
+ r.size,
214
+ `$${formatUsd(r.entryPrice)}`,
215
+ pnl,
216
+ dur,
217
+ statusColor(r.status),
218
+ ];
219
+ });
220
+ console.log(makeTable(["Time", "Exchange", "Symbol", "Side", "Size", "Entry", "P&L", "Duration", "Status"], rows));
221
+ });
222
+ });
223
+ // ── history prune ──
224
+ history
225
+ .command("prune")
226
+ .description("Remove old execution records")
227
+ .option("--keep-days <days>", "Keep records from last N days", "30")
228
+ .action(async (opts) => {
229
+ const pruned = pruneExecutionLog(parseInt(opts.keepDays));
230
+ if (isJson()) {
231
+ return printJson(jsonOk({ pruned }));
232
+ }
233
+ console.log(chalk.green(`\n Pruned ${pruned} old records (keeping last ${opts.keepDays} days).\n`));
234
+ });
235
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from "commander";
2
+ declare const ENV_FILE: string;
3
+ export { ENV_FILE, loadEnvFile, setEnvVar };
4
+ declare function loadEnvFile(): Record<string, string>;
5
+ declare function setEnvVar(key: string, value: string): void;
6
+ export declare const EXCHANGE_ENV_MAP: Record<string, {
7
+ envKey: string;
8
+ chain: "solana" | "evm";
9
+ label: string;
10
+ }>;
11
+ export declare function validateKey(chain: "solana" | "evm", key: string): Promise<{
12
+ valid: boolean;
13
+ address: string;
14
+ }>;
15
+ export declare function registerInitCommand(program: Command): void;