@raintree-technology/perps 0.1.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 (316) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/dist/adapters/aevo.d.ts +64 -0
  5. package/dist/adapters/aevo.js +899 -0
  6. package/dist/adapters/certification.d.ts +33 -0
  7. package/dist/adapters/certification.js +99 -0
  8. package/dist/adapters/decibel/order-manager.d.ts +45 -0
  9. package/dist/adapters/decibel/order-manager.js +140 -0
  10. package/dist/adapters/decibel/rest-client.d.ts +176 -0
  11. package/dist/adapters/decibel/rest-client.js +155 -0
  12. package/dist/adapters/decibel/ws-feed.d.ts +28 -0
  13. package/dist/adapters/decibel/ws-feed.js +166 -0
  14. package/dist/adapters/decibel.d.ts +108 -0
  15. package/dist/adapters/decibel.js +1377 -0
  16. package/dist/adapters/hyperliquid.d.ts +63 -0
  17. package/dist/adapters/hyperliquid.js +797 -0
  18. package/dist/adapters/index.d.ts +11 -0
  19. package/dist/adapters/index.js +12 -0
  20. package/dist/adapters/interface.d.ts +310 -0
  21. package/dist/adapters/interface.js +15 -0
  22. package/dist/adapters/orderly.d.ts +70 -0
  23. package/dist/adapters/orderly.js +936 -0
  24. package/dist/adapters/paradex.d.ts +69 -0
  25. package/dist/adapters/paradex.js +862 -0
  26. package/dist/adapters/utils.d.ts +17 -0
  27. package/dist/adapters/utils.js +122 -0
  28. package/dist/cli/command-metadata.d.ts +2 -0
  29. package/dist/cli/command-metadata.js +44 -0
  30. package/dist/cli/context.d.ts +14 -0
  31. package/dist/cli/context.js +59 -0
  32. package/dist/cli/experience.d.ts +48 -0
  33. package/dist/cli/experience.js +243 -0
  34. package/dist/cli/ink/app/AppShell.d.ts +12 -0
  35. package/dist/cli/ink/app/AppShell.js +32 -0
  36. package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
  37. package/dist/cli/ink/app/MetricStrip.js +14 -0
  38. package/dist/cli/ink/app/Panel.d.ts +9 -0
  39. package/dist/cli/ink/app/Panel.js +7 -0
  40. package/dist/cli/ink/app/ascii.d.ts +2 -0
  41. package/dist/cli/ink/app/ascii.js +46 -0
  42. package/dist/cli/ink/app/index.d.ts +5 -0
  43. package/dist/cli/ink/app/index.js +4 -0
  44. package/dist/cli/ink/app/types.d.ts +15 -0
  45. package/dist/cli/ink/app/types.js +1 -0
  46. package/dist/cli/ink/components/PnL.d.ts +12 -0
  47. package/dist/cli/ink/components/PnL.js +23 -0
  48. package/dist/cli/ink/components/Spinner.d.ts +13 -0
  49. package/dist/cli/ink/components/Spinner.js +13 -0
  50. package/dist/cli/ink/components/Table.d.ts +14 -0
  51. package/dist/cli/ink/components/Table.js +42 -0
  52. package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
  53. package/dist/cli/ink/components/WatchHeader.js +18 -0
  54. package/dist/cli/ink/components/index.d.ts +4 -0
  55. package/dist/cli/ink/components/index.js +4 -0
  56. package/dist/cli/ink/index.d.ts +4 -0
  57. package/dist/cli/ink/index.js +4 -0
  58. package/dist/cli/ink/render.d.ts +12 -0
  59. package/dist/cli/ink/render.js +21 -0
  60. package/dist/cli/ink/theme.d.ts +29 -0
  61. package/dist/cli/ink/theme.js +40 -0
  62. package/dist/cli/network-defaults.d.ts +10 -0
  63. package/dist/cli/network-defaults.js +35 -0
  64. package/dist/cli/output.d.ts +11 -0
  65. package/dist/cli/output.js +115 -0
  66. package/dist/cli/program.d.ts +18 -0
  67. package/dist/cli/program.js +164 -0
  68. package/dist/cli/watch.d.ts +19 -0
  69. package/dist/cli/watch.js +35 -0
  70. package/dist/client/index.d.ts +55 -0
  71. package/dist/client/index.js +157 -0
  72. package/dist/commands/account/add.d.ts +2 -0
  73. package/dist/commands/account/add.js +510 -0
  74. package/dist/commands/account/balances-simple.d.ts +5 -0
  75. package/dist/commands/account/balances-simple.js +63 -0
  76. package/dist/commands/account/index.d.ts +2 -0
  77. package/dist/commands/account/index.js +17 -0
  78. package/dist/commands/account/ls.d.ts +2 -0
  79. package/dist/commands/account/ls.js +95 -0
  80. package/dist/commands/account/positions-simple.d.ts +5 -0
  81. package/dist/commands/account/positions-simple.js +77 -0
  82. package/dist/commands/account/remove.d.ts +2 -0
  83. package/dist/commands/account/remove.js +47 -0
  84. package/dist/commands/account/set-default.d.ts +2 -0
  85. package/dist/commands/account/set-default.js +47 -0
  86. package/dist/commands/agent/index.d.ts +2 -0
  87. package/dist/commands/agent/index.js +126 -0
  88. package/dist/commands/arb/alert.d.ts +6 -0
  89. package/dist/commands/arb/alert.js +88 -0
  90. package/dist/commands/arb/basis-execute.d.ts +6 -0
  91. package/dist/commands/arb/basis-execute.js +332 -0
  92. package/dist/commands/arb/basis.d.ts +6 -0
  93. package/dist/commands/arb/basis.js +181 -0
  94. package/dist/commands/arb/compare.d.ts +6 -0
  95. package/dist/commands/arb/compare.js +216 -0
  96. package/dist/commands/arb/execute.d.ts +6 -0
  97. package/dist/commands/arb/execute.js +467 -0
  98. package/dist/commands/arb/funding.d.ts +6 -0
  99. package/dist/commands/arb/funding.js +201 -0
  100. package/dist/commands/arb/history.d.ts +6 -0
  101. package/dist/commands/arb/history.js +153 -0
  102. package/dist/commands/arb/index.d.ts +6 -0
  103. package/dist/commands/arb/index.js +29 -0
  104. package/dist/commands/arb/positions.d.ts +6 -0
  105. package/dist/commands/arb/positions.js +158 -0
  106. package/dist/commands/arb/spread.d.ts +6 -0
  107. package/dist/commands/arb/spread.js +253 -0
  108. package/dist/commands/arb/track.d.ts +6 -0
  109. package/dist/commands/arb/track.js +259 -0
  110. package/dist/commands/asset/book-simple.d.ts +5 -0
  111. package/dist/commands/asset/book-simple.js +77 -0
  112. package/dist/commands/asset/index.d.ts +2 -0
  113. package/dist/commands/asset/index.js +5 -0
  114. package/dist/commands/completion.d.ts +2 -0
  115. package/dist/commands/completion.js +161 -0
  116. package/dist/commands/config/index.d.ts +5 -0
  117. package/dist/commands/config/index.js +109 -0
  118. package/dist/commands/data/index.d.ts +31 -0
  119. package/dist/commands/data/index.js +1466 -0
  120. package/dist/commands/doctor.d.ts +2 -0
  121. package/dist/commands/doctor.js +201 -0
  122. package/dist/commands/exchange/index.d.ts +2 -0
  123. package/dist/commands/exchange/index.js +107 -0
  124. package/dist/commands/index.d.ts +2 -0
  125. package/dist/commands/index.js +48 -0
  126. package/dist/commands/markets/index.d.ts +2 -0
  127. package/dist/commands/markets/index.js +5 -0
  128. package/dist/commands/markets/ls-simple.d.ts +7 -0
  129. package/dist/commands/markets/ls-simple.js +277 -0
  130. package/dist/commands/operator/index.d.ts +2 -0
  131. package/dist/commands/operator/index.js +146 -0
  132. package/dist/commands/order/cancel-simple.d.ts +5 -0
  133. package/dist/commands/order/cancel-simple.js +104 -0
  134. package/dist/commands/order/index.d.ts +2 -0
  135. package/dist/commands/order/index.js +13 -0
  136. package/dist/commands/order/limit-simple.d.ts +5 -0
  137. package/dist/commands/order/limit-simple.js +195 -0
  138. package/dist/commands/order/market-simple.d.ts +5 -0
  139. package/dist/commands/order/market-simple.js +190 -0
  140. package/dist/commands/order/shared.d.ts +17 -0
  141. package/dist/commands/order/shared.js +51 -0
  142. package/dist/commands/order/trigger-simple.d.ts +5 -0
  143. package/dist/commands/order/trigger-simple.js +246 -0
  144. package/dist/commands/referral/index.d.ts +2 -0
  145. package/dist/commands/referral/index.js +7 -0
  146. package/dist/commands/referral/set.d.ts +2 -0
  147. package/dist/commands/referral/set.js +26 -0
  148. package/dist/commands/referral/status.d.ts +2 -0
  149. package/dist/commands/referral/status.js +31 -0
  150. package/dist/commands/replay/index.d.ts +2 -0
  151. package/dist/commands/replay/index.js +152 -0
  152. package/dist/commands/risk/analytics.d.ts +2 -0
  153. package/dist/commands/risk/analytics.js +64 -0
  154. package/dist/commands/risk/audit.d.ts +2 -0
  155. package/dist/commands/risk/audit.js +52 -0
  156. package/dist/commands/risk/index.d.ts +2 -0
  157. package/dist/commands/risk/index.js +9 -0
  158. package/dist/commands/risk/rules.d.ts +2 -0
  159. package/dist/commands/risk/rules.js +102 -0
  160. package/dist/commands/server.d.ts +2 -0
  161. package/dist/commands/server.js +208 -0
  162. package/dist/commands/setup/index.d.ts +2 -0
  163. package/dist/commands/setup/index.js +478 -0
  164. package/dist/commands/signal/index.d.ts +2 -0
  165. package/dist/commands/signal/index.js +129 -0
  166. package/dist/commands/state/index.d.ts +2 -0
  167. package/dist/commands/state/index.js +5 -0
  168. package/dist/commands/state/show.d.ts +2 -0
  169. package/dist/commands/state/show.js +105 -0
  170. package/dist/commands/strategy/index.d.ts +4 -0
  171. package/dist/commands/strategy/index.js +73 -0
  172. package/dist/commands/traces/index.d.ts +2 -0
  173. package/dist/commands/traces/index.js +76 -0
  174. package/dist/commands/ui/demo.d.ts +9 -0
  175. package/dist/commands/ui/demo.js +195 -0
  176. package/dist/commands/ui/index.d.ts +2 -0
  177. package/dist/commands/ui/index.js +7 -0
  178. package/dist/commands/ui/terminal.d.ts +2 -0
  179. package/dist/commands/ui/terminal.js +255 -0
  180. package/dist/commands/upgrade.d.ts +2 -0
  181. package/dist/commands/upgrade.js +98 -0
  182. package/dist/index.d.ts +2 -0
  183. package/dist/index.js +4 -0
  184. package/dist/lib/agent/audit.d.ts +12 -0
  185. package/dist/lib/agent/audit.js +13 -0
  186. package/dist/lib/agent/gateway.d.ts +13 -0
  187. package/dist/lib/agent/gateway.js +598 -0
  188. package/dist/lib/agent/metrics.d.ts +33 -0
  189. package/dist/lib/agent/metrics.js +175 -0
  190. package/dist/lib/agent/signature.d.ts +8 -0
  191. package/dist/lib/agent/signature.js +28 -0
  192. package/dist/lib/agent/tools.d.ts +28 -0
  193. package/dist/lib/agent/tools.js +453 -0
  194. package/dist/lib/agent/x402.d.ts +23 -0
  195. package/dist/lib/agent/x402.js +62 -0
  196. package/dist/lib/api-wallet.d.ts +69 -0
  197. package/dist/lib/api-wallet.js +101 -0
  198. package/dist/lib/balance-watcher.d.ts +25 -0
  199. package/dist/lib/balance-watcher.js +83 -0
  200. package/dist/lib/book-watcher.d.ts +25 -0
  201. package/dist/lib/book-watcher.js +48 -0
  202. package/dist/lib/config.d.ts +88 -0
  203. package/dist/lib/config.js +427 -0
  204. package/dist/lib/constants.d.ts +50 -0
  205. package/dist/lib/constants.js +84 -0
  206. package/dist/lib/contracts.d.ts +7 -0
  207. package/dist/lib/contracts.js +8 -0
  208. package/dist/lib/credential-vault.d.ts +22 -0
  209. package/dist/lib/credential-vault.js +109 -0
  210. package/dist/lib/db/accounts.d.ts +83 -0
  211. package/dist/lib/db/accounts.js +203 -0
  212. package/dist/lib/db/funding-history.d.ts +69 -0
  213. package/dist/lib/db/funding-history.js +183 -0
  214. package/dist/lib/db/index.d.ts +11 -0
  215. package/dist/lib/db/index.js +272 -0
  216. package/dist/lib/events/bus.d.ts +10 -0
  217. package/dist/lib/events/bus.js +17 -0
  218. package/dist/lib/events/types.d.ts +51 -0
  219. package/dist/lib/events/types.js +1 -0
  220. package/dist/lib/exchange.d.ts +30 -0
  221. package/dist/lib/exchange.js +84 -0
  222. package/dist/lib/execution/journal.d.ts +25 -0
  223. package/dist/lib/execution/journal.js +158 -0
  224. package/dist/lib/execution/safety.d.ts +34 -0
  225. package/dist/lib/execution/safety.js +197 -0
  226. package/dist/lib/exit-codes.d.ts +18 -0
  227. package/dist/lib/exit-codes.js +60 -0
  228. package/dist/lib/fetch.d.ts +18 -0
  229. package/dist/lib/fetch.js +66 -0
  230. package/dist/lib/fs-security.d.ts +10 -0
  231. package/dist/lib/fs-security.js +26 -0
  232. package/dist/lib/funding-tracker.d.ts +40 -0
  233. package/dist/lib/funding-tracker.js +118 -0
  234. package/dist/lib/logger.d.ts +27 -0
  235. package/dist/lib/logger.js +82 -0
  236. package/dist/lib/network-model.d.ts +13 -0
  237. package/dist/lib/network-model.js +30 -0
  238. package/dist/lib/onboarding.d.ts +133 -0
  239. package/dist/lib/onboarding.js +1459 -0
  240. package/dist/lib/operator-state.d.ts +25 -0
  241. package/dist/lib/operator-state.js +82 -0
  242. package/dist/lib/orders-watcher.d.ts +24 -0
  243. package/dist/lib/orders-watcher.js +74 -0
  244. package/dist/lib/paths.d.ts +20 -0
  245. package/dist/lib/paths.js +23 -0
  246. package/dist/lib/portfolio-watcher.d.ts +33 -0
  247. package/dist/lib/portfolio-watcher.js +95 -0
  248. package/dist/lib/position-watcher.d.ts +16 -0
  249. package/dist/lib/position-watcher.js +44 -0
  250. package/dist/lib/price-watcher.d.ts +15 -0
  251. package/dist/lib/price-watcher.js +84 -0
  252. package/dist/lib/prompts.d.ts +32 -0
  253. package/dist/lib/prompts.js +105 -0
  254. package/dist/lib/rate-limit.d.ts +32 -0
  255. package/dist/lib/rate-limit.js +88 -0
  256. package/dist/lib/risk/analytics.d.ts +39 -0
  257. package/dist/lib/risk/analytics.js +98 -0
  258. package/dist/lib/risk/drawdown.d.ts +18 -0
  259. package/dist/lib/risk/drawdown.js +49 -0
  260. package/dist/lib/risk/evaluation-log.d.ts +29 -0
  261. package/dist/lib/risk/evaluation-log.js +61 -0
  262. package/dist/lib/risk/index.d.ts +4 -0
  263. package/dist/lib/risk/index.js +4 -0
  264. package/dist/lib/risk/limits.d.ts +23 -0
  265. package/dist/lib/risk/limits.js +27 -0
  266. package/dist/lib/risk/manager.d.ts +32 -0
  267. package/dist/lib/risk/manager.js +85 -0
  268. package/dist/lib/risk/policy-middleware.d.ts +33 -0
  269. package/dist/lib/risk/policy-middleware.js +267 -0
  270. package/dist/lib/risk/position-sizer.d.ts +9 -0
  271. package/dist/lib/risk/position-sizer.js +14 -0
  272. package/dist/lib/risk/rules-store.d.ts +16 -0
  273. package/dist/lib/risk/rules-store.js +47 -0
  274. package/dist/lib/schema.d.ts +254 -0
  275. package/dist/lib/schema.js +199 -0
  276. package/dist/lib/secrets.d.ts +3 -0
  277. package/dist/lib/secrets.js +62 -0
  278. package/dist/lib/settings.d.ts +24 -0
  279. package/dist/lib/settings.js +86 -0
  280. package/dist/lib/signals.d.ts +73 -0
  281. package/dist/lib/signals.js +136 -0
  282. package/dist/lib/stable-stringify.d.ts +6 -0
  283. package/dist/lib/stable-stringify.js +17 -0
  284. package/dist/lib/state-context.d.ts +44 -0
  285. package/dist/lib/state-context.js +133 -0
  286. package/dist/lib/strategy/basis-trade.d.ts +2 -0
  287. package/dist/lib/strategy/basis-trade.js +24 -0
  288. package/dist/lib/strategy/funding-arb.d.ts +2 -0
  289. package/dist/lib/strategy/funding-arb.js +23 -0
  290. package/dist/lib/strategy/interface.d.ts +23 -0
  291. package/dist/lib/strategy/interface.js +1 -0
  292. package/dist/lib/strategy/registry.d.ts +4 -0
  293. package/dist/lib/strategy/registry.js +10 -0
  294. package/dist/lib/telemetry.d.ts +25 -0
  295. package/dist/lib/telemetry.js +101 -0
  296. package/dist/lib/trace-queries.d.ts +20 -0
  297. package/dist/lib/trace-queries.js +133 -0
  298. package/dist/lib/trace.d.ts +1 -0
  299. package/dist/lib/trace.js +4 -0
  300. package/dist/lib/trade-reputation.d.ts +6 -0
  301. package/dist/lib/trade-reputation.js +99 -0
  302. package/dist/lib/ui-tokens.d.ts +21 -0
  303. package/dist/lib/ui-tokens.js +26 -0
  304. package/dist/lib/validate.d.ts +39 -0
  305. package/dist/lib/validate.js +108 -0
  306. package/dist/lib/validation.d.ts +9 -0
  307. package/dist/lib/validation.js +64 -0
  308. package/dist/server/cache.d.ts +38 -0
  309. package/dist/server/cache.js +56 -0
  310. package/dist/server/index.d.ts +2 -0
  311. package/dist/server/index.js +89 -0
  312. package/dist/server/ipc.d.ts +18 -0
  313. package/dist/server/ipc.js +159 -0
  314. package/dist/server/subscriptions.d.ts +18 -0
  315. package/dist/server/subscriptions.js +114 -0
  316. package/package.json +124 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * Arb Basis Execute Command
