@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,133 @@
1
+ import { loadConfig } from "./config.js";
2
+ import { readOperatorState } from "./operator-state.js";
3
+ // ---------------------------------------------------------------------------
4
+ // Helpers
5
+ // ---------------------------------------------------------------------------
6
+ function resolveEquityFromBalances(balances) {
7
+ if (balances.length === 0) {
8
+ return { equity: 0, available: 0, marginUsed: 0 };
9
+ }
10
+ const stableAssets = ["USD", "USDC", "USDT"];
11
+ const preferred = balances.find((b) => stableAssets.includes(b.asset.toUpperCase()));
12
+ const candidate = preferred ?? balances[0];
13
+ const equity = Number.parseFloat(candidate.total);
14
+ const available = Number.parseFloat(candidate.available);
15
+ const marginUsed = Number.parseFloat(candidate.marginUsed);
16
+ return {
17
+ equity: Number.isFinite(equity) ? equity : 0,
18
+ available: Number.isFinite(available) ? available : 0,
19
+ marginUsed: Number.isFinite(marginUsed) ? marginUsed : 0,
20
+ };
21
+ }
22
+ function toPositionContext(p) {
23
+ const size = Number.parseFloat(p.size);
24
+ const markPrice = Number.parseFloat(p.markPrice);
25
+ const liqPrice = p.liquidationPrice
26
+ ? Number.parseFloat(p.liquidationPrice)
27
+ : null;
28
+ return {
29
+ market: p.market,
30
+ side: p.side,
31
+ size,
32
+ entryPrice: Number.parseFloat(p.entryPrice),
33
+ markPrice,
34
+ notionalUsd: Math.abs(size) * markPrice,
35
+ unrealizedPnl: Number.parseFloat(p.unrealizedPnl),
36
+ realizedPnl: Number.parseFloat(p.realizedPnl),
37
+ leverage: p.leverage,
38
+ liquidationPrice: liqPrice,
39
+ marginType: p.marginType,
40
+ };
41
+ }
42
+ function round2(n) {
43
+ return Math.round(n * 100) / 100;
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Builder
47
+ // ---------------------------------------------------------------------------
48
+ export async function buildStateContext(adapter, exchangeId, config) {
49
+ const errors = [];
50
+ // Fetch in parallel with allSettled so partial failures don't kill everything
51
+ const [positionsResult, balancesResult, ordersResult] = await Promise.allSettled([
52
+ adapter.getPositions(),
53
+ adapter.getBalances(),
54
+ adapter.getOrders(),
55
+ ]);
56
+ let rawPositions = [];
57
+ if (positionsResult.status === "fulfilled") {
58
+ rawPositions = positionsResult.value;
59
+ }
60
+ else {
61
+ errors.push(`Failed to fetch positions: ${positionsResult.reason}`);
62
+ }
63
+ let rawBalances = [];
64
+ if (balancesResult.status === "fulfilled") {
65
+ rawBalances = balancesResult.value;
66
+ }
67
+ else {
68
+ errors.push(`Failed to fetch balances: ${balancesResult.reason}`);
69
+ }
70
+ let rawOrders = [];
71
+ if (ordersResult.status === "fulfilled") {
72
+ rawOrders = ordersResult.value;
73
+ }
74
+ else {
75
+ errors.push(`Failed to fetch orders: ${ordersResult.reason}`);
76
+ }
77
+ // Resolve equity from balances
78
+ const { equity, available, marginUsed } = resolveEquityFromBalances(rawBalances);
79
+ // Map positions
80
+ const positions = rawPositions.map(toPositionContext);
81
+ // Compute aggregates
82
+ const totalExposureUsd = positions.reduce((sum, p) => sum + p.notionalUsd, 0);
83
+ const netUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
84
+ const netRealizedPnl = positions.reduce((sum, p) => sum + p.realizedPnl, 0);
85
+ const leverageUsed = equity > 0 ? round2(totalExposureUsd / equity) : 0;
86
+ // Filter open orders
87
+ const openOrders = rawOrders.filter((o) => o.status === "open");
88
+ // Read operator state for halted/drawdown info
89
+ const operatorState = readOperatorState();
90
+ const killSwitchHalted = operatorState.killSwitch.enabled;
91
+ const heartbeatHalted = operatorState.heartbeat?.halted ?? false;
92
+ const halted = killSwitchHalted || heartbeatHalted;
93
+ let haltReason = "";
94
+ if (killSwitchHalted) {
95
+ haltReason = operatorState.killSwitch.reason ?? "kill switch enabled";
96
+ }
97
+ else if (heartbeatHalted) {
98
+ haltReason = operatorState.heartbeat?.haltReason ?? "halted";
99
+ }
100
+ const drawdownFromPeakPct = operatorState.heartbeat?.drawdownPct ?? 0;
101
+ const peakEquity = operatorState.heartbeat?.peakEquity ?? equity;
102
+ // Risk limits from config
103
+ const resolvedConfig = config ?? loadConfig(false);
104
+ const risk = resolvedConfig.risk;
105
+ return {
106
+ exchange: exchangeId,
107
+ timestamp: Date.now(),
108
+ equity,
109
+ availableBalance: available,
110
+ marginUsed,
111
+ marginAvailable: available,
112
+ totalExposureUsd,
113
+ netUnrealizedPnl,
114
+ netRealizedPnl,
115
+ leverageUsed,
116
+ positionCount: positions.length,
117
+ positions,
118
+ openOrderCount: openOrders.length,
119
+ openOrders,
120
+ halted,
121
+ haltReason,
122
+ drawdownFromPeakPct,
123
+ peakEquity,
124
+ riskLimits: {
125
+ maxPositionSizeUsd: risk.maxPositionSizeUsd,
126
+ maxTotalExposureUsd: risk.maxTotalExposureUsd,
127
+ maxLeverage: risk.maxLeverage,
128
+ minSignalConfidence: risk.minSignalConfidence,
129
+ maxDrawdownPct: risk.maxDrawdownPct,
130
+ },
131
+ errors,
132
+ };
133
+ }
@@ -0,0 +1,2 @@
1
+ import type { StrategyDefinition } from "./interface.js";
2
+ export declare const basisTradeStrategy: StrategyDefinition;
@@ -0,0 +1,24 @@
1
+ import { registerStrategy } from "./registry.js";
2
+ export const basisTradeStrategy = {
3
+ id: "basis-trade",
4
+ name: "Basis Trade (Cash-and-Carry)",
5
+ description: "Single-exchange basis convergence trade. When the perp trades at a premium to the index, short the perp to profit as the basis converges. When at a discount, long the perp.",
6
+ legs: 1,
7
+ shape: "directional",
8
+ exchanges: "single",
9
+ entrySignal: "Absolute basis spread (perp vs index) exceeds minimum threshold",
10
+ exitSignal: "Basis converges toward zero, manual close, or TP/SL hit",
11
+ parameters: [
12
+ { name: "minBasis", type: "number", default: 0.1, description: "Minimum basis spread (%) to enter" },
13
+ { name: "size", type: "number", description: "Position size in USD" },
14
+ { name: "confidence", type: "number", default: 0.5, description: "Signal confidence (0-1)" },
15
+ { name: "takeProfitPct", type: "number", description: "Take-profit basis convergence threshold (%)" },
16
+ { name: "stopLossPct", type: "number", description: "Stop-loss basis divergence threshold (%)" },
17
+ ],
18
+ riskProfile: {
19
+ maxPositionSizeUsd: 3000,
20
+ maxLeverage: 3,
21
+ maxDrawdownPct: 5,
22
+ },
23
+ };
24
+ registerStrategy(basisTradeStrategy);
@@ -0,0 +1,2 @@
1
+ import type { StrategyDefinition } from "./interface.js";
2
+ export declare const fundingArbStrategy: StrategyDefinition;
@@ -0,0 +1,23 @@
1
+ import { registerStrategy } from "./registry.js";
2
+ export const fundingArbStrategy = {
3
+ id: "funding-arb",
4
+ name: "Funding Rate Arbitrage",
5
+ description: "Delta-neutral cross-exchange funding rate capture. Short on the exchange paying the highest rate, long on the exchange paying the lowest. Profits from the funding rate differential.",
6
+ legs: 2,
7
+ shape: "delta-neutral",
8
+ exchanges: "cross",
9
+ entrySignal: "Funding rate spread between exchanges exceeds minimum threshold",
10
+ exitSignal: "Funding rate spread compresses or reverses, manual close, or TP/SL hit",
11
+ parameters: [
12
+ { name: "minSpread", type: "number", default: 0.05, description: "Minimum annualized funding spread (%) to enter" },
13
+ { name: "size", type: "number", description: "Position size in USD per leg" },
14
+ { name: "confidence", type: "number", default: 0.5, description: "Signal confidence (0-1)" },
15
+ { name: "takeProfitPct", type: "number", default: 4, description: "Take-profit percentage" },
16
+ { name: "stopLossPct", type: "number", default: 2, description: "Stop-loss percentage" },
17
+ ],
18
+ riskProfile: {
19
+ maxPositionSizeUsd: 5000,
20
+ maxLeverage: 3,
21
+ },
22
+ };
23
+ registerStrategy(fundingArbStrategy);
@@ -0,0 +1,23 @@
1
+ export interface StrategyParameter {
2
+ name: string;
3
+ type: "number" | "string" | "boolean";
4
+ default?: unknown;
5
+ description: string;
6
+ }
7
+ export interface StrategyRiskProfile {
8
+ maxPositionSizeUsd?: number;
9
+ maxLeverage?: number;
10
+ maxDrawdownPct?: number;
11
+ }
12
+ export interface StrategyDefinition {
13
+ id: string;
14
+ name: string;
15
+ description: string;
16
+ legs: number;
17
+ shape: "delta-neutral" | "directional" | "market-neutral";
18
+ parameters: StrategyParameter[];
19
+ riskProfile: StrategyRiskProfile;
20
+ exchanges: "single" | "cross";
21
+ entrySignal: string;
22
+ exitSignal: string;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { StrategyDefinition } from "./interface.js";
2
+ export declare function registerStrategy(definition: StrategyDefinition): void;
3
+ export declare function listStrategies(): StrategyDefinition[];
4
+ export declare function getStrategy(id: string): StrategyDefinition | undefined;
@@ -0,0 +1,10 @@
1
+ const strategies = new Map();
2
+ export function registerStrategy(definition) {
3
+ strategies.set(definition.id, definition);
4
+ }
5
+ export function listStrategies() {
6
+ return [...strategies.values()];
7
+ }
8
+ export function getStrategy(id) {
9
+ return strategies.get(id);
10
+ }
@@ -0,0 +1,25 @@
1
+ export interface TelemetryCounter {
2
+ total: number;
3
+ success: number;
4
+ failed: number;
5
+ skipped: number;
6
+ manual: number;
7
+ lastUpdatedAt: number;
8
+ }
9
+ export interface TelemetryStore {
10
+ version: number;
11
+ updatedAt: number;
12
+ counters: Record<string, TelemetryCounter>;
13
+ }
14
+ export declare function recordTelemetryMetric(args: {
15
+ metric: string;
16
+ status: "success" | "failed" | "skipped" | "manual";
17
+ }): void;
18
+ export declare function getTelemetrySnapshot(): TelemetryStore;
19
+ export declare function summarizeFailureRates(store: TelemetryStore): Array<{
20
+ metric: string;
21
+ total: number;
22
+ failureRate: number;
23
+ successRate: number;
24
+ }>;
25
+ export declare function resetTelemetry(): void;
@@ -0,0 +1,101 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { TELEMETRY_PATH } from "./paths.js";
4
+ import { ensurePrivateDir, hardenPrivateFile, PRIVATE_FILE_MODE } from "./fs-security.js";
5
+ const TELEMETRY_SCHEMA_VERSION = 1;
6
+ function telemetryEnabled() {
7
+ if (process.env.PERPS_DISABLE_TELEMETRY === "1") {
8
+ return false;
9
+ }
10
+ if (process.env.VITEST) {
11
+ return false;
12
+ }
13
+ return true;
14
+ }
15
+ function createCounter() {
16
+ return {
17
+ total: 0,
18
+ success: 0,
19
+ failed: 0,
20
+ skipped: 0,
21
+ manual: 0,
22
+ lastUpdatedAt: 0,
23
+ };
24
+ }
25
+ function createStore() {
26
+ return {
27
+ version: TELEMETRY_SCHEMA_VERSION,
28
+ updatedAt: Date.now(),
29
+ counters: {},
30
+ };
31
+ }
32
+ function readStore() {
33
+ if (!existsSync(TELEMETRY_PATH)) {
34
+ return createStore();
35
+ }
36
+ try {
37
+ const raw = readFileSync(TELEMETRY_PATH, "utf-8");
38
+ const parsed = JSON.parse(raw);
39
+ return {
40
+ version: TELEMETRY_SCHEMA_VERSION,
41
+ updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
42
+ counters: parsed.counters ?? {},
43
+ };
44
+ }
45
+ catch {
46
+ return createStore();
47
+ }
48
+ }
49
+ function writeStore(store) {
50
+ ensurePrivateDir(dirname(TELEMETRY_PATH));
51
+ writeFileSync(TELEMETRY_PATH, `${JSON.stringify(store, null, 2)}\n`, {
52
+ mode: PRIVATE_FILE_MODE,
53
+ });
54
+ hardenPrivateFile(TELEMETRY_PATH);
55
+ }
56
+ function upsertCounter(store, key) {
57
+ const existing = store.counters[key] ?? createCounter();
58
+ store.counters[key] = existing;
59
+ return existing;
60
+ }
61
+ export function recordTelemetryMetric(args) {
62
+ if (!telemetryEnabled()) {
63
+ return;
64
+ }
65
+ const store = readStore();
66
+ const counter = upsertCounter(store, args.metric);
67
+ counter.total += 1;
68
+ if (args.status === "success")
69
+ counter.success += 1;
70
+ if (args.status === "failed")
71
+ counter.failed += 1;
72
+ if (args.status === "skipped")
73
+ counter.skipped += 1;
74
+ if (args.status === "manual")
75
+ counter.manual += 1;
76
+ counter.lastUpdatedAt = Date.now();
77
+ store.updatedAt = counter.lastUpdatedAt;
78
+ writeStore(store);
79
+ }
80
+ export function getTelemetrySnapshot() {
81
+ return readStore();
82
+ }
83
+ export function summarizeFailureRates(store) {
84
+ const rows = [];
85
+ for (const [metric, counter] of Object.entries(store.counters)) {
86
+ const total = Math.max(0, counter.total);
87
+ const failureRate = total > 0 ? counter.failed / total : 0;
88
+ const successRate = total > 0 ? counter.success / total : 0;
89
+ rows.push({
90
+ metric,
91
+ total,
92
+ failureRate,
93
+ successRate,
94
+ });
95
+ }
96
+ rows.sort((a, b) => b.failureRate - a.failureRate || b.total - a.total);
97
+ return rows;
98
+ }
99
+ export function resetTelemetry() {
100
+ writeStore(createStore());
101
+ }
@@ -0,0 +1,20 @@
1
+ export interface TraceSummary {
2
+ traceId: string;
3
+ firstSeen: number;
4
+ lastSeen: number;
5
+ exchange: string;
6
+ markets: string[];
7
+ journalEntries: number;
8
+ riskEvaluations: number;
9
+ riskDenials: number;
10
+ journalStatus: "succeeded" | "failed" | "pending" | "mixed" | null;
11
+ durationMs: number;
12
+ }
13
+ export interface TraceListOptions {
14
+ minutes?: number;
15
+ exchange?: string;
16
+ market?: string;
17
+ limit?: number;
18
+ status?: "succeeded" | "failed" | "denied";
19
+ }
20
+ export declare function listTraces(options?: TraceListOptions): TraceSummary[];
@@ -0,0 +1,133 @@
1
+ import { getDb } from "./db/index.js";
2
+ function deriveJournalStatus(statuses, journalEntries) {
3
+ if (journalEntries === 0)
4
+ return null;
5
+ if (statuses.size === 1) {
6
+ const value = [...statuses][0];
7
+ if (value === "succeeded" || value === "failed" || value === "pending") {
8
+ return value;
9
+ }
10
+ return "mixed";
11
+ }
12
+ return "mixed";
13
+ }
14
+ export function listTraces(options) {
15
+ const db = getDb();
16
+ const minutes = options?.minutes ?? 60;
17
+ const limit = options?.limit ?? 50;
18
+ const cutoff = Date.now() - minutes * 60 * 1000;
19
+ const traceMap = new Map();
20
+ // 1. Query risk_evaluation_log grouped by trace_id
21
+ const riskRows = db
22
+ .prepare(`SELECT trace_id,
23
+ MIN(created_at) as first_seen,
24
+ MAX(created_at) as last_seen,
25
+ exchange,
26
+ GROUP_CONCAT(DISTINCT market) as markets,
27
+ COUNT(*) as eval_count,
28
+ SUM(CASE WHEN allowed = 0 THEN 1 ELSE 0 END) as denial_count
29
+ FROM risk_evaluation_log
30
+ WHERE trace_id IS NOT NULL AND created_at > ?
31
+ GROUP BY trace_id`)
32
+ .all(cutoff);
33
+ for (const row of riskRows) {
34
+ const markets = new Set(row.markets ? row.markets.split(",").filter(Boolean) : []);
35
+ traceMap.set(row.trace_id, {
36
+ traceId: row.trace_id,
37
+ firstSeen: row.first_seen,
38
+ lastSeen: row.last_seen,
39
+ exchange: row.exchange,
40
+ markets,
41
+ journalEntries: 0,
42
+ riskEvaluations: row.eval_count,
43
+ riskDenials: row.denial_count,
44
+ statuses: new Set(),
45
+ });
46
+ }
47
+ // 2. Query execution_journal grouped by trace_id
48
+ const journalRows = db
49
+ .prepare(`SELECT trace_id,
50
+ MIN(created_at) as first_seen,
51
+ MAX(created_at) as last_seen,
52
+ exchange,
53
+ GROUP_CONCAT(DISTINCT market) as markets,
54
+ COUNT(*) as entry_count,
55
+ GROUP_CONCAT(DISTINCT status) as statuses
56
+ FROM execution_journal
57
+ WHERE trace_id IS NOT NULL AND created_at > ?
58
+ GROUP BY trace_id`)
59
+ .all(cutoff);
60
+ for (const row of journalRows) {
61
+ const existing = traceMap.get(row.trace_id);
62
+ const journalMarkets = row.markets
63
+ ? row.markets.split(",").filter(Boolean)
64
+ : [];
65
+ const journalStatuses = row.statuses
66
+ ? row.statuses.split(",").filter(Boolean)
67
+ : [];
68
+ if (existing) {
69
+ // Merge
70
+ existing.firstSeen = Math.min(existing.firstSeen, row.first_seen);
71
+ existing.lastSeen = Math.max(existing.lastSeen, row.last_seen);
72
+ for (const m of journalMarkets)
73
+ existing.markets.add(m);
74
+ existing.journalEntries = row.entry_count;
75
+ for (const s of journalStatuses)
76
+ existing.statuses.add(s);
77
+ }
78
+ else {
79
+ const markets = new Set(journalMarkets);
80
+ const statuses = new Set(journalStatuses);
81
+ traceMap.set(row.trace_id, {
82
+ traceId: row.trace_id,
83
+ firstSeen: row.first_seen,
84
+ lastSeen: row.last_seen,
85
+ exchange: row.exchange,
86
+ markets,
87
+ journalEntries: row.entry_count,
88
+ riskEvaluations: 0,
89
+ riskDenials: 0,
90
+ statuses,
91
+ });
92
+ }
93
+ }
94
+ // 3. Convert to TraceSummary, apply filters, sort, limit
95
+ let results = [];
96
+ for (const acc of traceMap.values()) {
97
+ const journalStatus = deriveJournalStatus(acc.statuses, acc.journalEntries);
98
+ results.push({
99
+ traceId: acc.traceId,
100
+ firstSeen: acc.firstSeen,
101
+ lastSeen: acc.lastSeen,
102
+ exchange: acc.exchange,
103
+ markets: [...acc.markets],
104
+ journalEntries: acc.journalEntries,
105
+ riskEvaluations: acc.riskEvaluations,
106
+ riskDenials: acc.riskDenials,
107
+ journalStatus,
108
+ durationMs: acc.lastSeen - acc.firstSeen,
109
+ });
110
+ }
111
+ // Apply optional filters
112
+ if (options?.exchange) {
113
+ const ex = options.exchange;
114
+ results = results.filter((t) => t.exchange === ex);
115
+ }
116
+ if (options?.market) {
117
+ const mkt = options.market;
118
+ results = results.filter((t) => t.markets.includes(mkt));
119
+ }
120
+ if (options?.status) {
121
+ const st = options.status;
122
+ if (st === "denied") {
123
+ results = results.filter((t) => t.riskDenials > 0);
124
+ }
125
+ else {
126
+ results = results.filter((t) => t.journalStatus === st);
127
+ }
128
+ }
129
+ // Sort by lastSeen DESC
130
+ results.sort((a, b) => b.lastSeen - a.lastSeen);
131
+ // Limit
132
+ return results.slice(0, limit);
133
+ }
@@ -0,0 +1 @@
1
+ export declare function generateTraceId(): string;
@@ -0,0 +1,4 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export function generateTraceId() {
3
+ return `trc_${randomUUID()}`;
4
+ }
@@ -0,0 +1,6 @@
1
+ export interface TradeReputationCheckInput {
2
+ exchangeId: string;
3
+ market: string;
4
+ command?: string;
5
+ }
6
+ export declare function assertTradeAllowed(input: TradeReputationCheckInput): void;
@@ -0,0 +1,99 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ function parseCsvList(raw) {
3
+ if (!raw) {
4
+ return [];
5
+ }
6
+ return raw
7
+ .split(",")
8
+ .map((value) => value.trim())
9
+ .filter((value) => value.length > 0);
10
+ }
11
+ function asStringArray(value) {
12
+ if (!Array.isArray(value)) {
13
+ return [];
14
+ }
15
+ return value.filter((item) => typeof item === "string");
16
+ }
17
+ function compilePatterns(values, source) {
18
+ const patterns = [];
19
+ for (const value of values) {
20
+ try {
21
+ patterns.push(new RegExp(value, "i"));
22
+ }
23
+ catch {
24
+ throw new Error(`Invalid market pattern in ${source}: ${value}`);
25
+ }
26
+ }
27
+ return patterns;
28
+ }
29
+ function normalizeExchange(value) {
30
+ return value.trim().toLowerCase();
31
+ }
32
+ function normalizeMarket(value) {
33
+ return value.trim().toUpperCase();
34
+ }
35
+ function loadBlocklistFromEnv() {
36
+ const exchanges = parseCsvList(process.env.PERPS_BLOCKED_EXCHANGES).map(normalizeExchange);
37
+ const markets = parseCsvList(process.env.PERPS_BLOCKED_MARKETS).map(normalizeMarket);
38
+ const rawPatterns = parseCsvList(process.env.PERPS_BLOCKED_MARKET_PATTERNS);
39
+ const marketPatterns = compilePatterns(rawPatterns, "PERPS_BLOCKED_MARKET_PATTERNS");
40
+ return {
41
+ exchanges,
42
+ markets,
43
+ marketPatterns,
44
+ };
45
+ }
46
+ function loadBlocklistFromFile(filePath) {
47
+ if (!existsSync(filePath)) {
48
+ throw new Error(`Reputation blocklist file not found: ${filePath}`);
49
+ }
50
+ let parsed;
51
+ try {
52
+ parsed = JSON.parse(readFileSync(filePath, "utf-8"));
53
+ }
54
+ catch {
55
+ throw new Error(`Unable to parse reputation blocklist file: ${filePath}`);
56
+ }
57
+ const exchanges = asStringArray(parsed.exchanges).map(normalizeExchange);
58
+ const markets = asStringArray(parsed.markets).map(normalizeMarket);
59
+ const rawPatterns = asStringArray(parsed.marketPatterns);
60
+ const marketPatterns = compilePatterns(rawPatterns, `blocklist file (${filePath})`);
61
+ return {
62
+ exchanges,
63
+ markets,
64
+ marketPatterns,
65
+ };
66
+ }
67
+ function dedupe(values) {
68
+ return [...new Set(values)];
69
+ }
70
+ function loadEffectiveBlocklist() {
71
+ const envBlocklist = loadBlocklistFromEnv();
72
+ const filePath = process.env.PERPS_REPUTATION_BLOCKLIST_FILE?.trim();
73
+ if (!filePath) {
74
+ return envBlocklist;
75
+ }
76
+ const fileBlocklist = loadBlocklistFromFile(filePath);
77
+ return {
78
+ exchanges: dedupe([...envBlocklist.exchanges, ...fileBlocklist.exchanges]),
79
+ markets: dedupe([...envBlocklist.markets, ...fileBlocklist.markets]),
80
+ marketPatterns: [...envBlocklist.marketPatterns, ...fileBlocklist.marketPatterns],
81
+ };
82
+ }
83
+ export function assertTradeAllowed(input) {
84
+ const exchange = normalizeExchange(input.exchangeId);
85
+ const market = normalizeMarket(input.market);
86
+ const blocklist = loadEffectiveBlocklist();
87
+ const contextPrefix = input.command ? `${input.command}: ` : "";
88
+ if (blocklist.exchanges.includes(exchange)) {
89
+ throw new Error(`${contextPrefix}trade blocked by reputation policy: exchange "${exchange}" is blocked`);
90
+ }
91
+ if (blocklist.markets.includes(market)) {
92
+ throw new Error(`${contextPrefix}trade blocked by reputation policy: market "${market}" is blocked`);
93
+ }
94
+ for (const pattern of blocklist.marketPatterns) {
95
+ if (pattern.test(market)) {
96
+ throw new Error(`${contextPrefix}trade blocked by reputation policy: market "${market}" matched blocked pattern ${pattern.source}`);
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * UI tokens for lib/ modules
3
+ *
4
+ * Re-exports highlighter and inquirerTheme so that lib/ code
5
+ * never reaches into cli/ directly.
6
+ */
7
+ export { highlighter } from "../cli/experience.js";
8
+ export declare const inquirerTheme: {
9
+ prefix: {
10
+ idle: string;
11
+ done: string;
12
+ };
13
+ style: {
14
+ answer: (text: string) => string;
15
+ message: (text: string) => string;
16
+ error: (text: string) => string;
17
+ highlight: (text: string) => string;
18
+ description: (text: string) => string;
19
+ help: (text: string) => string;
20
+ };
21
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * UI tokens for lib/ modules
3
+ *
4
+ * Re-exports highlighter and inquirerTheme so that lib/ code
5
+ * never reaches into cli/ directly.
6
+ */
7
+ export { highlighter } from "../cli/experience.js";
8
+ /**
9
+ * Theme configuration for @inquirer/prompts
10
+ * Uses highlighter so NO_COLOR is respected in prompts.
11
+ */
12
+ import { highlighter } from "../cli/experience.js";
13
+ export const inquirerTheme = {
14
+ prefix: {
15
+ idle: highlighter.info("?"),
16
+ done: highlighter.success("✔"),
17
+ },
18
+ style: {
19
+ answer: highlighter.info,
20
+ message: highlighter.bold,
21
+ error: highlighter.error,
22
+ highlight: highlighter.info,
23
+ description: highlighter.dimWhite,
24
+ help: highlighter.dimWhite,
25
+ },
26
+ };