perp-cli 0.11.0 → 0.12.1

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 (240) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bridge-engine.js +102 -49
  21. package/dist/cli-spec.js +1 -2
  22. package/dist/commands/account.js +2 -500
  23. package/dist/commands/agent.d.ts +8 -2
  24. package/dist/commands/agent.js +1903 -366
  25. package/dist/commands/alerts.js +3 -1
  26. package/dist/commands/arb/index.js +37 -7
  27. package/dist/commands/arb-auto.js +44 -11
  28. package/dist/commands/arb.js +83 -34
  29. package/dist/commands/bot.d.ts +1 -1
  30. package/dist/commands/bot.js +18 -14
  31. package/dist/commands/bridge.d.ts +1 -1
  32. package/dist/commands/bridge.js +4 -4
  33. package/dist/commands/funds.d.ts +1 -1
  34. package/dist/commands/funds.js +18 -68
  35. package/dist/commands/jobs.d.ts +1 -1
  36. package/dist/commands/jobs.js +2 -2
  37. package/dist/commands/manage.d.ts +1 -1
  38. package/dist/commands/manage.js +12 -84
  39. package/dist/commands/plan.d.ts +1 -1
  40. package/dist/commands/plan.js +2 -2
  41. package/dist/commands/portfolio.d.ts +19 -3
  42. package/dist/commands/portfolio.js +747 -163
  43. package/dist/commands/rebalance.d.ts +1 -1
  44. package/dist/commands/rebalance.js +18 -12
  45. package/dist/commands/risk.js +2 -6
  46. package/dist/commands/settings.js +30 -53
  47. package/dist/commands/trade.js +16 -15
  48. package/dist/commands/wallet.d.ts +2 -1
  49. package/dist/commands/wallet.js +756 -28
  50. package/dist/errors.d.ts +64 -0
  51. package/dist/errors.js +82 -2
  52. package/dist/event-stream.js +12 -1
  53. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  54. package/dist/exchanges/aster-typed-data.js +189 -0
  55. package/dist/exchanges/aster.d.ts +60 -11
  56. package/dist/exchanges/aster.js +278 -136
  57. package/dist/exchanges/hyperliquid.d.ts +14 -0
  58. package/dist/exchanges/hyperliquid.js +84 -34
  59. package/dist/exchanges/lighter-spot.js +14 -3
  60. package/dist/exchanges/lighter.d.ts +66 -0
  61. package/dist/exchanges/lighter.js +199 -27
  62. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  63. package/dist/exchanges/pacifica-typed-data.js +74 -0
  64. package/dist/exchanges/pacifica.d.ts +29 -1
  65. package/dist/exchanges/pacifica.js +94 -6
  66. package/dist/funding/rates.js +59 -21
  67. package/dist/guardrail/perp-guardrail.js +9 -1
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +268 -360
  70. package/dist/jobs.js +3 -1
  71. package/dist/mcp-server.js +85 -83
  72. package/dist/position-history.js +47 -4
  73. package/dist/settings.d.ts +67 -3
  74. package/dist/settings.js +1 -2
  75. package/dist/signer/ows-evm.d.ts +14 -0
  76. package/dist/signer/ows-evm.js +28 -9
  77. package/dist/smart-order.d.ts +8 -2
  78. package/dist/smart-order.js +46 -11
  79. package/dist/strategies/funding-arb.js +29 -8
  80. package/dist/trade-validator.js +30 -5
  81. package/dist/utils.d.ts +10 -0
  82. package/dist/utils.js +19 -0
  83. package/package.json +2 -2
  84. package/skills/perp-cli/SKILL.md +23 -14
  85. package/skills/perp-cli/references/agent-operations.md +6 -4
  86. package/skills/perp-cli/references/commands.md +55 -34
  87. package/skills/perp-cli/scripts/preflight.sh +4 -4
  88. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  89. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  90. package/dist/__tests__/alert-logic.test.js +0 -107
  91. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  92. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  93. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  94. package/dist/__tests__/arb-history-stats.test.js +0 -176
  95. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  96. package/dist/__tests__/arb-logic.test.js +0 -84
  97. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  98. package/dist/__tests__/arb-manage.test.js +0 -253
  99. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  100. package/dist/__tests__/arb-new-features.test.js +0 -457
  101. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  102. package/dist/__tests__/arb-sizing.test.js +0 -48
  103. package/dist/__tests__/arb-state.test.d.ts +0 -1
  104. package/dist/__tests__/arb-state.test.js +0 -284
  105. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  106. package/dist/__tests__/arb-userflow.test.js +0 -945
  107. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  108. package/dist/__tests__/arb-utils.test.js +0 -264
  109. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  110. package/dist/__tests__/bot-conditions.test.js +0 -341
  111. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  112. package/dist/__tests__/client-id-tracker.test.js +0 -137
  113. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  114. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  115. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  116. package/dist/__tests__/commands/order-intent.test.js +0 -600
  117. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  118. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  119. package/dist/__tests__/config.test.d.ts +0 -1
  120. package/dist/__tests__/config.test.js +0 -86
  121. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  122. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  123. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  124. package/dist/__tests__/dex-asset-map.test.js +0 -191
  125. package/dist/__tests__/errors.test.d.ts +0 -1
  126. package/dist/__tests__/errors.test.js +0 -110
  127. package/dist/__tests__/event-stream.test.d.ts +0 -1
  128. package/dist/__tests__/event-stream.test.js +0 -276
  129. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  130. package/dist/__tests__/exchanges/interface.test.js +0 -132
  131. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  132. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  133. package/dist/__tests__/execution-log.test.d.ts +0 -1
  134. package/dist/__tests__/execution-log.test.js +0 -106
  135. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  136. package/dist/__tests__/funding-calc.test.js +0 -71
  137. package/dist/__tests__/funding-history.test.d.ts +0 -1
  138. package/dist/__tests__/funding-history.test.js +0 -343
  139. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  140. package/dist/__tests__/funding-rates.test.js +0 -342
  141. package/dist/__tests__/funding.test.d.ts +0 -1
  142. package/dist/__tests__/funding.test.js +0 -173
  143. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  144. package/dist/__tests__/gap-logic.test.js +0 -43
  145. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  146. package/dist/__tests__/hip3-dex.test.js +0 -234
  147. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  148. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  149. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  150. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  151. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  152. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  153. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  154. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  155. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  156. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  157. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  158. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  159. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  160. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  161. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  162. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  163. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  165. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  166. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  167. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  169. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  170. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  171. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  173. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  175. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  177. package/dist/__tests__/liquidity.test.d.ts +0 -1
  178. package/dist/__tests__/liquidity.test.js +0 -225
  179. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  180. package/dist/__tests__/plan-executor.test.js +0 -314
  181. package/dist/__tests__/position-history.test.d.ts +0 -1
  182. package/dist/__tests__/position-history.test.js +0 -367
  183. package/dist/__tests__/retry.test.d.ts +0 -1
  184. package/dist/__tests__/retry.test.js +0 -310
  185. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  186. package/dist/__tests__/risk-assessment.test.js +0 -288
  187. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  188. package/dist/__tests__/security-adversarial.test.js +0 -574
  189. package/dist/__tests__/strategies.test.d.ts +0 -1
  190. package/dist/__tests__/strategies.test.js +0 -539
  191. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  192. package/dist/__tests__/trade-execution.test.js +0 -129
  193. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  194. package/dist/__tests__/trade-validator.test.js +0 -655
  195. package/dist/__tests__/utils.test.d.ts +0 -1
  196. package/dist/__tests__/utils.test.js +0 -76
  197. package/dist/arb/reconcile.d.ts +0 -28
  198. package/dist/arb/reconcile.js +0 -162
  199. package/dist/commands/alert.d.ts +0 -2
  200. package/dist/commands/alert.js +0 -421
  201. package/dist/commands/analytics.d.ts +0 -3
  202. package/dist/commands/analytics.js +0 -311
  203. package/dist/commands/arb-manage.d.ts +0 -5
  204. package/dist/commands/arb-manage.js +0 -5
  205. package/dist/commands/dashboard.d.ts +0 -3
  206. package/dist/commands/dashboard.js +0 -176
  207. package/dist/commands/deposit.d.ts +0 -4
  208. package/dist/commands/deposit.js +0 -573
  209. package/dist/commands/dex.d.ts +0 -3
  210. package/dist/commands/dex.js +0 -120
  211. package/dist/commands/env.d.ts +0 -2
  212. package/dist/commands/env.js +0 -136
  213. package/dist/commands/funding.d.ts +0 -3
  214. package/dist/commands/funding.js +0 -554
  215. package/dist/commands/gap.d.ts +0 -2
  216. package/dist/commands/gap.js +0 -305
  217. package/dist/commands/health.d.ts +0 -2
  218. package/dist/commands/health.js +0 -67
  219. package/dist/commands/multileg.d.ts +0 -16
  220. package/dist/commands/multileg.js +0 -174
  221. package/dist/commands/ows.d.ts +0 -2
  222. package/dist/commands/ows.js +0 -887
  223. package/dist/commands/pnl.d.ts +0 -12
  224. package/dist/commands/pnl.js +0 -243
  225. package/dist/commands/run.d.ts +0 -3
  226. package/dist/commands/run.js +0 -202
  227. package/dist/commands/stream.d.ts +0 -5
  228. package/dist/commands/stream.js +0 -200
  229. package/dist/commands/withdraw.d.ts +0 -3
  230. package/dist/commands/withdraw.js +0 -187
  231. package/dist/exchanges/edgex.d.ts +0 -62
  232. package/dist/exchanges/edgex.js +0 -552
  233. package/dist/exchanges/nado.d.ts +0 -52
  234. package/dist/exchanges/nado.js +0 -223
  235. package/dist/funding/index.d.ts +0 -3
  236. package/dist/funding/index.js +0 -3
  237. package/dist/retry.d.ts +0 -74
  238. package/dist/retry.js +0 -129
  239. package/dist/ws/market-feed.d.ts +0 -74
  240. package/dist/ws/market-feed.js +0 -227