3
+ * Execute basis trades (cash-and-carry arbitrage) on a single exchange
4
+ */
5
+ import { createHash } from "node:crypto";
6
+ import { confirm } from "@inquirer/prompts";
7
+ import { getContext, getOutputOptions, getSelectedExchange } from "../../cli/program.js";
8
+ import { output, outputError } from "../../cli/output.js";
9
+ import { getExchangeAdapterById } from "../../lib/exchange.js";
10
+ import { DEFAULT_ARB_SIZE_USD } from "../../lib/constants.js";
11
+ import { validateAsset, validateSize } from "../../lib/validate.js";
12
+ import { getExchangeCredentials } from "../../lib/config.js";
13
+ import { RiskPolicyMiddleware } from "../../lib/risk/policy-middleware.js";
14
+ import { executeOrderWithSafety } from "../../lib/execution/safety.js";
15
+ import { runWithExecutionJournal } from "../../lib/execution/journal.js";
16
+ import { withJsonContract } from "../../lib/contracts.js";
17
+ import { CLIError, EXIT_CODES, inferExitCode } from "../../lib/exit-codes.js";
18
+ export function registerArbBasisExecuteCommand(arb) {
19
+ arb
20
+ .command("basis-execute [asset]")
21
+ .description("Execute basis trade (cash-and-carry arbitrage)")
22
+ .option("-s, --size <usd>", "Position size in USD (required)")
23
+ .option("--confidence <0-1>", "Signal confidence for risk evaluation (default: 0.5)")
24
+ .option("--exchange <id>", "Exchange to trade on (overrides default)")
25
+ .option("--min-basis <pct>", "Minimum basis to consider actionable (default: 0.1)")
26
+ .option("--tp <pct>", "Take-profit basis convergence threshold")
27
+ .option("--sl <pct>", "Stop-loss basis divergence threshold")
28
+ .option("--dry-run", "Show what would happen without executing")
29
+ .option("-y, --yes", "Skip confirmation prompt")
30
+ .option("--idempotency-key <key>", "Explicit idempotency key")
31
+ .action(async function (asset) {
32
+ const ctx = getContext(this);
33
+ const outputOpts = getOutputOptions(this);
34
+ const opts = this.opts();
35
+ const emitJson = (data) => {
36
+ output(withJsonContract("arb.basis-execute.result", data), outputOpts);
37
+ };
38
+ const isJson = outputOpts.json;
39
+ try {
40
+ if (isJson && !opts.yes && !opts.dryRun) {
41
+ throw new CLIError("Use --yes with --json for non-interactive basis execution", EXIT_CODES.VALIDATION_ERROR);
42
+ }
43
+ const assetName = validateAsset(asset);
44
+ const market = `${assetName}-PERP`;
45
+ if (!opts.size) {
46
+ throw new CLIError("Position size is required. Use --size <usd>", EXIT_CODES.VALIDATION_ERROR);
47
+ }
48
+ const requestedSizeUsd = validateSize(opts.size, DEFAULT_ARB_SIZE_USD);
49
+ const confidence = opts.confidence ? parseFloat(opts.confidence) : 0.5;
50
+ if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
51
+ throw new CLIError("Confidence must be a number between 0 and 1", EXIT_CODES.VALIDATION_ERROR);
52
+ }
53
+ const minBasis = opts.minBasis ? parseFloat(opts.minBasis) : 0.1;
54
+ if (!Number.isFinite(minBasis) || minBasis < 0) {
55
+ throw new CLIError("Min basis must be a non-negative number", EXIT_CODES.VALIDATION_ERROR);
56
+ }
57
+ const exchangeId = opts.exchange ?? getSelectedExchange(this);
58
+ const adapter = getExchangeAdapterById(exchangeId);
59
+ const credentials = getExchangeCredentials(ctx.config, exchangeId, {
60
+ requireTrading: true,
61
+ });
62
+ await adapter.connect({
63
+ testnet: ctx.config.testnet,
64
+ rpcUrl: credentials.fullnodeUrl,
65
+ wsUrl: credentials.wsUrl,
66
+ credentials,
67
+ });
68
+ try {
69
+ const ticker = await adapter.getTicker(market);
70
+ const markPrice = parseFloat(ticker.markPrice);
71
+ const indexPrice = parseFloat(ticker.indexPrice);
72
+ if (!Number.isFinite(markPrice) || markPrice <= 0) {
73
+ throw new CLIError("Unable to fetch reliable mark price for basis calculation", EXIT_CODES.DATA_UNAVAILABLE);
74
+ }
75
+ if (!Number.isFinite(indexPrice) || indexPrice <= 0) {
76
+ if (isJson) {
77
+ emitJson({
78
+ status: "no_data",
79
+ reason: "no_index_price",
80
+ market,
81
+ exchange: exchangeId,
82
+ });
83
+ }
84
+ else {
85
+ console.log("\n No index price available for this market on this exchange.\n");
86
+ }
87
+ process.exitCode = EXIT_CODES.DATA_UNAVAILABLE;
88
+ return;
89
+ }
90
+ // Compute basis: (markPrice - indexPrice) / indexPrice * 100
91
+ const basisPct = ((markPrice - indexPrice) / indexPrice) * 100;
92
+ // Guard: basis too small
93
+ if (Math.abs(basisPct) < minBasis) {
94
+ if (isJson) {
95
+ emitJson({
96
+ status: "no_opportunity",
97
+ reason: "basis_too_small",
98
+ market,
99
+ exchange: exchangeId,
100
+ basisPct,
101
+ minBasis,
102
+ });
103
+ }
104
+ else {
105
+ console.log(`\n Basis spread ${basisPct >= 0 ? "+" : ""}${basisPct.toFixed(4)}% is below minimum threshold of ${minBasis}%.`);
106
+ console.log(" No actionable opportunity.\n");
107
+ }
108
+ process.exitCode = EXIT_CODES.NO_OPPORTUNITY;
109
+ return;
110
+ }
111
+ // Determine side: basis > 0 (premium) -> short, basis < 0 (discount) -> long
112
+ const side = basisPct > 0 ? "short" : "long";
113
+ const direction = basisPct > 0 ? "premium" : "discount";
114
+ // Risk evaluation
115
+ const risk = new RiskPolicyMiddleware(ctx.config, exchangeId);
116
+ const evaluation = await risk.evaluateUsdSignal(adapter, { market, side, confidence, reason: "basis-trade", timestamp: Date.now() }, requestedSizeUsd);
117
+ if (!evaluation.allowed) {
118
+ throw new CLIError(`Trade blocked by risk policy: ${evaluation.reason ?? "unknown"}`, EXIT_CODES.EXECUTION_ERROR);
119
+ }
120
+ const sizeUsd = Math.min(requestedSizeUsd, evaluation.sizeUsd);
121
+ if (!Number.isFinite(sizeUsd) || sizeUsd <= 0) {
122
+ throw new CLIError("Risk policy reduced trade size to zero", EXIT_CODES.EXECUTION_ERROR);
123
+ }
124
+ // Build execution plan
125
+ const annualizedPct = Math.abs(basisPct) * 365;
126
+ const dailyProfit = (Math.abs(basisPct) / 100) * sizeUsd;
127
+ const plan = {
128
+ asset: assetName,
129
+ exchange: exchangeId,
130
+ size: sizeUsd,
131
+ side,
132
+ markPrice,
133
+ indexPrice,
134
+ basisPct,
135
+ direction,
136
+ annualizedPct,
137
+ dailyProfit,
138
+ };
139
+ if (!isJson) {
140
+ console.log("\n \x1b[1m━━━ Basis Trade Execution Plan ━━━\x1b[0m\n");
141
+ console.log(` Asset: ${assetName}`);
142
+ console.log(` Exchange: ${exchangeId}`);
143
+ console.log(` Position Size: $${sizeUsd.toLocaleString()}`);
144
+ if (Math.abs(sizeUsd - requestedSizeUsd) > 1e-9) {
145
+ console.log(` Risk Adjusted: from $${requestedSizeUsd.toLocaleString()}`);
146
+ }
147
+ console.log("");
148
+ console.log(` Mark Price: $${markPrice.toLocaleString()}`);
149
+ console.log(` Index Price: $${indexPrice.toLocaleString()}`);
150
+ console.log(` Basis: ${basisPct >= 0 ? "+" : ""}${basisPct.toFixed(4)}% (${direction})`);
151
+ console.log("");
152
+ const sideColor = side === "short" ? "\x1b[31m" : "\x1b[32m";
153
+ const sideArrow = side === "short" ? "▼" : "▲";
154
+ console.log(` ${sideColor}${sideArrow} ${side.toUpperCase()}\x1b[0m ${assetName}-PERP`);
155
+ console.log(` Rationale: ${direction === "premium" ? "Sell premium — basis converges to zero" : "Buy discount — basis converges to zero"}`);
156
+ console.log("");
157
+ console.log(" " + "─".repeat(45));
158
+ console.log(` Annualized: \x1b[32m${annualizedPct.toFixed(2)}%\x1b[0m`);
159
+ console.log(` Est. Daily: \x1b[32m$${dailyProfit.toFixed(2)}\x1b[0m`);
160
+ console.log(" " + "─".repeat(45));
161
+ console.log("");
162
+ }
163
+ if (opts.dryRun) {
164
+ if (isJson) {
165
+ emitJson({
166
+ status: "dry_run",
167
+ market,
168
+ asset: assetName,
169
+ exchange: exchangeId,
170
+ testnet: ctx.config.testnet,
171
+ requestedSizeUsd,
172
+ plan,
173
+ });
174
+ }
175
+ else {
176
+ console.log(" \x1b[33m[DRY RUN]\x1b[0m Execution skipped.\n");
177
+ }
178
+ return;
179
+ }
180
+ if (!opts.yes) {
181
+ const confirmed = await confirm({
182
+ message: "Execute this basis trade?",
183
+ default: false,
184
+ });
185
+ if (!confirmed) {
186
+ if (isJson) {
187
+ emitJson({
188
+ status: "cancelled",
189
+ reason: "user_declined_confirmation",
190
+ market,
191
+ asset: assetName,
192
+ plan,
193
+ });
194
+ }
195
+ else {
196
+ console.log("\n Execution cancelled.\n");
197
+ }
198
+ process.exitCode = EXIT_CODES.CANCELLED;
199
+ return;
200
+ }
201
+ }
202
+ const orderSize = (sizeUsd / markPrice).toFixed(8);
203
+ const shouldAttachTpSl = opts.tp !== undefined || opts.sl !== undefined;
204
+ const tp = opts.tp !== undefined ? parseFloat(opts.tp) : ctx.config.executionSafety.takeProfitPct;
205
+ const sl = opts.sl !== undefined ? parseFloat(opts.sl) : ctx.config.executionSafety.stopLossPct;
206
+ if (shouldAttachTpSl) {
207
+ if (!Number.isFinite(tp) || tp <= 0) {
208
+ throw new CLIError("TP must be a positive number", EXIT_CODES.VALIDATION_ERROR);
209
+ }
210
+ if (!Number.isFinite(sl) || sl <= 0) {
211
+ throw new CLIError("SL must be a positive number", EXIT_CODES.VALIDATION_ERROR);
212
+ }
213
+ }
214
+ // Build idempotency key
215
+ const idempotencyKey = opts.idempotencyKey?.trim() || undefined;
216
+ const autoKeyHash = createHash("sha256")
217
+ .update(`basis:${exchangeId}:${market}:${side}:${sizeUsd}`)
218
+ .digest("hex")
219
+ .slice(0, 16);
220
+ const timeBucket = Math.floor(Date.now() / 30_000);
221
+ const resolvedIdempotencyKey = idempotencyKey ?? `basis:${autoKeyHash}:${timeBucket}`;
222
+ if (!isJson) {
223
+ console.log(`\n \x1b[36mExecuting ${side.toUpperCase()} on ${exchangeId}...\x1b[0m\n`);
224
+ }
225
+ const journal = await runWithExecutionJournal({
226
+ idempotencyKey: resolvedIdempotencyKey,
227
+ metadata: {
228
+ command: "arb.basis-execute",
229
+ exchange: exchangeId,
230
+ testnet: ctx.config.testnet,
231
+ market,
232
+ side,
233
+ orderType: "market",
234
+ },
235
+ request: {
236
+ market,
237
+ side,
238
+ size: orderSize,
239
+ spreadAware: true,
240
+ spreadOffset: ctx.config.executionSafety.spreadOffset,
241
+ tp: shouldAttachTpSl ? tp : null,
242
+ sl: shouldAttachTpSl ? sl : null,
243
+ requestedSizeUsd,
244
+ adjustedSizeUsd: sizeUsd,
245
+ confidence,
246
+ basisPct,
247
+ },
248
+ execute: async () => {
249
+ const result = await executeOrderWithSafety(adapter, {
250
+ market,
251
+ side,
252
+ type: "market",
253
+ size: orderSize,
254
+ }, {
255
+ closeThenFlip: true,
256
+ spreadAwarePricing: true,
257
+ spreadOffset: ctx.config.executionSafety.spreadOffset,
258
+ attachTpSl: shouldAttachTpSl,
259
+ tpSlConfig: shouldAttachTpSl
260
+ ? { stopLossPct: sl, takeProfitPct: tp }
261
+ : undefined,
262
+ });
263
+ return {
264
+ order: {
265
+ id: result.order.id,
266
+ market: result.order.market,
267
+ side: result.order.side,
268
+ size: result.order.size,
269
+ status: result.order.status,
270
+ price: result.order.price,
271
+ },
272
+ safety: {
273
+ closedOppositePosition: result.closedOppositePosition,
274
+ tpSlOrderIds: result.tpSlOrderIds,
275
+ pricing: result.pricing,
276
+ },
277
+ };
278
+ },
279
+ });
280
+ if (!isJson) {
281
+ console.log(` ✓ ${side.toUpperCase()} ${journal.replayed ? "replayed" : "opened"}: ${journal.result.order.id}`);
282
+ console.log("\n \x1b[32m━━━ Basis Trade Executed ━━━\x1b[0m");
283
+ if (journal.result.safety.tpSlOrderIds.length > 0) {
284
+ console.log(" TP/SL attached");
285
+ }
286
+ console.log(` Monitor position with: perps -e ${exchangeId} account positions`);
287
+ console.log("");
288
+ }
289
+ else {
290
+ emitJson({
291
+ status: "executed",
292
+ market,
293
+ asset: assetName,
294
+ exchange: exchangeId,
295
+ testnet: ctx.config.testnet,
296
+ requestedSizeUsd,
297
+ plan,
298
+ execution: {
299
+ ...journal.result,
300
+ idempotency: {
301
+ key: journal.idempotencyKey,
302
+ replayed: journal.replayed,
303
+ autoGenerated: journal.autoGeneratedKey,
304
+ journalId: journal.journalId,
305
+ },
306
+ },
307
+ });
308
+ }
309
+ }
310
+ finally {
311
+ await adapter.disconnect().catch(() => undefined);
312
+ }
313
+ }
314
+ catch (err) {
315
+ const code = inferExitCode(err);
316
+ const message = err instanceof Error ? err.message : String(err);
317
+ if (isJson) {
318
+ emitJson({
319
+ status: "error",
320
+ error: {
321
+ message,
322
+ exitCode: code,
323
+ },
324
+ });
325
+ }
326
+ else {
327
+ outputError(message);
328
+ }
329
+ process.exit(code);
330
+ }
331
+ });
332
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Arb Basis Command
3
+ * Fetch perp and spot/index prices to show basis spread opportunities
4
+ */
5
+ import { Command } from "commander";
6
+ export declare function registerArbBasisCommand(arb: Command): void;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Arb Basis Command
3
+ * Fetch perp and spot/index prices to show basis spread opportunities
4
+ */
5
+ import { getContext, getOutputOptions } from "../../cli/program.js";
6
+ import { output, outputError } from "../../cli/output.js";
7
+ import { getExchangeAdapterById, getAvailableExchanges } from "../../lib/exchange.js";
8
+ import { withJsonContract } from "../../lib/contracts.js";
9
+ export function registerArbBasisCommand(arb) {
10
+ arb
11
+ .command("basis [asset]")
12
+ .description("Show basis spread between perp and index prices")
13
+ .option("--min-basis <pct>", "Minimum basis spread to show", "0.05")
14
+ .option("-w, --watch", "Watch mode - refresh every 10s")
15
+ .option("--json", "JSON output")
16
+ .action(async function (asset) {
17
+ const ctx = getContext(this);
18
+ const outputOpts = getOutputOptions(this);
19
+ const opts = this.opts();
20
+ const isTestnet = ctx.config.testnet;
21
+ const resolvedAsset = asset ? asset.toUpperCase() : "BTC";
22
+ const minBasis = parseFloat(opts.minBasis ?? "0.05");
23
+ try {
24
+ const exchanges = getAvailableExchanges();
25
+ if (outputOpts.json && opts.watch) {
26
+ throw new Error("Watch mode is not supported with --json");
27
+ }
28
+ if (opts.watch) {
29
+ await watchBasis(resolvedAsset, exchanges, minBasis, isTestnet);
30
+ }
31
+ else {
32
+ await showBasis(resolvedAsset, exchanges, minBasis, outputOpts.json, isTestnet);
33
+ }
34
+ }
35
+ catch (err) {
36
+ outputError(err instanceof Error ? err.message : String(err));
37
+ process.exit(1);
38
+ }
39
+ });
40
+ }
41
+ async function fetchBasis(asset, exchanges, isTestnet) {
42
+ const results = [];
43
+ const promises = exchanges.map(async (exchangeId) => {
44
+ const adapter = getExchangeAdapterById(exchangeId);
45
+ let connected = false;
46
+ try {
47
+ await adapter.connect({ testnet: isTestnet });
48
+ connected = true;
49
+ const symbol = `${asset}-PERP`;
50
+ const ticker = await adapter.getTicker(symbol);
51
+ const markPrice = parseFloat(ticker.markPrice);
52
+ const indexPrice = parseFloat(ticker.indexPrice);
53
+ if (!indexPrice || indexPrice === 0) {
54
+ return null;
55
+ }
56
+ const basisPct = ((markPrice - indexPrice) / indexPrice) * 100;
57
+ const annualizedPct = Math.abs(basisPct) * 365;
58
+ return {
59
+ exchange: adapter.info.name,
60
+ markPrice,
61
+ indexPrice,
62
+ basisPct,
63
+ direction: (basisPct >= 0 ? "premium" : "discount"),
64
+ annualizedPct,
65
+ };
66
+ }
67
+ catch {
68
+ return null;
69
+ }
70
+ finally {
71
+ if (connected) {
72
+ await adapter.disconnect().catch(() => undefined);
73
+ }
74
+ }
75
+ });
76
+ const settled = await Promise.allSettled(promises);
77
+ for (const result of settled) {
78
+ if (result.status === "fulfilled" && result.value !== null) {
79
+ results.push(result.value);
80
+ }
81
+ }
82
+ return results;
83
+ }
84
+ function formatUsd(value) {
85
+ return `$${value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
86
+ }
87
+ async function showBasis(asset, exchanges, minBasis, json, isTestnet) {
88
+ if (!json) {
89
+ console.log(`\nFetching ${asset} basis across ${exchanges.length} exchanges...\n`);
90
+ }
91
+ const opportunities = await fetchBasis(asset, exchanges, isTestnet);
92
+ // Filter by minimum basis
93
+ const filtered = opportunities.filter((o) => Math.abs(o.basisPct) >= minBasis);
94
+ // Sort by absolute basis descending
95
+ filtered.sort((a, b) => Math.abs(b.basisPct) - Math.abs(a.basisPct));
96
+ if (json) {
97
+ output(withJsonContract("arb.basis.result", {
98
+ asset,
99
+ opportunities: filtered,
100
+ timestamp: Date.now(),
101
+ }), { json: true });
102
+ return;
103
+ }
104
+ if (filtered.length === 0) {
105
+ console.log(` No basis opportunities found for ${asset} above ${minBasis}%`);
106
+ console.log("");
107
+ return;
108
+ }
109
+ console.log(` Basis Spread: ${asset}\n`);
110
+ console.log(" Exchange".padEnd(18) +
111
+ "Mark Price".padEnd(16) +
112
+ "Index Price".padEnd(16) +
113
+ "Basis (%)".padEnd(14) +
114
+ "Direction");
115
+ console.log(" " + "\u2500".repeat(76));
116
+ for (const o of filtered) {
117
+ const basisColor = o.direction === "premium" ? "\x1b[32m" : "\x1b[31m";
118
+ const basisSign = o.basisPct >= 0 ? "+" : "";
119
+ const dirLabel = o.direction === "premium"
120
+ ? "Premium (short perp opportunity)"
121
+ : "Discount";
122
+ console.log(` ${o.exchange.padEnd(16)}` +
123
+ `${formatUsd(o.markPrice).padEnd(16)}` +
124
+ `${formatUsd(o.indexPrice).padEnd(16)}` +
125
+ `${basisColor}${basisSign}${o.basisPct.toFixed(2)}%\x1b[0m`.padEnd(14 + 9) +
126
+ dirLabel);
127
+ }
128
+ // Best opportunity
129
+ const best = filtered[0];
130
+ console.log("\n " + "\u2500".repeat(76));
131
+ console.log(`\n Best opportunity: ${best.exchange} at ${best.basisPct >= 0 ? "+" : ""}${best.basisPct.toFixed(2)}% basis`);
132
+ console.log(` Annualized (if persistent): ~${best.annualizedPct.toFixed(1)}%`);
133
+ console.log("");
134
+ }
135
+ async function watchBasis(asset, exchanges, minBasis, isTestnet) {
136
+ console.log(`\nWatching ${asset} basis (Ctrl+C to exit)...\n`);
137
+ const refresh = async () => {
138
+ process.stdout.write("\x1B[2J\x1B[0f");
139
+ console.log(`\n ${asset} Basis Monitor (${new Date().toLocaleTimeString()})\n`);
140
+ const opportunities = await fetchBasis(asset, exchanges, isTestnet);
141
+ const filtered = opportunities.filter((o) => Math.abs(o.basisPct) >= minBasis);
142
+ filtered.sort((a, b) => Math.abs(b.basisPct) - Math.abs(a.basisPct));
143
+ if (filtered.length === 0) {
144
+ console.log(` No basis opportunities above ${minBasis}%`);
145
+ console.log("\n Press Ctrl+C to exit");
146
+ return;
147
+ }
148
+ console.log(" Exchange".padEnd(18) +
149
+ "Mark Price".padEnd(16) +
150
+ "Index Price".padEnd(16) +
151
+ "Basis (%)".padEnd(14) +
152
+ "Direction");
153
+ console.log(" " + "\u2500".repeat(76));
154
+ for (const o of filtered) {
155
+ const basisColor = o.direction === "premium" ? "\x1b[32m" : "\x1b[31m";
156
+ const basisSign = o.basisPct >= 0 ? "+" : "";
157
+ const dirLabel = o.direction === "premium"
158
+ ? "Premium (short perp opportunity)"
159
+ : "Discount";
160
+ console.log(` ${o.exchange.padEnd(16)}` +
161
+ `${formatUsd(o.markPrice).padEnd(16)}` +
162
+ `${formatUsd(o.indexPrice).padEnd(16)}` +
163
+ `${basisColor}${basisSign}${o.basisPct.toFixed(2)}%\x1b[0m`.padEnd(14 + 9) +
164
+ dirLabel);
165
+ }
166
+ if (filtered.length > 0) {
167
+ const best = filtered[0];
168
+ console.log("\n " + "\u2500".repeat(76));
169
+ console.log(`\n Best: ${best.exchange} at ${best.basisPct >= 0 ? "+" : ""}${best.basisPct.toFixed(2)}% (annualized ~${best.annualizedPct.toFixed(1)}%)`);
170
+ }
171
+ console.log("\n Press Ctrl+C to exit");
172
+ };
173
+ await refresh();
174
+ const interval = setInterval(refresh, 10000);
175
+ process.once("SIGINT", () => {
176
+ clearInterval(interval);
177
+ console.log("\n");
178
+ process.exit(0);
179
+ });
180
+ await new Promise(() => { });
181
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Arb Compare Command
3
+ * Compare arbitrage opportunities across all assets
4
+ */
5
+ import { Command } from "commander";
6
+ export declare function registerArbCompareCommand(arb: Command): void;