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,169 @@
1
+ import chalk from "chalk";
2
+ import { printJson, jsonOk, makeTable, formatUsd, formatPnl, withJsonErrors } from "../utils.js";
3
+ const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
4
+ async function fetchExchangeSnapshot(name, getAdapter) {
5
+ try {
6
+ const adapter = await getAdapter(name);
7
+ const [balance, positions, orders] = await Promise.all([
8
+ adapter.getBalance(),
9
+ adapter.getPositions(),
10
+ adapter.getOpenOrders(),
11
+ ]);
12
+ return {
13
+ exchange: name,
14
+ connected: true,
15
+ balance,
16
+ positions,
17
+ openOrders: orders.length,
18
+ };
19
+ }
20
+ catch (err) {
21
+ return {
22
+ exchange: name,
23
+ connected: false,
24
+ balance: null,
25
+ positions: [],
26
+ openOrders: 0,
27
+ error: err instanceof Error ? err.message : String(err),
28
+ };
29
+ }
30
+ }
31
+ function buildSummary(snapshots) {
32
+ let totalEquity = 0;
33
+ let totalAvailable = 0;
34
+ let totalMarginUsed = 0;
35
+ let totalUnrealizedPnl = 0;
36
+ let totalPositions = 0;
37
+ let totalOpenOrders = 0;
38
+ const allPositions = [];
39
+ for (const snap of snapshots) {
40
+ if (snap.balance) {
41
+ totalEquity += Number(snap.balance.equity);
42
+ totalAvailable += Number(snap.balance.available);
43
+ totalMarginUsed += Number(snap.balance.marginUsed);
44
+ totalUnrealizedPnl += Number(snap.balance.unrealizedPnl);
45
+ }
46
+ totalPositions += snap.positions.length;
47
+ totalOpenOrders += snap.openOrders;
48
+ for (const pos of snap.positions) {
49
+ allPositions.push({ ...pos, exchange: snap.exchange });
50
+ }
51
+ }
52
+ // Risk metrics
53
+ const marginUtilization = totalEquity > 0 ? (totalMarginUsed / totalEquity) * 100 : 0;
54
+ let largestPosition = null;
55
+ for (const pos of allPositions) {
56
+ const notional = Math.abs(Number(pos.size) * Number(pos.markPrice));
57
+ if (!largestPosition || notional > largestPosition.notional) {
58
+ largestPosition = { symbol: pos.symbol, exchange: pos.exchange, notional };
59
+ }
60
+ }
61
+ const exchangeConcentration = snapshots
62
+ .filter(s => s.balance && Number(s.balance.equity) > 0)
63
+ .map(s => ({
64
+ exchange: s.exchange,
65
+ pct: totalEquity > 0 ? (Number(s.balance.equity) / totalEquity) * 100 : 0,
66
+ }))
67
+ .sort((a, b) => b.pct - a.pct);
68
+ return {
69
+ totalEquity,
70
+ totalAvailable,
71
+ totalMarginUsed,
72
+ totalUnrealizedPnl,
73
+ totalPositions,
74
+ totalOpenOrders,
75
+ exchanges: snapshots,
76
+ positions: allPositions,
77
+ riskMetrics: {
78
+ marginUtilization,
79
+ largestPosition,
80
+ exchangeConcentration,
81
+ },
82
+ };
83
+ }
84
+ export function registerPortfolioCommands(program, getAdapterForExchange, isJson) {
85
+ program
86
+ .command("portfolio")
87
+ .description("Unified cross-exchange portfolio: balances, positions, risk")
88
+ .option("--exchange <exchanges>", "Comma-separated exchanges to include (default: all)")
89
+ .action(async (opts) => {
90
+ await withJsonErrors(isJson(), async () => {
91
+ const exchanges = opts.exchange
92
+ ? opts.exchange.split(",").map(e => e.trim())
93
+ : [...EXCHANGES];
94
+ const snapshots = await Promise.all(exchanges.map(ex => fetchExchangeSnapshot(ex, getAdapterForExchange)));
95
+ const summary = buildSummary(snapshots);
96
+ if (isJson()) {
97
+ return printJson(jsonOk(summary));
98
+ }
99
+ // ── Header ──
100
+ console.log(chalk.cyan.bold("\n Cross-Exchange Portfolio\n"));
101
+ // ── Balance Summary ──
102
+ console.log(chalk.white.bold(" Balances"));
103
+ const balRows = snapshots.map(s => {
104
+ if (!s.connected) {
105
+ return [chalk.white(s.exchange), chalk.red("disconnected"), "-", "-", "-", chalk.gray(s.error ?? "")];
106
+ }
107
+ const b = s.balance;
108
+ return [
109
+ chalk.white.bold(s.exchange),
110
+ `$${formatUsd(b.equity)}`,
111
+ `$${formatUsd(b.available)}`,
112
+ `$${formatUsd(b.marginUsed)}`,
113
+ formatPnl(b.unrealizedPnl),
114
+ chalk.green("connected"),
115
+ ];
116
+ });
117
+ // Totals row
118
+ balRows.push([
119
+ chalk.cyan.bold("TOTAL"),
120
+ chalk.cyan.bold(`$${formatUsd(summary.totalEquity)}`),
121
+ chalk.cyan.bold(`$${formatUsd(summary.totalAvailable)}`),
122
+ chalk.cyan.bold(`$${formatUsd(summary.totalMarginUsed)}`),
123
+ formatPnl(summary.totalUnrealizedPnl),
124
+ "",
125
+ ]);
126
+ console.log(makeTable(["Exchange", "Equity", "Available", "Margin Used", "uPnL", "Status"], balRows));
127
+ // ── Positions ──
128
+ if (summary.positions.length > 0) {
129
+ console.log(chalk.white.bold("\n Open Positions"));
130
+ const posRows = summary.positions.map(p => {
131
+ const sideColor = p.side === "long" ? chalk.green : chalk.red;
132
+ const notional = Math.abs(Number(p.size) * Number(p.markPrice));
133
+ return [
134
+ chalk.white.bold(p.symbol),
135
+ chalk.gray(p.exchange),
136
+ sideColor(p.side.toUpperCase()),
137
+ p.size,
138
+ `$${formatUsd(p.entryPrice)}`,
139
+ `$${formatUsd(p.markPrice)}`,
140
+ formatPnl(p.unrealizedPnl),
141
+ `$${formatUsd(notional)}`,
142
+ `${p.leverage}x`,
143
+ ];
144
+ });
145
+ console.log(makeTable(["Symbol", "Exchange", "Side", "Size", "Entry", "Mark", "uPnL", "Notional", "Lev"], posRows));
146
+ }
147
+ else {
148
+ console.log(chalk.gray("\n No open positions.\n"));
149
+ }
150
+ // ── Risk Metrics ──
151
+ console.log(chalk.white.bold("\n Risk Metrics"));
152
+ const mu = summary.riskMetrics.marginUtilization;
153
+ const muColor = mu < 30 ? chalk.green : mu < 60 ? chalk.yellow : chalk.red;
154
+ console.log(` Margin Utilization: ${muColor(`${mu.toFixed(1)}%`)}`);
155
+ if (summary.riskMetrics.largestPosition) {
156
+ const lp = summary.riskMetrics.largestPosition;
157
+ console.log(` Largest Position: ${lp.symbol} on ${lp.exchange} ($${formatUsd(lp.notional)})`);
158
+ }
159
+ if (summary.riskMetrics.exchangeConcentration.length > 0) {
160
+ console.log(` Exchange Allocation:`);
161
+ for (const ec of summary.riskMetrics.exchangeConcentration) {
162
+ const bar = "\u2588".repeat(Math.round(ec.pct / 5)) + "\u2591".repeat(20 - Math.round(ec.pct / 5));
163
+ console.log(` ${ec.exchange.padEnd(12)} ${bar} ${ec.pct.toFixed(1)}%`);
164
+ }
165
+ }
166
+ console.log();
167
+ });
168
+ });
169
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ export declare function registerRebalanceCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -0,0 +1,293 @@
1
+ import chalk from "chalk";
2
+ import { formatUsd, makeTable, printJson, jsonOk } from "../utils.js";
3
+ import { fetchAllBalances, computeRebalancePlan, describeBridgeRoute, estimateMoveTime, } from "../rebalance.js";
4
+ import { EXCHANGE_TO_CHAIN, executeBestBridge } from "../bridge-engine.js";
5
+ import { loadPrivateKey, parseSolanaKeypair } from "../config.js";
6
+ export function registerRebalanceCommands(program, getAdapterForExchange, isJson) {
7
+ const rebalance = program.command("rebalance").description("Cross-exchange balance management");
8
+ // ── rebalance check ──
9
+ rebalance
10
+ .command("check")
11
+ .description("Show balances across all exchanges")
12
+ .option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
13
+ .action(async (opts) => {
14
+ const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
15
+ const adapters = new Map();
16
+ for (const name of exchangeNames) {
17
+ try {
18
+ adapters.set(name, await getAdapterForExchange(name));
19
+ }
20
+ catch {
21
+ // skip unavailable
22
+ }
23
+ }
24
+ if (adapters.size === 0) {
25
+ console.error(chalk.red("\n No exchanges available. Check credentials.\n"));
26
+ return;
27
+ }
28
+ if (!isJson())
29
+ console.log(chalk.cyan("\n Fetching balances across exchanges...\n"));
30
+ const snapshots = await fetchAllBalances(adapters);
31
+ if (isJson())
32
+ return printJson(jsonOk(snapshots));
33
+ const totalEquity = snapshots.reduce((s, e) => s + e.equity, 0);
34
+ const totalAvailable = snapshots.reduce((s, e) => s + e.available, 0);
35
+ const rows = snapshots.map((s) => {
36
+ const pct = totalEquity > 0 ? ((s.equity / totalEquity) * 100).toFixed(1) + "%" : "-";
37
+ return [
38
+ chalk.white.bold(s.exchange.padEnd(12)),
39
+ `$${formatUsd(s.equity)}`,
40
+ `$${formatUsd(s.available)}`,
41
+ `$${formatUsd(s.marginUsed)}`,
42
+ s.unrealizedPnl >= 0
43
+ ? chalk.green(`+$${formatUsd(s.unrealizedPnl)}`)
44
+ : chalk.red(`-$${formatUsd(Math.abs(s.unrealizedPnl))}`),
45
+ pct,
46
+ ];
47
+ });
48
+ console.log(makeTable(["Exchange", "Equity", "Available", "Margin", "uPnL", "Alloc%"], rows));
49
+ console.log(chalk.cyan.bold(" Totals"));
50
+ console.log(` Total Equity: $${formatUsd(totalEquity)}`);
51
+ console.log(` Total Available: $${formatUsd(totalAvailable)}`);
52
+ console.log(` Exchanges: ${snapshots.length}\n`);
53
+ });
54
+ // ── rebalance plan ──
55
+ rebalance
56
+ .command("plan")
57
+ .description("Calculate optimal rebalancing moves")
58
+ .option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
59
+ .option("--min-move <usd>", "Minimum move amount ($)", "50")
60
+ .option("--reserve <usd>", "Keep at least this much per exchange ($)", "20")
61
+ .action(async (opts) => {
62
+ const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
63
+ const adapters = new Map();
64
+ for (const name of exchangeNames) {
65
+ try {
66
+ adapters.set(name, await getAdapterForExchange(name));
67
+ }
68
+ catch {
69
+ // skip unavailable
70
+ }
71
+ }
72
+ if (adapters.size < 2) {
73
+ console.error(chalk.red("\n Need at least 2 exchanges for rebalancing.\n"));
74
+ return;
75
+ }
76
+ if (!isJson())
77
+ console.log(chalk.cyan("\n Computing rebalance plan...\n"));
78
+ const snapshots = await fetchAllBalances(adapters);
79
+ const plan = computeRebalancePlan(snapshots, {
80
+ minMove: parseFloat(opts.minMove),
81
+ reserve: parseFloat(opts.reserve),
82
+ });
83
+ if (isJson())
84
+ return printJson(jsonOk(plan));
85
+ // Show current state
86
+ const rows = plan.snapshots.map((s) => {
87
+ const target = plan.targetPerExchange;
88
+ const diff = s.available - target;
89
+ const diffStr = diff >= 0
90
+ ? chalk.green(`+$${formatUsd(diff)}`)
91
+ : chalk.red(`-$${formatUsd(Math.abs(diff))}`);
92
+ return [
93
+ chalk.white.bold(s.exchange),
94
+ `$${formatUsd(s.available)}`,
95
+ `$${formatUsd(target)}`,
96
+ diffStr,
97
+ ];
98
+ });
99
+ console.log(makeTable(["Exchange", "Available", "Target", "Diff"], rows));
100
+ if (plan.moves.length === 0) {
101
+ console.log(chalk.green(" Balanced — no moves needed.\n"));
102
+ return;
103
+ }
104
+ console.log(chalk.cyan.bold(" Proposed Moves\n"));
105
+ for (let i = 0; i < plan.moves.length; i++) {
106
+ const m = plan.moves[i];
107
+ console.log(chalk.white.bold(` Move ${i + 1}: $${m.amount} ${m.from} → ${m.to}`));
108
+ console.log(chalk.gray(` Route: ${describeBridgeRoute(m)}`));
109
+ console.log(chalk.gray(` Time: ${estimateMoveTime(m)}`));
110
+ console.log();
111
+ }
112
+ console.log(chalk.yellow(` To execute: perp rebalance execute --exchanges ${opts.exchanges}\n`));
113
+ });
114
+ // ── rebalance execute ──
115
+ rebalance
116
+ .command("execute")
117
+ .description("Execute rebalancing (withdraw → bridge → deposit)")
118
+ .option("--exchanges <list>", "Comma-separated exchanges", "pacifica,hyperliquid,lighter")
119
+ .option("--min-move <usd>", "Minimum move amount ($)", "50")
120
+ .option("--reserve <usd>", "Keep at least this much per exchange ($)", "20")
121
+ .option("--dry-run", "Show what would happen without executing")
122
+ .option("--withdraw-only", "Only execute withdrawals (manual bridge + deposit)")
123
+ .option("--auto-bridge", "Auto-bridge via deBridge DLN (withdraw → bridge in one step)")
124
+ .action(async (opts) => {
125
+ const exchangeNames = opts.exchanges.split(",").map((e) => e.trim());
126
+ const adapters = new Map();
127
+ for (const name of exchangeNames) {
128
+ try {
129
+ adapters.set(name, await getAdapterForExchange(name));
130
+ }
131
+ catch {
132
+ // skip
133
+ }
134
+ }
135
+ if (adapters.size < 2) {
136
+ console.error(chalk.red("\n Need at least 2 exchanges.\n"));
137
+ return;
138
+ }
139
+ const snapshots = await fetchAllBalances(adapters);
140
+ const plan = computeRebalancePlan(snapshots, {
141
+ minMove: parseFloat(opts.minMove),
142
+ reserve: parseFloat(opts.reserve),
143
+ });
144
+ if (plan.moves.length === 0) {
145
+ if (isJson())
146
+ return printJson(jsonOk({ status: "balanced", moves: [] }));
147
+ console.log(chalk.green("\n Already balanced — nothing to do.\n"));
148
+ return;
149
+ }
150
+ if (!isJson())
151
+ console.log(chalk.cyan.bold("\n Executing Rebalance\n"));
152
+ const moveResults = [];
153
+ for (const move of plan.moves) {
154
+ if (!isJson())
155
+ console.log(chalk.white.bold(` $${move.amount} ${move.from} → ${move.to}`));
156
+ if (opts.dryRun) {
157
+ if (!isJson())
158
+ console.log(chalk.yellow(" [DRY RUN] Skipped.\n"));
159
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "dry_run" });
160
+ continue;
161
+ }
162
+ // Step 1: Withdraw from source exchange
163
+ try {
164
+ if (!isJson())
165
+ console.log(chalk.gray(` Step 1: Withdrawing $${move.amount} from ${move.from}...`));
166
+ await executeWithdraw(move, adapters);
167
+ if (!isJson())
168
+ console.log(chalk.green(` Withdrawal submitted.`));
169
+ }
170
+ catch (err) {
171
+ console.error(chalk.red(` Withdraw failed: ${err instanceof Error ? err.message : err}`));
172
+ if (!isJson())
173
+ console.log(chalk.yellow(` Skipping this move.\n`));
174
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "withdraw_failed", error: err instanceof Error ? err.message : String(err) });
175
+ continue;
176
+ }
177
+ if (opts.withdrawOnly) {
178
+ if (!isJson()) {
179
+ console.log(chalk.yellow(` [WITHDRAW-ONLY] Bridge and deposit manually.`));
180
+ console.log(chalk.gray(` Route: ${describeBridgeRoute(move)}\n`));
181
+ }
182
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "withdrawn" });
183
+ continue;
184
+ }
185
+ // Step 2: Bridge
186
+ const srcChain = EXCHANGE_TO_CHAIN[move.from];
187
+ const dstChain = EXCHANGE_TO_CHAIN[move.to];
188
+ if (!srcChain || !dstChain || srcChain === dstChain) {
189
+ if (!isJson())
190
+ console.log(chalk.green(` Same chain — no bridge needed.\n`));
191
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "same_chain" });
192
+ continue;
193
+ }
194
+ if (opts.autoBridge) {
195
+ try {
196
+ if (!isJson())
197
+ console.log(chalk.gray(` Step 2: Bridging $${move.amount} (${srcChain} → ${dstChain})...`));
198
+ // Load keys for bridge
199
+ const { senderAddress, recipientAddress, signerKey, dstSignerKey } = await loadBridgeKeys(move.from, move.to, srcChain, dstChain);
200
+ const result = await executeBestBridge(srcChain, dstChain, move.amount, signerKey, senderAddress, recipientAddress, dstSignerKey);
201
+ if (!isJson()) {
202
+ console.log(chalk.green(` Bridge submitted! TX: ${result.txHash}`));
203
+ console.log(chalk.gray(` Provider: ${result.provider} | ~$${result.amountOut} arriving on ${dstChain}\n`));
204
+ }
205
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "bridged", txHash: result.txHash });
206
+ }
207
+ catch (err) {
208
+ console.error(chalk.red(` Bridge failed: ${err instanceof Error ? err.message : err}`));
209
+ if (!isJson())
210
+ console.log(chalk.yellow(` Manual fallback: perp bridge exchange --from ${move.from} --to ${move.to} --amount ${move.amount}\n`));
211
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "bridge_failed", error: err instanceof Error ? err.message : String(err) });
212
+ }
213
+ }
214
+ else {
215
+ if (!isJson()) {
216
+ console.log(chalk.gray(` Step 2: Bridge needed.`));
217
+ console.log(chalk.gray(` Run: perp bridge exchange --from ${move.from} --to ${move.to} --amount ${move.amount}`));
218
+ console.log();
219
+ }
220
+ moveResults.push({ from: move.from, to: move.to, amount: move.amount, status: "needs_bridge" });
221
+ }
222
+ }
223
+ if (isJson())
224
+ return printJson(jsonOk({ status: "executed", moves: moveResults }));
225
+ console.log(chalk.cyan(" Rebalance initiated. Monitor progress with 'perp rebalance check'.\n"));
226
+ });
227
+ }
228
+ /**
229
+ * Execute a withdrawal from a source exchange.
230
+ */
231
+ async function executeWithdraw(move, adapters) {
232
+ const adapter = adapters.get(move.from);
233
+ if (!adapter)
234
+ throw new Error(`No adapter for ${move.from}`);
235
+ switch (move.from) {
236
+ case "pacifica": {
237
+ const { PacificaAdapter } = await import("../exchanges/pacifica.js");
238
+ if (!(adapter instanceof PacificaAdapter))
239
+ throw new Error("Invalid adapter");
240
+ await adapter.sdk.withdraw({ amount: String(move.amount), dest_address: adapter.publicKey }, adapter.publicKey, adapter.signer);
241
+ break;
242
+ }
243
+ case "hyperliquid": {
244
+ const { HyperliquidAdapter } = await import("../exchanges/hyperliquid.js");
245
+ if (!(adapter instanceof HyperliquidAdapter))
246
+ throw new Error("Invalid adapter");
247
+ await adapter.withdraw(String(move.amount), adapter.address);
248
+ break;
249
+ }
250
+ case "lighter": {
251
+ const { LighterAdapter } = await import("../exchanges/lighter.js");
252
+ if (!(adapter instanceof LighterAdapter))
253
+ throw new Error("Invalid adapter");
254
+ await adapter.withdraw(move.amount);
255
+ break;
256
+ }
257
+ default:
258
+ throw new Error(`Withdraw not supported for ${move.from}`);
259
+ }
260
+ }
261
+ /**
262
+ * Load sender/recipient addresses and signer key for a bridge move.
263
+ */
264
+ async function loadBridgeKeys(srcExchange, dstExchange, srcChain, dstChain) {
265
+ let senderAddress;
266
+ let recipientAddress;
267
+ let signerKey;
268
+ let dstSignerKey;
269
+ // Source
270
+ if (srcChain === "solana") {
271
+ const pk = await loadPrivateKey("pacifica");
272
+ senderAddress = parseSolanaKeypair(pk).publicKey.toBase58();
273
+ signerKey = pk;
274
+ }
275
+ else {
276
+ const pk = await loadPrivateKey(srcExchange);
277
+ const { ethers } = await import("ethers");
278
+ senderAddress = new ethers.Wallet(pk).address;
279
+ signerKey = pk;
280
+ }
281
+ // Destination
282
+ if (dstChain === "solana") {
283
+ const pk = await loadPrivateKey("pacifica");
284
+ recipientAddress = parseSolanaKeypair(pk).publicKey.toBase58();
285
+ }
286
+ else {
287
+ const pk = await loadPrivateKey(dstExchange);
288
+ const { ethers } = await import("ethers");
289
+ recipientAddress = new ethers.Wallet(pk).address;
290
+ dstSignerKey = pk; // For auto receiveMessage
291
+ }
292
+ return { senderAddress, recipientAddress, signerKey, dstSignerKey };
293
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ export declare function registerRiskCommands(program: Command, getAdapterForExchange: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;
@@ -0,0 +1,169 @@
1
+ import chalk from "chalk";
2
+ import { printJson, jsonOk, makeTable, formatUsd, withJsonErrors } from "../utils.js";
3
+ import { loadRiskLimits, saveRiskLimits, assessRisk } from "../risk.js";
4
+ const EXCHANGES = ["pacifica", "hyperliquid", "lighter"];
5
+ export function registerRiskCommands(program, getAdapterForExchange, isJson) {
6
+ const risk = program.command("risk").description("Risk management and guardrails");
7
+ // ── risk status ──
8
+ risk
9
+ .command("status")
10
+ .description("Assess current risk across all exchanges")
11
+ .option("--exchange <exchanges>", "Comma-separated exchanges (default: all)")
12
+ .action(async (opts) => {
13
+ await withJsonErrors(isJson(), async () => {
14
+ const exchanges = opts.exchange
15
+ ? opts.exchange.split(",").map(e => e.trim())
16
+ : [...EXCHANGES];
17
+ const balances = [];
18
+ const positions = [];
19
+ const results = await Promise.allSettled(exchanges.map(async (ex) => {
20
+ const adapter = await getAdapterForExchange(ex);
21
+ const [bal, pos] = await Promise.all([
22
+ adapter.getBalance(),
23
+ adapter.getPositions(),
24
+ ]);
25
+ balances.push({ exchange: ex, balance: bal });
26
+ for (const p of pos)
27
+ positions.push({ exchange: ex, position: p });
28
+ }));
29
+ // Log failed exchanges
30
+ for (let i = 0; i < results.length; i++) {
31
+ if (results[i].status === "rejected") {
32
+ const err = results[i].reason;
33
+ if (!isJson()) {
34
+ console.log(chalk.yellow(` ${exchanges[i]}: ${err instanceof Error ? err.message : String(err)}`));
35
+ }
36
+ }
37
+ }
38
+ const assessment = assessRisk(balances, positions);
39
+ if (isJson()) {
40
+ return printJson(jsonOk(assessment));
41
+ }
42
+ // Display
43
+ const levelColor = {
44
+ low: chalk.green,
45
+ medium: chalk.yellow,
46
+ high: chalk.red,
47
+ critical: chalk.bgRed.white,
48
+ }[assessment.level];
49
+ console.log(chalk.cyan.bold("\n Risk Assessment\n"));
50
+ console.log(` Overall Risk: ${levelColor(assessment.level.toUpperCase())}`);
51
+ console.log(` Can Trade: ${assessment.canTrade ? chalk.green("YES") : chalk.red("NO")}`);
52
+ console.log(` Total Equity: $${formatUsd(assessment.metrics.totalEquity)}`);
53
+ console.log(` Unrealized PnL: ${(assessment.metrics.totalUnrealizedPnl >= 0 ? chalk.green : chalk.red)(`$${formatUsd(Math.abs(assessment.metrics.totalUnrealizedPnl))}`)}`);
54
+ console.log(` Total Exposure: $${formatUsd(assessment.metrics.totalExposure)}`);
55
+ console.log(` Margin Utilization: ${assessment.metrics.marginUtilization.toFixed(1)}%`);
56
+ console.log(` Positions: ${assessment.metrics.positionCount}`);
57
+ console.log(` Largest Position: $${formatUsd(assessment.metrics.largestPositionUsd)}`);
58
+ console.log(` Max Leverage Used: ${assessment.metrics.maxLeverageUsed}x`);
59
+ if (assessment.violations.length > 0) {
60
+ console.log(chalk.red.bold("\n Violations"));
61
+ const vRows = assessment.violations.map(v => {
62
+ const sevColor = {
63
+ low: chalk.green,
64
+ medium: chalk.yellow,
65
+ high: chalk.red,
66
+ critical: chalk.bgRed.white,
67
+ }[v.severity];
68
+ return [
69
+ sevColor(v.severity.toUpperCase()),
70
+ v.rule,
71
+ v.message,
72
+ ];
73
+ });
74
+ console.log(makeTable(["Severity", "Rule", "Details"], vRows));
75
+ }
76
+ else {
77
+ console.log(chalk.green("\n No risk violations. All clear.\n"));
78
+ }
79
+ });
80
+ });
81
+ // ── risk limits ──
82
+ risk
83
+ .command("limits")
84
+ .description("View or set risk limits")
85
+ .option("--max-drawdown <usd>", "Max unrealized loss before closing all")
86
+ .option("--max-position <usd>", "Max single position notional")
87
+ .option("--max-exposure <usd>", "Max total exposure across all positions")
88
+ .option("--daily-loss <usd>", "Daily realized loss limit")
89
+ .option("--max-positions <n>", "Max number of simultaneous positions")
90
+ .option("--max-leverage <n>", "Max leverage per position")
91
+ .option("--max-margin <pct>", "Max margin utilization %")
92
+ .option("--reset", "Reset all limits to defaults")
93
+ .action(async (opts) => {
94
+ let limits = loadRiskLimits();
95
+ const hasUpdate = opts.maxDrawdown || opts.maxPosition || opts.maxExposure ||
96
+ opts.dailyLoss || opts.maxPositions || opts.maxLeverage || opts.maxMargin || opts.reset;
97
+ if (opts.reset) {
98
+ limits = {
99
+ maxDrawdownUsd: 500, maxPositionUsd: 5000, maxTotalExposureUsd: 20000,
100
+ dailyLossLimitUsd: 200, maxPositions: 10, maxLeverage: 20, maxMarginUtilization: 80,
101
+ };
102
+ }
103
+ if (opts.maxDrawdown)
104
+ limits.maxDrawdownUsd = parseFloat(opts.maxDrawdown);
105
+ if (opts.maxPosition)
106
+ limits.maxPositionUsd = parseFloat(opts.maxPosition);
107
+ if (opts.maxExposure)
108
+ limits.maxTotalExposureUsd = parseFloat(opts.maxExposure);
109
+ if (opts.dailyLoss)
110
+ limits.dailyLossLimitUsd = parseFloat(opts.dailyLoss);
111
+ if (opts.maxPositions)
112
+ limits.maxPositions = parseInt(opts.maxPositions);
113
+ if (opts.maxLeverage)
114
+ limits.maxLeverage = parseInt(opts.maxLeverage);
115
+ if (opts.maxMargin)
116
+ limits.maxMarginUtilization = parseFloat(opts.maxMargin);
117
+ if (hasUpdate)
118
+ saveRiskLimits(limits);
119
+ if (isJson())
120
+ return printJson(jsonOk(limits));
121
+ console.log(chalk.cyan.bold(`\n Risk Limits ${hasUpdate ? "(updated)" : ""}\n`));
122
+ console.log(` Max Drawdown: $${formatUsd(limits.maxDrawdownUsd)}`);
123
+ console.log(` Max Position Size: $${formatUsd(limits.maxPositionUsd)}`);
124
+ console.log(` Max Total Exposure: $${formatUsd(limits.maxTotalExposureUsd)}`);
125
+ console.log(` Daily Loss Limit: $${formatUsd(limits.dailyLossLimitUsd)}`);
126
+ console.log(` Max Positions: ${limits.maxPositions}`);
127
+ console.log(` Max Leverage: ${limits.maxLeverage}x`);
128
+ console.log(` Max Margin Util: ${limits.maxMarginUtilization}%\n`);
129
+ console.log(chalk.gray(` Config file: ~/.perp/risk.json\n`));
130
+ });
131
+ // ── risk check ── (pre-trade check, for agent use)
132
+ risk
133
+ .command("check")
134
+ .description("Pre-trade risk check (for agents)")
135
+ .requiredOption("--notional <usd>", "Order notional value in USD")
136
+ .requiredOption("--leverage <n>", "Order leverage")
137
+ .option("--exchange <exchanges>", "Comma-separated exchanges (default: all)")
138
+ .action(async (opts) => {
139
+ await withJsonErrors(isJson(), async () => {
140
+ const exchanges = opts.exchange
141
+ ? opts.exchange.split(",").map(e => e.trim())
142
+ : [...EXCHANGES];
143
+ const balances = [];
144
+ const positions = [];
145
+ await Promise.allSettled(exchanges.map(async (ex) => {
146
+ const adapter = await getAdapterForExchange(ex);
147
+ const [bal, pos] = await Promise.all([
148
+ adapter.getBalance(),
149
+ adapter.getPositions(),
150
+ ]);
151
+ balances.push({ exchange: ex, balance: bal });
152
+ for (const p of pos)
153
+ positions.push({ exchange: ex, position: p });
154
+ }));
155
+ const { preTradeCheck, assessRisk: ar } = await import("../risk.js");
156
+ const assessment = ar(balances, positions);
157
+ const result = preTradeCheck(assessment, parseFloat(opts.notional), parseFloat(opts.leverage));
158
+ if (isJson()) {
159
+ return printJson(jsonOk({ ...result, riskLevel: assessment.level }));
160
+ }
161
+ if (result.allowed) {
162
+ console.log(chalk.green(`\n Trade ALLOWED (risk: ${assessment.level})\n`));
163
+ }
164
+ else {
165
+ console.log(chalk.red(`\n Trade BLOCKED: ${result.reason}\n`));
166
+ }
167
+ });
168
+ });
169
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import type { ExchangeAdapter } from "../exchanges/interface.js";
3
+ export declare function registerRunCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, getAdapterFor: (exchange: string) => Promise<ExchangeAdapter>, isJson: () => boolean): void;