@@ -1,253 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { existsSync, unlinkSync, readFileSync, writeFileSync, renameSync } from "fs";
3
- import { resolve } from "path";
4
- import { logExecution, readExecutionLog } from "../execution-log.js";
5
- const PERP_DIR = resolve(process.env.HOME || "~", ".perp");
6
- const LOG_FILE = resolve(PERP_DIR, "executions.jsonl");
7
- const BACKUP_FILE = resolve(PERP_DIR, "executions.jsonl.arb-manage-backup");
8
- function detectArbPairs(positions) {
9
- const bySymbol = new Map();
10
- for (const p of positions) {
11
- const key = p.symbol.toUpperCase();
12
- if (!bySymbol.has(key))
13
- bySymbol.set(key, []);
14
- bySymbol.get(key).push(p);
15
- }
16
- const pairs = [];
17
- for (const [symbol, pos] of bySymbol) {
18
- const longs = pos.filter(p => p.side === "long");
19
- const shorts = pos.filter(p => p.side === "short");
20
- for (const l of longs) {
21
- for (const s of shorts) {
22
- if (l.exchange !== s.exchange) {
23
- pairs.push({ symbol, longExchange: l.exchange, shortExchange: s.exchange, longPos: l, shortPos: s });
24
- }
25
- }
26
- }
27
- }
28
- return pairs;
29
- }
30
- // ── Tests ──
31
- describe("Arb pair detection", () => {
32
- it("matches positions on different exchanges with opposite sides", () => {
33
- const positions = [
34
- { exchange: "hyperliquid", symbol: "BTC", side: "long", size: 0.1, entryPrice: 100000, markPrice: 101000, unrealizedPnl: 100 },
35
- { exchange: "lighter", symbol: "BTC", side: "short", size: 0.1, entryPrice: 100500, markPrice: 101000, unrealizedPnl: -50 },
36
- ];
37
- const pairs = detectArbPairs(positions);
38
- expect(pairs).toHaveLength(1);
39
- expect(pairs[0].symbol).toBe("BTC");
40
- expect(pairs[0].longExchange).toBe("hyperliquid");
41
- expect(pairs[0].shortExchange).toBe("lighter");
42
- });
43
- it("does not match positions on the same exchange", () => {
44
- const positions = [
45
- { exchange: "hyperliquid", symbol: "ETH", side: "long", size: 1, entryPrice: 3000, markPrice: 3100, unrealizedPnl: 100 },
46
- { exchange: "hyperliquid", symbol: "ETH", side: "short", size: 1, entryPrice: 3050, markPrice: 3100, unrealizedPnl: -50 },
47
- ];
48
- const pairs = detectArbPairs(positions);
49
- expect(pairs).toHaveLength(0);
50
- });
51
- it("does not match positions with same side", () => {
52
- const positions = [
53
- { exchange: "hyperliquid", symbol: "SOL", side: "long", size: 10, entryPrice: 150, markPrice: 155, unrealizedPnl: 50 },
54
- { exchange: "lighter", symbol: "SOL", side: "long", size: 10, entryPrice: 151, markPrice: 155, unrealizedPnl: 40 },
55
- ];
56
- const pairs = detectArbPairs(positions);
57
- expect(pairs).toHaveLength(0);
58
- });
59
- it("returns empty for no positions", () => {
60
- const pairs = detectArbPairs([]);
61
- expect(pairs).toHaveLength(0);
62
- });
63
- it("matches multiple arb pairs for different symbols", () => {
64
- const positions = [
65
- { exchange: "hyperliquid", symbol: "BTC", side: "long", size: 0.1, entryPrice: 100000, markPrice: 101000, unrealizedPnl: 100 },
66
- { exchange: "lighter", symbol: "BTC", side: "short", size: 0.1, entryPrice: 100500, markPrice: 101000, unrealizedPnl: -50 },
67
- { exchange: "pacifica", symbol: "ETH", side: "long", size: 1, entryPrice: 3000, markPrice: 3100, unrealizedPnl: 100 },
68
- { exchange: "hyperliquid", symbol: "ETH", side: "short", size: 1, entryPrice: 3050, markPrice: 3100, unrealizedPnl: -50 },
69
- ];
70
- const pairs = detectArbPairs(positions);
71
- expect(pairs).toHaveLength(2);
72
- const symbols = pairs.map(p => p.symbol).sort();
73
- expect(symbols).toEqual(["BTC", "ETH"]);
74
- });
75
- it("handles case-insensitive symbol matching", () => {
76
- const positions = [
77
- { exchange: "hyperliquid", symbol: "btc", side: "long", size: 0.1, entryPrice: 100000, markPrice: 101000, unrealizedPnl: 100 },
78
- { exchange: "lighter", symbol: "BTC", side: "short", size: 0.1, entryPrice: 100500, markPrice: 101000, unrealizedPnl: -50 },
79
- ];
80
- const pairs = detectArbPairs(positions);
81
- expect(pairs).toHaveLength(1);
82
- });
83
- });
84
- describe("Arb close dry-run", () => {
85
- it("dry-run flag prevents execution", () => {
86
- // Simulating dry-run behavior: the flag should be passed through
87
- const dryRun = true;
88
- const executed = [];
89
- // In dry-run mode, no orders should be placed
90
- if (!dryRun) {
91
- executed.push("sell");
92
- executed.push("buy");
93
- }
94
- expect(executed).toHaveLength(0);
95
- });
96
- it("produces correct actions for close", () => {
97
- const longPos = { exchange: "hyperliquid", symbol: "BTC", rawSymbol: "BTC", side: "long", size: 0.1 };
98
- const shortPos = { exchange: "lighter", symbol: "BTC", rawSymbol: "BTC-PERP", side: "short", size: 0.1 };
99
- const actions = [
100
- { exchange: longPos.exchange, action: "sell", symbol: longPos.rawSymbol, size: String(longPos.size) },
101
- { exchange: shortPos.exchange, action: "buy", symbol: shortPos.rawSymbol, size: String(shortPos.size) },
102
- ];
103
- expect(actions).toHaveLength(2);
104
- expect(actions[0].action).toBe("sell"); // close long = sell
105
- expect(actions[1].action).toBe("buy"); // close short = buy
106
- expect(actions[0].exchange).toBe("hyperliquid");
107
- expect(actions[1].exchange).toBe("lighter");
108
- });
109
- });
110
- describe("Arb history grouping and stats", () => {
111
- beforeEach(() => {
112
- if (existsSync(LOG_FILE)) {
113
- const content = readFileSync(LOG_FILE, "utf-8");
114
- writeFileSync(BACKUP_FILE, content);
115
- }
116
- if (existsSync(LOG_FILE))
117
- unlinkSync(LOG_FILE);
118
- });
119
- afterEach(() => {
120
- if (existsSync(LOG_FILE))
121
- unlinkSync(LOG_FILE);
122
- if (existsSync(BACKUP_FILE)) {
123
- renameSync(BACKUP_FILE, LOG_FILE);
124
- }
125
- });
126
- it("groups arb entries and closes by symbol", () => {
127
- logExecution({
128
- type: "arb_entry", exchange: "hyperliquid+lighter",
129
- symbol: "BTC", side: "entry", size: "0.1",
130
- status: "success", dryRun: false,
131
- meta: { longExchange: "hyperliquid", shortExchange: "lighter", spread: 35, markPrice: 100000 },
132
- });
133
- logExecution({
134
- type: "arb_close", exchange: "hyperliquid+lighter",
135
- symbol: "BTC", side: "close", size: "0.1",
136
- status: "success", dryRun: false,
137
- meta: { longExchange: "hyperliquid", shortExchange: "lighter", currentSpread: 3, unrealizedPnl: 5.5, netPnl: 4.2 },
138
- });
139
- const entries = readExecutionLog({ type: "arb_entry" });
140
- const closes = readExecutionLog({ type: "arb_close" });
141
- expect(entries).toHaveLength(1);
142
- expect(closes).toHaveLength(1);
143
- expect(entries[0].symbol).toBe("BTC");
144
- expect(closes[0].symbol).toBe("BTC");
145
- expect(entries[0].meta?.spread).toBe(35);
146
- expect(closes[0].meta?.netPnl).toBe(4.2);
147
- });
148
- it("calculates trade stats from multiple entries", () => {
149
- // Trade 1: winner
150
- logExecution({
151
- type: "arb_entry", exchange: "hyperliquid+lighter",
152
- symbol: "BTC", side: "entry", size: "0.1",
153
- status: "success", dryRun: false,
154
- meta: { spread: 40, markPrice: 100000 },
155
- });
156
- logExecution({
157
- type: "arb_close", exchange: "hyperliquid+lighter",
158
- symbol: "BTC", side: "close", size: "0.1",
159
- status: "success", dryRun: false,
160
- meta: { unrealizedPnl: 10, netPnl: 8 },
161
- });
162
- // Trade 2: loser
163
- logExecution({
164
- type: "arb_entry", exchange: "pacifica+hyperliquid",
165
- symbol: "ETH", side: "entry", size: "1.0",
166
- status: "success", dryRun: false,
167
- meta: { spread: 25, markPrice: 3000 },
168
- });
169
- logExecution({
170
- type: "arb_close", exchange: "pacifica+hyperliquid",
171
- symbol: "ETH", side: "close", size: "1.0",
172
- status: "success", dryRun: false,
173
- meta: { unrealizedPnl: -5, netPnl: -7 },
174
- });
175
- const allEntries = readExecutionLog()
176
- .filter(r => r.type === "arb_entry" || r.type === "arb_close");
177
- const entries = allEntries.filter(r => r.type === "arb_entry");
178
- const closes = allEntries.filter(r => r.type === "arb_close");
179
- expect(entries).toHaveLength(2);
180
- expect(closes).toHaveLength(2);
181
- // Verify we can compute stats
182
- const completedNetPnls = closes
183
- .filter(c => c.status === "success" && c.meta?.netPnl !== undefined)
184
- .map(c => Number(c.meta.netPnl));
185
- expect(completedNetPnls).toHaveLength(2);
186
- const totalNetPnl = completedNetPnls.reduce((s, n) => s + n, 0);
187
- expect(totalNetPnl).toBe(1); // 8 + (-7) = 1
188
- const winners = completedNetPnls.filter(n => n > 0);
189
- expect(winners).toHaveLength(1);
190
- const winRate = (winners.length / completedNetPnls.length) * 100;
191
- expect(winRate).toBe(50);
192
- });
193
- it("handles entries without matching closes (open trades)", () => {
194
- logExecution({
195
- type: "arb_entry", exchange: "hyperliquid+lighter",
196
- symbol: "SOL", side: "entry", size: "10",
197
- status: "success", dryRun: false,
198
- meta: { spread: 30, markPrice: 150 },
199
- });
200
- const entries = readExecutionLog({ type: "arb_entry" });
201
- const closes = readExecutionLog({ type: "arb_close" });
202
- expect(entries).toHaveLength(1);
203
- expect(closes).toHaveLength(0);
204
- // Should be treated as an open trade
205
- const symbol = entries[0].symbol;
206
- const matchingClose = closes.find(c => c.symbol === symbol);
207
- expect(matchingClose).toBeUndefined();
208
- });
209
- it("filters by period correctly", () => {
210
- // Log an entry with a very old timestamp manually
211
- const oldDate = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString();
212
- const recentDate = new Date().toISOString();
213
- logExecution({
214
- type: "arb_entry", exchange: "hyperliquid+lighter",
215
- symbol: "OLD_TRADE", side: "entry", size: "0.1",
216
- status: "success", dryRun: false,
217
- });
218
- logExecution({
219
- type: "arb_entry", exchange: "hyperliquid+lighter",
220
- symbol: "NEW_TRADE", side: "entry", size: "0.1",
221
- status: "success", dryRun: false,
222
- });
223
- // Read with 30-day filter: both are recent (logged just now), so both should appear
224
- const since30d = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
225
- const recent = readExecutionLog({ type: "arb_entry", since: since30d });
226
- expect(recent).toHaveLength(2);
227
- });
228
- });
229
- describe("Duration formatting", () => {
230
- function formatDuration(ms) {
231
- const hours = Math.floor(ms / (1000 * 60 * 60));
232
- const days = Math.floor(hours / 24);
233
- const remainingHours = hours % 24;
234
- if (days > 0)
235
- return `${days}d ${remainingHours}h`;
236
- if (hours > 0)
237
- return `${hours}h`;
238
- const minutes = Math.floor(ms / (1000 * 60));
239
- return `${minutes}m`;
240
- }
241
- it("formats minutes", () => {
242
- expect(formatDuration(30 * 60 * 1000)).toBe("30m");
243
- });
244
- it("formats hours", () => {
245
- expect(formatDuration(5 * 60 * 60 * 1000)).toBe("5h");
246
- });
247
- it("formats days and hours", () => {
248
- expect(formatDuration(26 * 60 * 60 * 1000)).toBe("1d 2h");
249
- });
250
- it("formats zero", () => {
251
- expect(formatDuration(0)).toBe("0m");
252
- });
253
- });
@@ -1 +0,0 @@
1
- export {};