@exagent/agent 0.3.3 → 0.3.4

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 (148) hide show
  1. package/dist/{chunk-F4TCYYTD.js → chunk-WTECTX2Z.js} +13 -1
  2. package/dist/cli.js +155 -78
  3. package/dist/index.js +1 -1
  4. package/package.json +1 -1
  5. package/src/cli.ts +122 -3
  6. package/src/config.ts +18 -1
  7. package/src/setup.ts +46 -64
  8. package/dist/chunk-25J5ZDKX.js +0 -5622
  9. package/dist/chunk-2ML4XS5X.js +0 -5626
  10. package/dist/chunk-2OKYNZ3J.js +0 -5622
  11. package/dist/chunk-2PAASZJN.js +0 -5132
  12. package/dist/chunk-2WAYVOLU.js +0 -5624
  13. package/dist/chunk-2XBVVY3I.js +0 -5621
  14. package/dist/chunk-37HCPVBZ.js +0 -2941
  15. package/dist/chunk-3DZAZBLF.js +0 -5652
  16. package/dist/chunk-3IXCKNSV.js +0 -5008
  17. package/dist/chunk-3MXFTRXB.js +0 -3326
  18. package/dist/chunk-4MURFLNJ.js +0 -5136
  19. package/dist/chunk-4UVMO6ZM.js +0 -6318
  20. package/dist/chunk-56YQROFU.js +0 -5639
  21. package/dist/chunk-5B3ZEGMD.js +0 -4330
  22. package/dist/chunk-5NU6FDDE.js +0 -6020
  23. package/dist/chunk-5WADKPJU.js +0 -1552
  24. package/dist/chunk-6LOIFEEV.js +0 -4895
  25. package/dist/chunk-6YIXPNL4.js +0 -5626
  26. package/dist/chunk-72HK2V74.js +0 -5224
  27. package/dist/chunk-7Q72QQIV.js +0 -4697
  28. package/dist/chunk-7V3XTBIF.js +0 -4896
  29. package/dist/chunk-A4CM4F7Q.js +0 -5633
  30. package/dist/chunk-ADXXR2MA.js +0 -4816
  31. package/dist/chunk-AG6CJPIC.js +0 -4895
  32. package/dist/chunk-AJPXHBTF.js +0 -4459
  33. package/dist/chunk-AQ6R37XV.js +0 -4809
  34. package/dist/chunk-AS4UEZMU.js +0 -5001
  35. package/dist/chunk-ASYMD22Y.js +0 -5624
  36. package/dist/chunk-AU4MCQCE.js +0 -5624
  37. package/dist/chunk-B4VHIITU.js +0 -5748
  38. package/dist/chunk-B6GVDNKQ.js +0 -5624
  39. package/dist/chunk-BCIAW6ZL.js +0 -5132
  40. package/dist/chunk-BJZ5PCG3.js +0 -5358
  41. package/dist/chunk-BS4J5QSM.js +0 -5622
  42. package/dist/chunk-BV2AUUX6.js +0 -2940
  43. package/dist/chunk-BWNSH2LK.js +0 -1574
  44. package/dist/chunk-C3GMBW3R.js +0 -5640
  45. package/dist/chunk-C6CVQGJ4.js +0 -5624
  46. package/dist/chunk-CGXKXNUJ.js +0 -5626
  47. package/dist/chunk-CIEZAYOU.js +0 -4701
  48. package/dist/chunk-CORZCEAQ.js +0 -5621
  49. package/dist/chunk-CS5LGZWP.js +0 -5357
  50. package/dist/chunk-CVT3KC24.js +0 -5624
  51. package/dist/chunk-D5MJ45R7.js +0 -3258
  52. package/dist/chunk-DSBRZ5DZ.js +0 -5624
  53. package/dist/chunk-E2X7JARQ.js +0 -4437
  54. package/dist/chunk-E3GY36ZP.js +0 -3258
  55. package/dist/chunk-E6NCIFKB.js +0 -4733
  56. package/dist/chunk-EOXLKW4D.js +0 -4895
  57. package/dist/chunk-FCI7LX4Q.js +0 -5624
  58. package/dist/chunk-FFJSKTOL.js +0 -4539
  59. package/dist/chunk-FOQYP3IB.js +0 -2950
  60. package/dist/chunk-GNEYTZDH.js +0 -4686
  61. package/dist/chunk-GPMXUMYH.js +0 -5991
  62. package/dist/chunk-GZWPAQPU.js +0 -4593
  63. package/dist/chunk-H5DXDKMX.js +0 -5619
  64. package/dist/chunk-HFQRTMS6.js +0 -3377
  65. package/dist/chunk-HQKRHX6Y.js +0 -5626
  66. package/dist/chunk-HTF3TNBY.js +0 -4834
  67. package/dist/chunk-IADSQBBY.js +0 -5523
  68. package/dist/chunk-IE2SXMZK.js +0 -4890
  69. package/dist/chunk-IGUQVJCB.js +0 -5622
  70. package/dist/chunk-IIREL7SL.js +0 -5615
  71. package/dist/chunk-IJK4EFTJ.js +0 -6043
  72. package/dist/chunk-J2MQ3Y5O.js +0 -5223
  73. package/dist/chunk-J3NG7AGT.js +0 -6047
  74. package/dist/chunk-JIBBZ3NV.js +0 -5132
  75. package/dist/chunk-JIPSBE6S.js +0 -5622
  76. package/dist/chunk-JPG755XK.js +0 -4589
  77. package/dist/chunk-JQBNL5GX.js +0 -5230
  78. package/dist/chunk-KS3F5WSX.js +0 -4831
  79. package/dist/chunk-KUYTQ4FR.js +0 -4808
  80. package/dist/chunk-KVP4CMJ5.js +0 -4711
  81. package/dist/chunk-LAR2I44B.js +0 -5626
  82. package/dist/chunk-LBTHSED2.js +0 -1531
  83. package/dist/chunk-M6OAMYVM.js +0 -5621
  84. package/dist/chunk-MFN5WWOY.js +0 -5132
  85. package/dist/chunk-MMTSKXLK.js +0 -5624
  86. package/dist/chunk-MPUSQLTH.js +0 -5626
  87. package/dist/chunk-MREXDTWL.js +0 -1555
  88. package/dist/chunk-MUEDKRFC.js +0 -5624
  89. package/dist/chunk-NOVPL2JH.js +0 -3327
  90. package/dist/chunk-NQIP4MHV.js +0 -4334
  91. package/dist/chunk-NXXKMYLS.js +0 -5624
  92. package/dist/chunk-OBYNZXNM.js +0 -4756
  93. package/dist/chunk-OFY4HBOJ.js +0 -5624
  94. package/dist/chunk-OJNUEZEK.js +0 -5602
  95. package/dist/chunk-OQCJOMUQ.js +0 -5624
  96. package/dist/chunk-OZH75GY6.js +0 -5132
  97. package/dist/chunk-P3IJVDMZ.js +0 -4700
  98. package/dist/chunk-PMYMYMBH.js +0 -4877
  99. package/dist/chunk-PRELNRVN.js +0 -5623
  100. package/dist/chunk-PSQUSNSI.js +0 -4703
  101. package/dist/chunk-QAIQ5IB6.js +0 -5624
  102. package/dist/chunk-QG22GADV.js +0 -6316
  103. package/dist/chunk-QNE2KGGK.js +0 -3315
  104. package/dist/chunk-RH7ZBSG4.js +0 -5132
  105. package/dist/chunk-RLD5MUCR.js +0 -5626
  106. package/dist/chunk-S42VEBNR.js +0 -3268
  107. package/dist/chunk-SEM6UXU4.js +0 -3324
  108. package/dist/chunk-SI5WP77M.js +0 -4430
  109. package/dist/chunk-SID4SQSY.js +0 -4837
  110. package/dist/chunk-SIELPKWF.js +0 -1558
  111. package/dist/chunk-SVBLY6QT.js +0 -5742
  112. package/dist/chunk-SVFTC5V2.js +0 -6021
  113. package/dist/chunk-SXHTX62B.js +0 -4823
  114. package/dist/chunk-T2YCEA5U.js +0 -4730
  115. package/dist/chunk-TARCHIOU.js +0 -4718
  116. package/dist/chunk-TDACLKD7.js +0 -5867
  117. package/dist/chunk-TGCBM3NP.js +0 -4890
  118. package/dist/chunk-TIWG6KAK.js +0 -4769
  119. package/dist/chunk-TKLKATVM.js +0 -1534
  120. package/dist/chunk-TSLZ4A5P.js +0 -5222
  121. package/dist/chunk-TWSDKORW.js +0 -4698
  122. package/dist/chunk-U5QHYVMJ.js +0 -3341
  123. package/dist/chunk-UAP5CTHB.js +0 -5985
  124. package/dist/chunk-UK6SEUWU.js +0 -3210
  125. package/dist/chunk-UKU5YO65.js +0 -5132
  126. package/dist/chunk-UOZQXP4Q.js +0 -5144
  127. package/dist/chunk-UPTN2TSS.js +0 -4727
  128. package/dist/chunk-UQT2APOE.js +0 -2944
  129. package/dist/chunk-V32QDZKW.js +0 -5132
  130. package/dist/chunk-VDK4XPAC.js +0 -6318
  131. package/dist/chunk-VKY2CDCD.js +0 -5622
  132. package/dist/chunk-VUCSYMCY.js +0 -3323
  133. package/dist/chunk-VVLNBD5Y.js +0 -5132
  134. package/dist/chunk-W3TQ22O6.js +0 -4459
  135. package/dist/chunk-WA4DSGOM.js +0 -3355
  136. package/dist/chunk-WI6MIICK.js +0 -4687
  137. package/dist/chunk-XHYHRJMK.js +0 -6319
  138. package/dist/chunk-XRHJLL74.js +0 -4893
  139. package/dist/chunk-XXWXEBJQ.js +0 -4885
  140. package/dist/chunk-YC6TH2H3.js +0 -5624
  141. package/dist/chunk-YDH6HCUJ.js +0 -5624
  142. package/dist/chunk-YJD35VKQ.js +0 -4890
  143. package/dist/chunk-YTZ5MZLP.js +0 -6318
  144. package/dist/chunk-ZBIQJBY7.js +0 -5620
  145. package/dist/chunk-ZKTSA2AE.js +0 -5629
  146. package/dist/chunk-ZKZZL3PE.js +0 -3379
  147. package/dist/chunk-ZM5KCPRK.js +0 -4541
  148. package/dist/chunk-ZTYPDSE3.js +0 -3258
@@ -1,2950 +0,0 @@
1
- // src/config.ts
2
- import { readFileSync, existsSync, writeFileSync } from "fs";
3
- import { z } from "zod";
4
- var configSchema = z.object({
5
- agentId: z.string(),
6
- apiUrl: z.string().url(),
7
- apiToken: z.string(),
8
- wallet: z.object({
9
- privateKey: z.string()
10
- }).optional(),
11
- llm: z.object({
12
- provider: z.enum(["openai", "anthropic", "google", "deepseek", "mistral", "groq", "together", "ollama"]),
13
- model: z.string().optional(),
14
- apiKey: z.string().optional(),
15
- endpoint: z.string().optional(),
16
- temperature: z.number().min(0).max(2).optional(),
17
- maxTokens: z.number().optional()
18
- }),
19
- strategy: z.object({
20
- file: z.string().optional(),
21
- template: z.string().optional()
22
- }),
23
- trading: z.object({
24
- mode: z.enum(["live", "paper"]).default("paper"),
25
- timeHorizon: z.enum(["intraday", "swing", "position"]).default("swing"),
26
- maxPositionSizeBps: z.number().min(100).max(1e4).default(2e3),
27
- maxDailyLossBps: z.number().min(0).max(1e4).default(500),
28
- maxConcurrentPositions: z.number().min(1).max(100).default(5),
29
- tradingIntervalMs: z.number().min(1e3).default(6e4),
30
- maxSlippageBps: z.number().min(10).max(1e3).default(100),
31
- minTradeValueUSD: z.number().min(0).default(10),
32
- initialCapitalUSD: z.number().optional()
33
- }),
34
- venues: z.object({
35
- hyperliquid_perp: z.object({
36
- enabled: z.boolean().default(false),
37
- apiUrl: z.string().default("https://api.hyperliquid.xyz"),
38
- wsUrl: z.string().default("wss://api.hyperliquid.xyz/ws"),
39
- maxLeverage: z.number().min(1).max(50).default(10),
40
- maxNotionalUSD: z.number().default(5e4),
41
- allowedInstruments: z.array(z.string()).optional()
42
- }).optional(),
43
- polymarket: z.object({
44
- enabled: z.boolean().default(false),
45
- clobApiUrl: z.string().default("https://clob.polymarket.com"),
46
- gammaApiUrl: z.string().default("https://gamma-api.polymarket.com"),
47
- maxNotionalUSD: z.number().default(1e3),
48
- maxTotalExposureUSD: z.number().default(5e3),
49
- allowedCategories: z.array(z.string()).optional()
50
- }).optional()
51
- }).optional(),
52
- relay: z.object({
53
- url: z.string(),
54
- heartbeatIntervalMs: z.number().default(3e4),
55
- reconnectMaxAttempts: z.number().default(50)
56
- })
57
- });
58
- function loadConfig(path = "agent-config.json") {
59
- if (!existsSync(path)) {
60
- throw new Error(`Config file not found: ${path}. Run 'exagent init' first.`);
61
- }
62
- const raw = readFileSync(path, "utf-8");
63
- let parsed;
64
- try {
65
- parsed = JSON.parse(raw);
66
- } catch {
67
- throw new Error(`Invalid JSON in ${path}`);
68
- }
69
- const config = parsed;
70
- const llm = config.llm || {};
71
- if (process.env.EXAGENT_LLM_PROVIDER) llm.provider = process.env.EXAGENT_LLM_PROVIDER;
72
- if (process.env.EXAGENT_LLM_MODEL) llm.model = process.env.EXAGENT_LLM_MODEL;
73
- if (process.env.EXAGENT_LLM_API_KEY) llm.apiKey = process.env.EXAGENT_LLM_API_KEY;
74
- if (process.env.EXAGENT_API_URL) config.apiUrl = process.env.EXAGENT_API_URL;
75
- if (process.env.EXAGENT_API_TOKEN) config.apiToken = process.env.EXAGENT_API_TOKEN;
76
- if (process.env.EXAGENT_WALLET_PRIVATE_KEY) {
77
- config.wallet = { privateKey: process.env.EXAGENT_WALLET_PRIVATE_KEY };
78
- }
79
- config.llm = llm;
80
- const result = configSchema.safeParse(config);
81
- if (!result.success) {
82
- const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
83
- throw new Error(`Invalid config:
84
- ${issues}`);
85
- }
86
- return result.data;
87
- }
88
- function generateSampleConfig(agentId, apiUrl) {
89
- const config = {
90
- agentId,
91
- apiUrl,
92
- apiToken: "<your-jwt-token>",
93
- wallet: {
94
- privateKey: "<your-hex-private-key>"
95
- },
96
- llm: {
97
- provider: "openai",
98
- model: "gpt-4o",
99
- apiKey: "<your-api-key>"
100
- },
101
- strategy: {
102
- template: "momentum"
103
- },
104
- trading: {
105
- mode: "paper",
106
- timeHorizon: "swing",
107
- maxPositionSizeBps: 2e3,
108
- maxDailyLossBps: 500,
109
- maxConcurrentPositions: 5,
110
- tradingIntervalMs: 6e4,
111
- maxSlippageBps: 100,
112
- minTradeValueUSD: 10,
113
- initialCapitalUSD: 1e4
114
- },
115
- venues: {
116
- hyperliquid_perp: {
117
- enabled: false,
118
- apiUrl: "https://api.hyperliquid.xyz",
119
- wsUrl: "wss://api.hyperliquid.xyz/ws",
120
- maxLeverage: 10,
121
- maxNotionalUSD: 5e4
122
- },
123
- polymarket: {
124
- enabled: false,
125
- clobApiUrl: "https://clob.polymarket.com",
126
- gammaApiUrl: "https://gamma-api.polymarket.com",
127
- maxNotionalUSD: 1e3,
128
- maxTotalExposureUSD: 5e3
129
- }
130
- },
131
- relay: {
132
- url: apiUrl.replace(/^http/, "ws") + "/ws/agent",
133
- heartbeatIntervalMs: 3e4,
134
- reconnectMaxAttempts: 50
135
- }
136
- };
137
- return JSON.stringify(config, null, 2);
138
- }
139
- function writeSampleConfig(agentId, apiUrl, path = "agent-config.json") {
140
- writeFileSync(path, generateSampleConfig(agentId, apiUrl));
141
- }
142
-
143
- // src/relay.ts
144
- import WebSocket from "ws";
145
- var RelayClient = class {
146
- ws = null;
147
- config;
148
- heartbeatTimer = null;
149
- reconnectTimer = null;
150
- reconnectAttempts = 0;
151
- intentionalClose = false;
152
- authenticated = false;
153
- lastStatus = null;
154
- onCommand;
155
- onConnected;
156
- onDisconnected;
157
- constructor(config) {
158
- this.config = {
159
- url: config.url,
160
- agentId: config.agentId,
161
- token: config.token,
162
- heartbeatIntervalMs: config.heartbeatIntervalMs ?? 3e4,
163
- reconnectMaxAttempts: config.reconnectMaxAttempts ?? 50
164
- };
165
- this.onCommand = config.onCommand;
166
- this.onConnected = config.onConnected;
167
- this.onDisconnected = config.onDisconnected;
168
- }
169
- get isConnected() {
170
- return this.ws?.readyState === WebSocket.OPEN && this.authenticated;
171
- }
172
- async connect() {
173
- return new Promise((resolve2, reject) => {
174
- try {
175
- this.intentionalClose = false;
176
- this.ws = new WebSocket(this.config.url);
177
- const timeout = setTimeout(() => {
178
- if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
179
- this.ws.terminate();
180
- reject(new Error("Connection timeout (15s)"));
181
- }
182
- }, 15e3);
183
- this.ws.on("open", () => {
184
- clearTimeout(timeout);
185
- this.reconnectAttempts = 0;
186
- this.authenticate();
187
- });
188
- this.ws.on("message", (data) => {
189
- try {
190
- const msg = JSON.parse(data.toString());
191
- this.handleMessage(msg, resolve2);
192
- } catch {
193
- console.error("[relay] Failed to parse message");
194
- }
195
- });
196
- this.ws.on("close", () => {
197
- this.cleanup();
198
- this.onDisconnected?.();
199
- if (!this.intentionalClose) {
200
- this.scheduleReconnect();
201
- }
202
- });
203
- this.ws.on("error", (err) => {
204
- console.error("[relay] WebSocket error:", err.message);
205
- if (!this.authenticated) {
206
- clearTimeout(timeout);
207
- reject(err);
208
- }
209
- });
210
- } catch (err) {
211
- reject(err);
212
- }
213
- });
214
- }
215
- disconnect() {
216
- this.intentionalClose = true;
217
- this.cleanup();
218
- if (this.ws) {
219
- this.ws.close();
220
- this.ws = null;
221
- }
222
- }
223
- authenticate() {
224
- this.send({
225
- type: "auth",
226
- agentId: this.config.agentId,
227
- token: this.config.token
228
- });
229
- }
230
- handleMessage(msg, onAuth) {
231
- switch (msg.type) {
232
- case "auth_success":
233
- this.authenticated = true;
234
- this.startHeartbeat();
235
- this.onConnected?.();
236
- onAuth?.();
237
- console.log(`[relay] Authenticated as agent ${this.config.agentId}`);
238
- break;
239
- case "auth_error":
240
- console.error("[relay] Auth failed:", msg.error);
241
- this.disconnect();
242
- break;
243
- case "command":
244
- this.onCommand?.(msg.command);
245
- break;
246
- case "pong":
247
- break;
248
- }
249
- }
250
- send(msg) {
251
- if (this.ws?.readyState === WebSocket.OPEN) {
252
- this.ws.send(JSON.stringify(msg));
253
- }
254
- }
255
- sendHeartbeat(status) {
256
- this.lastStatus = status;
257
- this.send({
258
- type: "heartbeat",
259
- agentId: this.config.agentId,
260
- status,
261
- timestamp: Date.now()
262
- });
263
- }
264
- sendMessage(messageType, level, title, body, data) {
265
- this.send({
266
- type: "message",
267
- agentId: this.config.agentId,
268
- messageType,
269
- level,
270
- title,
271
- body,
272
- data,
273
- timestamp: Date.now()
274
- });
275
- }
276
- sendTradeSignal(signal) {
277
- this.send({
278
- type: "trade_signal",
279
- agentId: this.config.agentId,
280
- signal,
281
- timestamp: Date.now()
282
- });
283
- }
284
- startHeartbeat() {
285
- this.stopHeartbeat();
286
- this.heartbeatTimer = setInterval(() => {
287
- if (this.lastStatus) {
288
- this.sendHeartbeat(this.lastStatus);
289
- }
290
- }, this.config.heartbeatIntervalMs);
291
- }
292
- stopHeartbeat() {
293
- if (this.heartbeatTimer) {
294
- clearInterval(this.heartbeatTimer);
295
- this.heartbeatTimer = null;
296
- }
297
- }
298
- cleanup() {
299
- this.stopHeartbeat();
300
- this.authenticated = false;
301
- if (this.reconnectTimer) {
302
- clearTimeout(this.reconnectTimer);
303
- this.reconnectTimer = null;
304
- }
305
- }
306
- scheduleReconnect() {
307
- if (this.reconnectAttempts >= this.config.reconnectMaxAttempts) {
308
- console.error(`[relay] Max reconnect attempts (${this.config.reconnectMaxAttempts}) reached`);
309
- return;
310
- }
311
- const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
312
- this.reconnectAttempts++;
313
- console.log(`[relay] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
314
- this.reconnectTimer = setTimeout(async () => {
315
- try {
316
- await this.connect();
317
- } catch (err) {
318
- console.error("[relay] Reconnect failed:", err.message);
319
- }
320
- }, delay);
321
- }
322
- };
323
-
324
- // src/signal.ts
325
- var SignalReporter = class {
326
- relay;
327
- constructor(relay) {
328
- this.relay = relay;
329
- }
330
- reportTrade(signal) {
331
- if (!this.relay.isConnected) {
332
- console.warn("[signal] Not connected to relay \u2014 trade signal queued locally");
333
- return;
334
- }
335
- this.relay.sendTradeSignal(signal);
336
- this.relay.sendMessage(
337
- "trade_executed",
338
- "success",
339
- `${signal.side.toUpperCase()} ${signal.symbol}`,
340
- `${signal.side} ${signal.size} ${signal.symbol} @ $${signal.price.toFixed(2)} on ${signal.venue}`,
341
- { signal }
342
- );
343
- }
344
- reportPerpFill(signal) {
345
- if (!this.relay.isConnected) return;
346
- this.relay.sendTradeSignal(signal);
347
- this.relay.sendMessage(
348
- "perp_fill",
349
- "success",
350
- `Perp ${signal.side.toUpperCase()} ${signal.symbol}`,
351
- `${signal.side} ${signal.size} ${signal.symbol} @ $${signal.price.toFixed(2)} (${signal.leverage ?? 1}x)`,
352
- { signal }
353
- );
354
- }
355
- reportPredictionFill(signal) {
356
- if (!this.relay.isConnected) return;
357
- this.relay.sendTradeSignal(signal);
358
- this.relay.sendMessage(
359
- "prediction_fill",
360
- "success",
361
- `Prediction ${signal.side.toUpperCase()}`,
362
- `${signal.side} $${signal.size.toFixed(2)} on ${signal.symbol} @ ${signal.price.toFixed(4)}`,
363
- { signal }
364
- );
365
- }
366
- reportError(title, body, data) {
367
- if (!this.relay.isConnected) return;
368
- this.relay.sendMessage("error", "error", title, body, data);
369
- }
370
- reportInfo(title, body, data) {
371
- if (!this.relay.isConnected) return;
372
- this.relay.sendMessage("info", "info", title, body, data);
373
- }
374
- };
375
-
376
- // src/store.ts
377
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
378
- import { dirname } from "path";
379
- var FileStore = class {
380
- data = {};
381
- filePath;
382
- constructor(filePath = "data/strategy-store.json") {
383
- this.filePath = filePath;
384
- this.load();
385
- }
386
- load() {
387
- try {
388
- if (existsSync2(this.filePath)) {
389
- const raw = readFileSync2(this.filePath, "utf-8");
390
- this.data = JSON.parse(raw);
391
- }
392
- } catch {
393
- this.data = {};
394
- }
395
- }
396
- flush() {
397
- const dir = dirname(this.filePath);
398
- if (!existsSync2(dir)) {
399
- mkdirSync(dir, { recursive: true });
400
- }
401
- writeFileSync2(this.filePath, JSON.stringify(this.data, null, 2));
402
- }
403
- get(key) {
404
- return this.data[key];
405
- }
406
- set(key, value) {
407
- this.data[key] = value;
408
- this.flush();
409
- }
410
- delete(key) {
411
- delete this.data[key];
412
- this.flush();
413
- }
414
- keys() {
415
- return Object.keys(this.data);
416
- }
417
- };
418
-
419
- // src/position-tracker.ts
420
- var QUOTE_ASSETS = /* @__PURE__ */ new Set(["USDC", "USDbC", "DAI", "USDT", "EURC"]);
421
- var PERP_VENUES = /* @__PURE__ */ new Set(["hyperliquid_perp"]);
422
- var PREDICTION_VENUES = /* @__PURE__ */ new Set(["polymarket"]);
423
- var PositionTracker = class {
424
- positions = /* @__PURE__ */ new Map();
425
- trades = [];
426
- realizedPnL = 0;
427
- store;
428
- constructor(store) {
429
- this.store = store;
430
- this.load();
431
- }
432
- /** Build a unique position key: venue-scoped when venue is provided */
433
- positionKey(token, venue) {
434
- return venue ? `${venue}:${token}` : token;
435
- }
436
- load() {
437
- const saved = this.store.get("position_tracker");
438
- if (saved) {
439
- this.positions = new Map(saved.positions);
440
- this.trades = saved.trades;
441
- this.realizedPnL = saved.realizedPnL;
442
- }
443
- }
444
- save() {
445
- this.store.set("position_tracker", {
446
- positions: Array.from(this.positions.entries()),
447
- trades: this.trades.slice(-1e3),
448
- realizedPnL: this.realizedPnL
449
- });
450
- }
451
- recordBuy(token, quantity, price, fee, venue, chain, venueFillId) {
452
- const isPerp = venue ? PERP_VENUES.has(venue) : false;
453
- const isPrediction = venue ? PREDICTION_VENUES.has(venue) : false;
454
- if (!isPerp && !isPrediction && QUOTE_ASSETS.has(token)) return;
455
- const key = this.positionKey(token, venue);
456
- const existing = this.positions.get(key);
457
- if (existing) {
458
- if (isPerp && existing.quantity < 0) {
459
- const closedQty = Math.min(quantity, Math.abs(existing.quantity));
460
- const pnl = (existing.costBasisPerUnit - price) * closedQty - fee;
461
- this.realizedPnL += pnl;
462
- existing.quantity += quantity;
463
- if (Math.abs(existing.quantity) <= 1e-6) {
464
- this.positions.delete(key);
465
- } else if (existing.quantity > 0) {
466
- existing.costBasisPerUnit = price;
467
- }
468
- } else {
469
- const totalQty = existing.quantity + quantity;
470
- const totalCost = existing.costBasisPerUnit * existing.quantity + price * quantity;
471
- existing.costBasisPerUnit = totalCost / totalQty;
472
- existing.quantity = totalQty;
473
- }
474
- } else {
475
- this.positions.set(key, {
476
- token,
477
- quantity,
478
- costBasisPerUnit: price,
479
- entryTimestamp: Date.now(),
480
- venue,
481
- chain
482
- });
483
- }
484
- this.trades.push({
485
- token,
486
- action: "buy",
487
- quantity,
488
- price,
489
- fee,
490
- timestamp: Date.now(),
491
- venue,
492
- chain,
493
- venueFillId
494
- });
495
- this.save();
496
- }
497
- recordSell(token, quantity, price, fee, venue, chain, venueFillId) {
498
- const isPerp = venue ? PERP_VENUES.has(venue) : false;
499
- const isPrediction = venue ? PREDICTION_VENUES.has(venue) : false;
500
- if (!isPerp && !isPrediction && QUOTE_ASSETS.has(token)) return;
501
- const key = this.positionKey(token, venue);
502
- const existing = this.positions.get(key);
503
- if (existing) {
504
- if (isPerp && existing.quantity > 0) {
505
- const closedQty = Math.min(quantity, existing.quantity);
506
- const pnl = (price - existing.costBasisPerUnit) * closedQty - fee;
507
- this.realizedPnL += pnl;
508
- existing.quantity -= quantity;
509
- if (Math.abs(existing.quantity) <= 1e-6) {
510
- this.positions.delete(key);
511
- } else if (existing.quantity < 0) {
512
- existing.costBasisPerUnit = price;
513
- }
514
- } else if (isPerp && existing.quantity <= 0) {
515
- const totalQty = Math.abs(existing.quantity) + quantity;
516
- const totalCost = existing.costBasisPerUnit * Math.abs(existing.quantity) + price * quantity;
517
- existing.costBasisPerUnit = totalCost / totalQty;
518
- existing.quantity -= quantity;
519
- } else {
520
- const pnl = (price - existing.costBasisPerUnit) * quantity - fee;
521
- this.realizedPnL += pnl;
522
- existing.quantity -= quantity;
523
- if (existing.quantity <= 1e-6) {
524
- this.positions.delete(key);
525
- }
526
- }
527
- } else if (isPerp) {
528
- this.positions.set(key, {
529
- token,
530
- quantity: -quantity,
531
- costBasisPerUnit: price,
532
- entryTimestamp: Date.now(),
533
- venue,
534
- chain
535
- });
536
- }
537
- this.trades.push({
538
- token,
539
- action: "sell",
540
- quantity,
541
- price,
542
- fee,
543
- timestamp: Date.now(),
544
- venue,
545
- chain,
546
- venueFillId
547
- });
548
- this.save();
549
- }
550
- getSummary(prices) {
551
- let totalUnrealizedPnL = 0;
552
- const openPositions = Array.from(this.positions.values());
553
- for (const pos of openPositions) {
554
- const currentPrice = prices[pos.token];
555
- if (currentPrice) {
556
- if (pos.quantity >= 0) {
557
- totalUnrealizedPnL += (currentPrice - pos.costBasisPerUnit) * pos.quantity;
558
- } else {
559
- totalUnrealizedPnL += (pos.costBasisPerUnit - currentPrice) * Math.abs(pos.quantity);
560
- }
561
- }
562
- }
563
- return {
564
- openPositions,
565
- totalUnrealizedPnL,
566
- totalRealizedPnL: this.realizedPnL
567
- };
568
- }
569
- getPositions() {
570
- return Array.from(this.positions.values());
571
- }
572
- getTrades(limit) {
573
- if (limit) return this.trades.slice(-limit);
574
- return [...this.trades];
575
- }
576
- getRealizedPnL() {
577
- return this.realizedPnL;
578
- }
579
- reset() {
580
- this.positions.clear();
581
- this.trades = [];
582
- this.realizedPnL = 0;
583
- this.save();
584
- }
585
- };
586
-
587
- // src/llm/base.ts
588
- var BaseLLMAdapter = class {
589
- config;
590
- constructor(config) {
591
- this.config = config;
592
- }
593
- getTemperature() {
594
- return this.config.temperature ?? 0.7;
595
- }
596
- getMaxTokens() {
597
- return this.config.maxTokens ?? 4096;
598
- }
599
- };
600
-
601
- // src/llm/openai.ts
602
- var OpenAIAdapter = class extends BaseLLMAdapter {
603
- endpoint;
604
- constructor(config) {
605
- super(config);
606
- this.endpoint = config.endpoint || "https://api.openai.com/v1";
607
- }
608
- async chat(messages) {
609
- const res = await fetch(`${this.endpoint}/chat/completions`, {
610
- method: "POST",
611
- headers: {
612
- "Content-Type": "application/json",
613
- Authorization: `Bearer ${this.config.apiKey}`
614
- },
615
- body: JSON.stringify({
616
- model: this.config.model || "gpt-4o",
617
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
618
- temperature: this.getTemperature(),
619
- max_tokens: this.getMaxTokens()
620
- })
621
- });
622
- if (!res.ok) {
623
- const body = await res.text();
624
- throw new Error(`OpenAI API error ${res.status}: ${body}`);
625
- }
626
- const data = await res.json();
627
- return {
628
- content: data.choices[0]?.message?.content || "",
629
- tokens: data.usage ? { input: data.usage.prompt_tokens, output: data.usage.completion_tokens } : void 0
630
- };
631
- }
632
- getMetadata() {
633
- return {
634
- provider: "openai",
635
- model: this.config.model || "gpt-4o"
636
- };
637
- }
638
- };
639
-
640
- // src/llm/anthropic.ts
641
- var AnthropicAdapter = class extends BaseLLMAdapter {
642
- endpoint;
643
- constructor(config) {
644
- super(config);
645
- this.endpoint = config.endpoint || "https://api.anthropic.com";
646
- }
647
- async chat(messages) {
648
- const systemMessage = messages.find((m) => m.role === "system");
649
- const nonSystemMessages = messages.filter((m) => m.role !== "system");
650
- const body = {
651
- model: this.config.model || "claude-sonnet-4-20250514",
652
- messages: nonSystemMessages.map((m) => ({ role: m.role, content: m.content })),
653
- max_tokens: this.getMaxTokens(),
654
- temperature: this.getTemperature()
655
- };
656
- if (systemMessage) {
657
- body.system = systemMessage.content;
658
- }
659
- const res = await fetch(`${this.endpoint}/v1/messages`, {
660
- method: "POST",
661
- headers: {
662
- "Content-Type": "application/json",
663
- "x-api-key": this.config.apiKey || "",
664
- "anthropic-version": "2023-06-01"
665
- },
666
- body: JSON.stringify(body)
667
- });
668
- if (!res.ok) {
669
- const text = await res.text();
670
- throw new Error(`Anthropic API error ${res.status}: ${text}`);
671
- }
672
- const data = await res.json();
673
- const textContent = data.content.find((c) => c.type === "text");
674
- return {
675
- content: textContent?.text || "",
676
- tokens: data.usage ? { input: data.usage.input_tokens, output: data.usage.output_tokens } : void 0
677
- };
678
- }
679
- getMetadata() {
680
- return {
681
- provider: "anthropic",
682
- model: this.config.model || "claude-sonnet-4-20250514"
683
- };
684
- }
685
- };
686
-
687
- // src/llm/google.ts
688
- var GoogleAdapter = class extends BaseLLMAdapter {
689
- constructor(config) {
690
- super(config);
691
- }
692
- async chat(messages) {
693
- const model = this.config.model || "gemini-2.5-flash";
694
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${this.config.apiKey}`;
695
- const systemMessage = messages.find((m) => m.role === "system");
696
- const nonSystemMessages = messages.filter((m) => m.role !== "system");
697
- const body = {
698
- contents: nonSystemMessages.map((m) => ({
699
- role: m.role === "assistant" ? "model" : "user",
700
- parts: [{ text: m.content }]
701
- })),
702
- generationConfig: {
703
- temperature: this.getTemperature(),
704
- maxOutputTokens: this.getMaxTokens()
705
- }
706
- };
707
- if (systemMessage) {
708
- body.systemInstruction = { parts: [{ text: systemMessage.content }] };
709
- }
710
- const res = await fetch(url, {
711
- method: "POST",
712
- headers: { "Content-Type": "application/json" },
713
- body: JSON.stringify(body)
714
- });
715
- if (!res.ok) {
716
- const text2 = await res.text();
717
- throw new Error(`Google AI error ${res.status}: ${text2}`);
718
- }
719
- const data = await res.json();
720
- const text = data.candidates[0]?.content?.parts[0]?.text || "";
721
- return {
722
- content: text,
723
- tokens: data.usageMetadata ? { input: data.usageMetadata.promptTokenCount, output: data.usageMetadata.candidatesTokenCount } : void 0
724
- };
725
- }
726
- getMetadata() {
727
- return {
728
- provider: "google",
729
- model: this.config.model || "gemini-2.5-flash"
730
- };
731
- }
732
- };
733
-
734
- // src/llm/deepseek.ts
735
- var DeepSeekAdapter = class extends BaseLLMAdapter {
736
- constructor(config) {
737
- super(config);
738
- }
739
- async chat(messages) {
740
- const res = await fetch("https://api.deepseek.com/chat/completions", {
741
- method: "POST",
742
- headers: {
743
- "Content-Type": "application/json",
744
- Authorization: `Bearer ${this.config.apiKey}`
745
- },
746
- body: JSON.stringify({
747
- model: this.config.model || "deepseek-chat",
748
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
749
- temperature: this.getTemperature(),
750
- max_tokens: this.getMaxTokens()
751
- })
752
- });
753
- if (!res.ok) {
754
- const body = await res.text();
755
- throw new Error(`DeepSeek API error ${res.status}: ${body}`);
756
- }
757
- const data = await res.json();
758
- return {
759
- content: data.choices[0]?.message?.content || "",
760
- tokens: data.usage ? { input: data.usage.prompt_tokens, output: data.usage.completion_tokens } : void 0
761
- };
762
- }
763
- getMetadata() {
764
- return {
765
- provider: "deepseek",
766
- model: this.config.model || "deepseek-chat"
767
- };
768
- }
769
- };
770
-
771
- // src/llm/mistral.ts
772
- var MistralAdapter = class extends BaseLLMAdapter {
773
- constructor(config) {
774
- super(config);
775
- }
776
- async chat(messages) {
777
- const res = await fetch("https://api.mistral.ai/v1/chat/completions", {
778
- method: "POST",
779
- headers: {
780
- "Content-Type": "application/json",
781
- Authorization: `Bearer ${this.config.apiKey}`
782
- },
783
- body: JSON.stringify({
784
- model: this.config.model || "mistral-large-latest",
785
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
786
- temperature: this.getTemperature(),
787
- max_tokens: this.getMaxTokens()
788
- })
789
- });
790
- if (!res.ok) {
791
- const body = await res.text();
792
- throw new Error(`Mistral API error ${res.status}: ${body}`);
793
- }
794
- const data = await res.json();
795
- return {
796
- content: data.choices[0]?.message?.content || "",
797
- tokens: data.usage ? { input: data.usage.prompt_tokens, output: data.usage.completion_tokens } : void 0
798
- };
799
- }
800
- getMetadata() {
801
- return {
802
- provider: "mistral",
803
- model: this.config.model || "mistral-large-latest"
804
- };
805
- }
806
- };
807
-
808
- // src/llm/groq.ts
809
- var GroqAdapter = class extends BaseLLMAdapter {
810
- constructor(config) {
811
- super(config);
812
- }
813
- async chat(messages) {
814
- const res = await fetch("https://api.groq.com/openai/v1/chat/completions", {
815
- method: "POST",
816
- headers: {
817
- "Content-Type": "application/json",
818
- Authorization: `Bearer ${this.config.apiKey}`
819
- },
820
- body: JSON.stringify({
821
- model: this.config.model || "llama-3.3-70b-versatile",
822
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
823
- temperature: this.getTemperature(),
824
- max_tokens: this.getMaxTokens()
825
- })
826
- });
827
- if (!res.ok) {
828
- const body = await res.text();
829
- throw new Error(`Groq API error ${res.status}: ${body}`);
830
- }
831
- const data = await res.json();
832
- return {
833
- content: data.choices[0]?.message?.content || "",
834
- tokens: data.usage ? { input: data.usage.prompt_tokens, output: data.usage.completion_tokens } : void 0
835
- };
836
- }
837
- getMetadata() {
838
- return {
839
- provider: "groq",
840
- model: this.config.model || "llama-3.3-70b-versatile"
841
- };
842
- }
843
- };
844
-
845
- // src/llm/together.ts
846
- var TogetherAdapter = class extends BaseLLMAdapter {
847
- constructor(config) {
848
- super(config);
849
- }
850
- async chat(messages) {
851
- const res = await fetch("https://api.together.xyz/v1/chat/completions", {
852
- method: "POST",
853
- headers: {
854
- "Content-Type": "application/json",
855
- Authorization: `Bearer ${this.config.apiKey}`
856
- },
857
- body: JSON.stringify({
858
- model: this.config.model || "meta-llama/Llama-3.3-70B-Instruct-Turbo",
859
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
860
- temperature: this.getTemperature(),
861
- max_tokens: this.getMaxTokens()
862
- })
863
- });
864
- if (!res.ok) {
865
- const body = await res.text();
866
- throw new Error(`Together API error ${res.status}: ${body}`);
867
- }
868
- const data = await res.json();
869
- return {
870
- content: data.choices[0]?.message?.content || "",
871
- tokens: data.usage ? { input: data.usage.prompt_tokens, output: data.usage.completion_tokens } : void 0
872
- };
873
- }
874
- getMetadata() {
875
- return {
876
- provider: "together",
877
- model: this.config.model || "meta-llama/Llama-3.3-70B-Instruct-Turbo"
878
- };
879
- }
880
- };
881
-
882
- // src/llm/ollama.ts
883
- var OllamaAdapter = class extends BaseLLMAdapter {
884
- endpoint;
885
- constructor(config) {
886
- super(config);
887
- this.endpoint = config.endpoint || "http://localhost:11434";
888
- }
889
- async chat(messages) {
890
- const res = await fetch(`${this.endpoint}/api/chat`, {
891
- method: "POST",
892
- headers: { "Content-Type": "application/json" },
893
- body: JSON.stringify({
894
- model: this.config.model || "llama3.3",
895
- messages: messages.map((m) => ({ role: m.role, content: m.content })),
896
- stream: false,
897
- options: {
898
- temperature: this.getTemperature(),
899
- num_predict: this.getMaxTokens()
900
- }
901
- })
902
- });
903
- if (!res.ok) {
904
- const body = await res.text();
905
- throw new Error(`Ollama error ${res.status}: ${body}`);
906
- }
907
- const data = await res.json();
908
- return {
909
- content: data.message?.content || "",
910
- tokens: data.prompt_eval_count ? { input: data.prompt_eval_count, output: data.eval_count || 0 } : void 0
911
- };
912
- }
913
- getMetadata() {
914
- return {
915
- provider: "ollama",
916
- model: this.config.model || "llama3.3"
917
- };
918
- }
919
- };
920
-
921
- // src/llm/index.ts
922
- function createLLMAdapter(config) {
923
- switch (config.provider) {
924
- case "openai":
925
- return new OpenAIAdapter(config);
926
- case "anthropic":
927
- return new AnthropicAdapter(config);
928
- case "google":
929
- return new GoogleAdapter(config);
930
- case "deepseek":
931
- return new DeepSeekAdapter(config);
932
- case "mistral":
933
- return new MistralAdapter(config);
934
- case "groq":
935
- return new GroqAdapter(config);
936
- case "together":
937
- return new TogetherAdapter(config);
938
- case "ollama":
939
- return new OllamaAdapter(config);
940
- default:
941
- throw new Error(`Unknown LLM provider: ${config.provider}`);
942
- }
943
- }
944
-
945
- // src/strategy/templates.ts
946
- var templates = [
947
- {
948
- id: "momentum",
949
- name: "Momentum Trader",
950
- description: "Identifies tokens with strong upward price momentum and rides the trend. Uses LLM to analyze price action and market sentiment.",
951
- category: "momentum",
952
- venues: ["hyperliquid_perp", "hyperliquid_spot"],
953
- riskLevel: "moderate",
954
- systemPrompt: `You are a momentum trading agent. Analyze the provided market data and identify tokens with strong upward momentum.
955
-
956
- Rules:
957
- - Only trade tokens with clear directional momentum (avoid choppy markets)
958
- - Use trailing stops to protect gains
959
- - Size positions based on conviction (higher confidence = larger position)
960
- - Cut losses quickly if momentum reverses
961
- - Consider volume as confirmation of momentum
962
-
963
- Return a JSON array of trade signals.`,
964
- code: `
965
- const prices = context.market.getPrices();
966
- const positions = context.position.openPositions;
967
-
968
- const messages = [
969
- { role: 'system', content: 'You are a momentum trading agent. Analyze market data and return trade signals as a JSON array. Each signal: { symbol, side: "buy"|"sell", confidence: 0-1, reasoning }. Return [] if no opportunities.' },
970
- { role: 'user', content: 'Current prices: ' + JSON.stringify(prices) + '\\nOpen positions: ' + JSON.stringify(positions.map(p => p.token)) + '\\nAnalyze for momentum opportunities.' }
971
- ];
972
-
973
- const response = await context.llm.chat(messages);
974
- try {
975
- const signals = JSON.parse(response.content);
976
- return Array.isArray(signals) ? signals : [];
977
- } catch {
978
- return [];
979
- }
980
- `
981
- },
982
- {
983
- id: "value",
984
- name: "Value Investor",
985
- description: "Identifies undervalued tokens based on fundamental analysis. Buys dips and holds for mean reversion.",
986
- category: "value",
987
- venues: ["hyperliquid_spot"],
988
- riskLevel: "conservative",
989
- systemPrompt: `You are a value investing agent. Identify tokens trading below their intrinsic value.
990
-
991
- Rules:
992
- - Focus on established tokens with strong fundamentals
993
- - Buy on significant dips (>10% from recent highs)
994
- - Hold positions longer (swing/position timeframe)
995
- - Avoid chasing pumps
996
- - Diversify across sectors
997
-
998
- Return a JSON array of trade signals.`,
999
- code: `
1000
- const prices = context.market.getPrices();
1001
- const positions = context.position.openPositions;
1002
-
1003
- const messages = [
1004
- { role: 'system', content: 'You are a value investing agent. Identify undervalued tokens. Return trade signals as JSON array: { symbol, side: "buy"|"sell", confidence: 0-1, reasoning }. Return [] if nothing is compelling.' },
1005
- { role: 'user', content: 'Current prices: ' + JSON.stringify(prices) + '\\nPositions: ' + JSON.stringify(positions.map(p => ({ token: p.token, entry: p.costBasisPerUnit }))) + '\\nLook for value opportunities.' }
1006
- ];
1007
-
1008
- const response = await context.llm.chat(messages);
1009
- try {
1010
- const signals = JSON.parse(response.content);
1011
- return Array.isArray(signals) ? signals : [];
1012
- } catch {
1013
- return [];
1014
- }
1015
- `
1016
- },
1017
- {
1018
- id: "arbitrage",
1019
- name: "Arbitrage Hunter",
1020
- description: "Scans for price discrepancies across venues and chains. Executes quickly to capture spreads.",
1021
- category: "arbitrage",
1022
- venues: ["hyperliquid_perp", "hyperliquid_spot", "uniswap", "aerodrome"],
1023
- riskLevel: "aggressive",
1024
- systemPrompt: `You are an arbitrage agent. Find price discrepancies between venues.
1025
-
1026
- Rules:
1027
- - Speed is critical \u2014 execute quickly before spreads close
1028
- - Account for fees and slippage in profitability calculations
1029
- - Only trade when net profit > 0.5% after all costs
1030
- - Monitor cross-chain opportunities (CEX vs DEX spreads)
1031
-
1032
- Return a JSON array of trade signals.`,
1033
- code: `
1034
- const prices = context.market.getPrices();
1035
-
1036
- const messages = [
1037
- { role: 'system', content: 'You are an arbitrage agent. Find price discrepancies. Return trade signals as JSON array: { symbol, side: "buy"|"sell", venue, confidence: 0-1, reasoning }. Return [] if no arb opportunities.' },
1038
- { role: 'user', content: 'Market prices: ' + JSON.stringify(prices) + '\\nScan for cross-venue arbitrage opportunities.' }
1039
- ];
1040
-
1041
- const response = await context.llm.chat(messages);
1042
- try {
1043
- const signals = JSON.parse(response.content);
1044
- return Array.isArray(signals) ? signals : [];
1045
- } catch {
1046
- return [];
1047
- }
1048
- `
1049
- },
1050
- {
1051
- id: "hold",
1052
- name: "Hold (No Trading)",
1053
- description: "Passive strategy that makes no trades. Useful for monitoring only.",
1054
- category: "custom",
1055
- venues: [],
1056
- riskLevel: "conservative",
1057
- systemPrompt: "",
1058
- code: `return [];`
1059
- }
1060
- ];
1061
- function getTemplate(id) {
1062
- return templates.find((t) => t.id === id);
1063
- }
1064
- function listTemplates() {
1065
- return [...templates];
1066
- }
1067
-
1068
- // src/strategy/loader.ts
1069
- import { existsSync as existsSync3 } from "fs";
1070
- import { resolve } from "path";
1071
- async function loadStrategy(config) {
1072
- if (config.file) {
1073
- return loadFromFile(config.file);
1074
- }
1075
- if (config.template) {
1076
- const template = getTemplate(config.template);
1077
- if (!template) {
1078
- throw new Error(`Unknown strategy template: ${config.template}. Available: momentum, value, arbitrage, hold`);
1079
- }
1080
- return loadFromCode(template.code);
1081
- }
1082
- return holdStrategy;
1083
- }
1084
- async function loadFromFile(filePath) {
1085
- const resolved = resolve(filePath);
1086
- if (!existsSync3(resolved)) {
1087
- throw new Error(`Strategy file not found: ${resolved}`);
1088
- }
1089
- try {
1090
- const mod = await import(resolved);
1091
- const fn = mod.default || mod.strategy;
1092
- if (typeof fn !== "function") {
1093
- throw new Error(`Strategy file must export a default function or 'strategy' function`);
1094
- }
1095
- return fn;
1096
- } catch (err) {
1097
- throw new Error(`Failed to load strategy from ${resolved}: ${err.message}`);
1098
- }
1099
- }
1100
- async function loadFromCode(code) {
1101
- const AsyncFunction = Object.getPrototypeOf(async function() {
1102
- }).constructor;
1103
- const fn = new AsyncFunction("context", code);
1104
- return fn;
1105
- }
1106
- var holdStrategy = async (_context) => {
1107
- return [];
1108
- };
1109
-
1110
- // src/trading/risk.ts
1111
- var RiskManager = class {
1112
- params;
1113
- dailyPnL = 0;
1114
- dailyFees = 0;
1115
- lastResetDate = "";
1116
- initialCapitalUSD;
1117
- constructor(params, initialCapitalUSD = 1e4) {
1118
- this.params = params;
1119
- this.initialCapitalUSD = initialCapitalUSD;
1120
- this.resetIfNewDay();
1121
- }
1122
- filterSignals(signals, market, openPositionCount) {
1123
- this.resetIfNewDay();
1124
- if (this.isDailyLossLimitHit()) {
1125
- console.log("[risk] Daily loss limit hit \u2014 blocking all trades");
1126
- return [];
1127
- }
1128
- return signals.filter((signal) => this.validateSignal(signal, market, openPositionCount));
1129
- }
1130
- validateSignal(signal, market, openPositionCount) {
1131
- const threshold = this.params.confidenceThreshold ?? 0.5;
1132
- if (signal.confidence !== void 0 && signal.confidence < threshold) {
1133
- console.log(`[risk] Blocked ${signal.symbol}: confidence ${signal.confidence} < ${threshold}`);
1134
- return false;
1135
- }
1136
- const tradeValue = signal.size * signal.price;
1137
- const maxPositionValue = this.params.maxPositionSizeBps / 1e4 * this.initialCapitalUSD;
1138
- if (tradeValue > maxPositionValue) {
1139
- console.log(`[risk] Blocked ${signal.symbol}: trade $${tradeValue.toFixed(0)} > max $${maxPositionValue.toFixed(0)}`);
1140
- return false;
1141
- }
1142
- if (tradeValue < this.params.minTradeValueUSD) {
1143
- console.log(`[risk] Blocked ${signal.symbol}: trade $${tradeValue.toFixed(2)} < min $${this.params.minTradeValueUSD}`);
1144
- return false;
1145
- }
1146
- if ((signal.side === "buy" || signal.side === "long") && openPositionCount >= this.params.maxConcurrentPositions) {
1147
- console.log(`[risk] Blocked ${signal.symbol}: ${openPositionCount} positions >= max ${this.params.maxConcurrentPositions}`);
1148
- return false;
1149
- }
1150
- if (signal.orderType === "market") {
1151
- const currentPrice = market.getPrice(signal.symbol);
1152
- if (currentPrice) {
1153
- const slippageBps = Math.abs(signal.price - currentPrice) / currentPrice * 1e4;
1154
- if (slippageBps > this.params.maxSlippageBps) {
1155
- console.log(`[risk] Blocked ${signal.symbol}: slippage ${slippageBps.toFixed(0)}bps > max ${this.params.maxSlippageBps}bps`);
1156
- return false;
1157
- }
1158
- }
1159
- }
1160
- return true;
1161
- }
1162
- recordTrade(pnl, fee) {
1163
- this.dailyPnL += pnl;
1164
- this.dailyFees += fee;
1165
- }
1166
- isDailyLossLimitHit() {
1167
- const limit = this.params.maxDailyLossBps / 1e4 * this.initialCapitalUSD;
1168
- return this.dailyPnL < -limit;
1169
- }
1170
- getDailyPnL() {
1171
- return this.dailyPnL;
1172
- }
1173
- getDailyLossLimit() {
1174
- return this.params.maxDailyLossBps / 1e4 * this.initialCapitalUSD;
1175
- }
1176
- resetIfNewDay() {
1177
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1178
- if (today !== this.lastResetDate) {
1179
- this.dailyPnL = 0;
1180
- this.dailyFees = 0;
1181
- this.lastResetDate = today;
1182
- }
1183
- }
1184
- };
1185
-
1186
- // src/trading/market.ts
1187
- var MarketDataService = class {
1188
- prices = {};
1189
- lastFetch = 0;
1190
- cacheTTL;
1191
- constructor(cacheTTLMs = 3e4) {
1192
- this.cacheTTL = cacheTTLMs;
1193
- }
1194
- async refreshPrices(symbols) {
1195
- if (symbols.length === 0) return this.prices;
1196
- const now = Date.now();
1197
- if (now - this.lastFetch < this.cacheTTL && Object.keys(this.prices).length > 0) {
1198
- return this.prices;
1199
- }
1200
- try {
1201
- const ids = symbols.map((s) => s.toLowerCase()).join(",");
1202
- const res = await fetch(
1203
- `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`
1204
- );
1205
- if (res.ok) {
1206
- const data = await res.json();
1207
- for (const [id, price] of Object.entries(data)) {
1208
- this.prices[id.toUpperCase()] = price.usd;
1209
- }
1210
- this.lastFetch = now;
1211
- }
1212
- } catch (err) {
1213
- console.warn("[market] Price fetch failed:", err.message);
1214
- }
1215
- return this.prices;
1216
- }
1217
- getPrice(symbol) {
1218
- return this.prices[symbol.toUpperCase()];
1219
- }
1220
- getPrices() {
1221
- return { ...this.prices };
1222
- }
1223
- setPrice(symbol, price) {
1224
- this.prices[symbol.toUpperCase()] = price;
1225
- }
1226
- setPrices(prices) {
1227
- for (const [symbol, price] of Object.entries(prices)) {
1228
- this.prices[symbol.toUpperCase()] = price;
1229
- }
1230
- }
1231
- getOHLCV(_symbol, _timeframe) {
1232
- return [];
1233
- }
1234
- };
1235
-
1236
- // src/paper/executor.ts
1237
- import { randomUUID } from "crypto";
1238
- var PaperExecutor = class {
1239
- cash;
1240
- positions = /* @__PURE__ */ new Map();
1241
- trades = [];
1242
- equityCurve = [];
1243
- slippageBps;
1244
- feeRate;
1245
- peakEquity;
1246
- maxDrawdown = 0;
1247
- constructor(initialCash = 1e4, slippageBps = 50, feeRate = 2e-3) {
1248
- this.cash = initialCash;
1249
- this.slippageBps = slippageBps;
1250
- this.feeRate = feeRate;
1251
- this.peakEquity = initialCash;
1252
- this.recordEquity();
1253
- }
1254
- execute(signal, market) {
1255
- const marketPrice = market.getPrice(signal.symbol);
1256
- if (!marketPrice) {
1257
- console.warn(`[paper] No price for ${signal.symbol}`);
1258
- return null;
1259
- }
1260
- const isBuy = signal.side === "buy" || signal.side === "long";
1261
- const slippage = this.slippageBps / 1e4 * marketPrice;
1262
- const executedPrice = isBuy ? marketPrice + slippage : marketPrice - slippage;
1263
- const fee = signal.size * executedPrice * this.feeRate;
1264
- if (isBuy) {
1265
- const totalCost = signal.size * executedPrice + fee;
1266
- if (totalCost > this.cash) {
1267
- console.warn(`[paper] Insufficient funds: need $${totalCost.toFixed(2)}, have $${this.cash.toFixed(2)}`);
1268
- return null;
1269
- }
1270
- this.cash -= totalCost;
1271
- const existing = this.positions.get(signal.symbol);
1272
- if (existing) {
1273
- const totalQty = existing.quantity + signal.size;
1274
- existing.avgPrice = (existing.avgPrice * existing.quantity + executedPrice * signal.size) / totalQty;
1275
- existing.quantity = totalQty;
1276
- } else {
1277
- this.positions.set(signal.symbol, { quantity: signal.size, avgPrice: executedPrice });
1278
- }
1279
- } else {
1280
- const existing = this.positions.get(signal.symbol);
1281
- if (!existing || existing.quantity < signal.size) {
1282
- console.warn(`[paper] No position to sell: ${signal.symbol}`);
1283
- return null;
1284
- }
1285
- const proceeds = signal.size * executedPrice - fee;
1286
- this.cash += proceeds;
1287
- existing.quantity -= signal.size;
1288
- if (existing.quantity <= 1e-6) {
1289
- this.positions.delete(signal.symbol);
1290
- }
1291
- }
1292
- const trade = {
1293
- id: randomUUID(),
1294
- symbol: signal.symbol,
1295
- side: isBuy ? "buy" : "sell",
1296
- size: signal.size,
1297
- entryPrice: executedPrice,
1298
- fee,
1299
- timestamp: Date.now(),
1300
- venue: `paper_${signal.venue}`
1301
- };
1302
- this.trades.push(trade);
1303
- this.recordEquity();
1304
- return trade;
1305
- }
1306
- recordEquity() {
1307
- const equity = this.getEquity();
1308
- this.equityCurve.push({ timestamp: Date.now(), equity });
1309
- if (equity > this.peakEquity) this.peakEquity = equity;
1310
- const drawdown = (this.peakEquity - equity) / this.peakEquity;
1311
- if (drawdown > this.maxDrawdown) this.maxDrawdown = drawdown;
1312
- }
1313
- getEquity() {
1314
- let positionValue = 0;
1315
- for (const [, pos] of this.positions) {
1316
- positionValue += pos.quantity * pos.avgPrice;
1317
- }
1318
- return this.cash + positionValue;
1319
- }
1320
- getCash() {
1321
- return this.cash;
1322
- }
1323
- getPositions() {
1324
- return new Map(this.positions);
1325
- }
1326
- getTrades() {
1327
- return [...this.trades];
1328
- }
1329
- getEquityCurve() {
1330
- return [...this.equityCurve];
1331
- }
1332
- getMetrics() {
1333
- const initialEquity = this.equityCurve[0]?.equity || 0;
1334
- const currentEquity = this.getEquity();
1335
- const totalReturn = initialEquity > 0 ? (currentEquity - initialEquity) / initialEquity : 0;
1336
- const dailyReturns = this.calculateDailyReturns();
1337
- const sharpeRatio = this.calculateSharpe(dailyReturns);
1338
- let wins = 0;
1339
- let losses = 0;
1340
- let grossProfit = 0;
1341
- let grossLoss = 0;
1342
- const sellTrades = this.trades.filter((t) => t.side === "sell");
1343
- for (const sell of sellTrades) {
1344
- const buyTrades = this.trades.filter((t) => t.side === "buy" && t.symbol === sell.symbol);
1345
- const avgEntry = buyTrades.reduce((sum, t) => sum + t.entryPrice, 0) / (buyTrades.length || 1);
1346
- const pnl = (sell.entryPrice - avgEntry) * sell.size - sell.fee;
1347
- if (pnl > 0) {
1348
- wins++;
1349
- grossProfit += pnl;
1350
- } else {
1351
- losses++;
1352
- grossLoss += Math.abs(pnl);
1353
- }
1354
- }
1355
- return {
1356
- totalReturn,
1357
- sharpeRatio,
1358
- maxDrawdown: this.maxDrawdown,
1359
- winRate: wins + losses > 0 ? wins / (wins + losses) : 0,
1360
- profitFactor: grossLoss > 0 ? grossProfit / grossLoss : grossProfit > 0 ? Infinity : 0,
1361
- totalTrades: this.trades.length
1362
- };
1363
- }
1364
- calculateDailyReturns() {
1365
- if (this.equityCurve.length < 2) return [];
1366
- const dailyEquity = /* @__PURE__ */ new Map();
1367
- for (const point of this.equityCurve) {
1368
- const date = new Date(point.timestamp).toISOString().slice(0, 10);
1369
- dailyEquity.set(date, point.equity);
1370
- }
1371
- const dates = Array.from(dailyEquity.keys()).sort();
1372
- const returns = [];
1373
- for (let i = 1; i < dates.length; i++) {
1374
- const prev = dailyEquity.get(dates[i - 1]);
1375
- const curr = dailyEquity.get(dates[i]);
1376
- if (prev > 0) returns.push((curr - prev) / prev);
1377
- }
1378
- return returns;
1379
- }
1380
- calculateSharpe(returns) {
1381
- if (returns.length < 2) return 0;
1382
- const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
1383
- const variance = returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / (returns.length - 1);
1384
- const std = Math.sqrt(variance);
1385
- if (std === 0) return 0;
1386
- return mean / std * Math.sqrt(365);
1387
- }
1388
- };
1389
-
1390
- // src/perp/client.ts
1391
- var HyperliquidClient = class {
1392
- apiUrl;
1393
- meta = null;
1394
- assetIndexCache = /* @__PURE__ */ new Map();
1395
- constructor(config) {
1396
- this.apiUrl = config.apiUrl;
1397
- }
1398
- // ── INFO API (read-only) ───────────────────────────────────
1399
- async getMeta() {
1400
- if (this.meta) return this.meta;
1401
- const resp = await this.infoRequest({ type: "meta" });
1402
- this.meta = resp.universe;
1403
- this.meta.forEach((asset, idx) => {
1404
- this.assetIndexCache.set(asset.name, idx);
1405
- });
1406
- return this.meta;
1407
- }
1408
- async getAssetIndex(coin) {
1409
- if (this.assetIndexCache.has(coin)) return this.assetIndexCache.get(coin);
1410
- await this.getMeta();
1411
- const idx = this.assetIndexCache.get(coin);
1412
- if (idx === void 0) throw new Error(`Unknown instrument: ${coin}`);
1413
- return idx;
1414
- }
1415
- async getAllMids() {
1416
- return this.infoRequest({ type: "allMids" });
1417
- }
1418
- async getClearinghouseState(user) {
1419
- return this.infoRequest({ type: "clearinghouseState", user });
1420
- }
1421
- async getUserFills(user, startTime) {
1422
- return this.infoRequest({
1423
- type: "userFills",
1424
- user,
1425
- ...startTime !== void 0 && { startTime }
1426
- });
1427
- }
1428
- async getUserFillsByTime(user, startTime, endTime) {
1429
- return this.infoRequest({
1430
- type: "userFillsByTime",
1431
- user,
1432
- startTime,
1433
- ...endTime !== void 0 && { endTime }
1434
- });
1435
- }
1436
- async getOpenOrders(user) {
1437
- return this.infoRequest({ type: "openOrders", user });
1438
- }
1439
- async getL2Book(coin, depth) {
1440
- return this.infoRequest({
1441
- type: "l2Book",
1442
- coin,
1443
- ...depth !== void 0 && { nSigFigs: depth }
1444
- });
1445
- }
1446
- // ── HIGH-LEVEL HELPERS ─────────────────────────────────────
1447
- async getPositions(user) {
1448
- const state = await this.getClearinghouseState(user);
1449
- return state.assetPositions.filter((p) => parseFloat(p.position.szi) !== 0).map((p) => this.parsePosition(p));
1450
- }
1451
- async getAccountSummary(user) {
1452
- const state = await this.getClearinghouseState(user);
1453
- const cms = state.crossMarginSummary;
1454
- const totalEquity = parseFloat(cms.accountValue);
1455
- const totalNotional = parseFloat(cms.totalNtlPos);
1456
- const totalMarginUsed = parseFloat(cms.totalMarginUsed);
1457
- return {
1458
- totalEquity,
1459
- availableMargin: totalEquity - totalMarginUsed,
1460
- totalMarginUsed,
1461
- totalUnrealizedPnl: parseFloat(cms.totalRawUsd) - totalEquity,
1462
- totalNotional,
1463
- maintenanceMargin: totalMarginUsed * 0.5,
1464
- effectiveLeverage: totalEquity > 0 ? totalNotional / totalEquity : 0,
1465
- cashBalance: parseFloat(cms.accountValue) - state.assetPositions.reduce(
1466
- (sum, p) => sum + parseFloat(p.position.unrealizedPnl),
1467
- 0
1468
- )
1469
- };
1470
- }
1471
- async getMarketData(instruments) {
1472
- const mids = await this.getAllMids();
1473
- return instruments.filter((inst) => mids[inst] !== void 0).map((inst) => ({
1474
- instrument: inst,
1475
- midPrice: parseFloat(mids[inst]),
1476
- bestBid: parseFloat(mids[inst]),
1477
- bestAsk: parseFloat(mids[inst]),
1478
- funding8h: 0,
1479
- openInterest: 0,
1480
- volume24h: 0,
1481
- priceChange24h: 0
1482
- }));
1483
- }
1484
- parseFill(fill) {
1485
- return {
1486
- oid: fill.oid,
1487
- coin: fill.coin,
1488
- side: fill.side,
1489
- px: fill.px,
1490
- sz: fill.sz,
1491
- fee: fill.fee,
1492
- time: fill.time,
1493
- hash: fill.hash,
1494
- isMaker: fill.startPosition !== fill.px,
1495
- builderFee: fill.builderFee,
1496
- liquidation: fill.liquidation
1497
- };
1498
- }
1499
- // ── PRIVATE ────────────────────────────────────────────────
1500
- parsePosition(ap) {
1501
- const pos = ap.position;
1502
- const size = parseFloat(pos.szi);
1503
- const entryPrice = parseFloat(pos.entryPx || "0");
1504
- const markPrice = parseFloat(pos.positionValue || "0") / Math.abs(size || 1);
1505
- return {
1506
- instrument: pos.coin,
1507
- assetIndex: this.assetIndexCache.get(pos.coin) ?? -1,
1508
- size,
1509
- entryPrice,
1510
- markPrice,
1511
- unrealizedPnl: parseFloat(pos.unrealizedPnl),
1512
- leverage: parseFloat(pos.leverage?.value || "1"),
1513
- marginType: pos.leverage?.type === "isolated" ? "isolated" : "cross",
1514
- liquidationPrice: parseFloat(pos.liquidationPx || "0"),
1515
- notionalUSD: Math.abs(size) * markPrice,
1516
- marginUsed: parseFloat(pos.marginUsed)
1517
- };
1518
- }
1519
- async infoRequest(body) {
1520
- const resp = await fetch(`${this.apiUrl}/info`, {
1521
- method: "POST",
1522
- headers: { "Content-Type": "application/json" },
1523
- body: JSON.stringify(body)
1524
- });
1525
- if (!resp.ok) {
1526
- throw new Error(`Hyperliquid Info API error: ${resp.status} ${await resp.text()}`);
1527
- }
1528
- return resp.json();
1529
- }
1530
- };
1531
-
1532
- // src/perp/signer.ts
1533
- var HYPERLIQUID_DOMAIN = {
1534
- name: "HyperliquidSignTransaction",
1535
- version: "1",
1536
- chainId: 42161n,
1537
- verifyingContract: "0x0000000000000000000000000000000000000000"
1538
- };
1539
- var HYPERLIQUID_TYPES = {
1540
- HyperliquidTransaction: [
1541
- { name: "hyperliquidChain", type: "string" },
1542
- { name: "action", type: "string" },
1543
- { name: "nonce", type: "uint64" }
1544
- ]
1545
- };
1546
- var lastNonce = 0n;
1547
- function getNextNonce() {
1548
- const now = BigInt(Date.now());
1549
- if (now <= lastNonce) {
1550
- lastNonce = lastNonce + 1n;
1551
- } else {
1552
- lastNonce = now;
1553
- }
1554
- return lastNonce;
1555
- }
1556
- var HyperliquidSigner = class {
1557
- constructor(walletClient) {
1558
- this.walletClient = walletClient;
1559
- }
1560
- async signAction(action, nonce) {
1561
- const actionNonce = nonce ?? getNextNonce();
1562
- const actionStr = JSON.stringify(action);
1563
- const account = this.walletClient.account;
1564
- if (!account) throw new Error("Wallet client has no account");
1565
- const signature = await this.walletClient.signTypedData({
1566
- account,
1567
- domain: HYPERLIQUID_DOMAIN,
1568
- types: HYPERLIQUID_TYPES,
1569
- primaryType: "HyperliquidTransaction",
1570
- message: {
1571
- hyperliquidChain: "Mainnet",
1572
- action: actionStr,
1573
- nonce: actionNonce
1574
- }
1575
- });
1576
- return { signature, nonce: actionNonce };
1577
- }
1578
- getAddress() {
1579
- const account = this.walletClient.account;
1580
- if (!account) throw new Error("Wallet client has no account");
1581
- return account.address;
1582
- }
1583
- };
1584
-
1585
- // src/perp/orders.ts
1586
- var HyperliquidOrderManager = class {
1587
- client;
1588
- signer;
1589
- config;
1590
- constructor(client, signer, config) {
1591
- this.client = client;
1592
- this.signer = signer;
1593
- this.config = config;
1594
- }
1595
- async placeOrder(signal) {
1596
- try {
1597
- const assetIndex = await this.client.getAssetIndex(signal.instrument);
1598
- const isBuy = signal.action === "open_long" || signal.action === "close_short";
1599
- const orderWire = {
1600
- a: assetIndex,
1601
- b: isBuy,
1602
- p: signal.orderType === "market" ? this.getMarketPrice(signal) : signal.price.toString(),
1603
- s: signal.size.toString(),
1604
- r: signal.reduceOnly,
1605
- t: signal.orderType === "market" ? { limit: { tif: "Ioc" } } : { limit: { tif: "Gtc" } }
1606
- };
1607
- const action = {
1608
- type: "order",
1609
- orders: [orderWire],
1610
- grouping: "na"
1611
- };
1612
- const nonce = getNextNonce();
1613
- const { signature } = await this.signer.signAction(action, nonce);
1614
- const resp = await this.exchangeRequest({
1615
- action,
1616
- nonce: Number(nonce),
1617
- signature: {
1618
- r: signature.slice(0, 66),
1619
- s: `0x${signature.slice(66, 130)}`,
1620
- v: parseInt(signature.slice(130, 132), 16)
1621
- },
1622
- vaultAddress: null
1623
- });
1624
- return this.parseOrderResponse(resp);
1625
- } catch (error) {
1626
- const message = error instanceof Error ? error.message : String(error);
1627
- console.error(`[perp] Order failed for ${signal.instrument}:`, message);
1628
- return { success: false, status: "error", error: message };
1629
- }
1630
- }
1631
- async cancelOrder(instrument, orderId) {
1632
- try {
1633
- const assetIndex = await this.client.getAssetIndex(instrument);
1634
- const action = {
1635
- type: "cancel",
1636
- cancels: [{ a: assetIndex, o: orderId }]
1637
- };
1638
- const nonce = getNextNonce();
1639
- const { signature } = await this.signer.signAction(action, nonce);
1640
- await this.exchangeRequest({
1641
- action,
1642
- nonce: Number(nonce),
1643
- signature: {
1644
- r: signature.slice(0, 66),
1645
- s: `0x${signature.slice(66, 130)}`,
1646
- v: parseInt(signature.slice(130, 132), 16)
1647
- },
1648
- vaultAddress: null
1649
- });
1650
- console.log(`[perp] Cancelled order ${orderId} for ${instrument}`);
1651
- return true;
1652
- } catch (error) {
1653
- const message = error instanceof Error ? error.message : String(error);
1654
- console.error(`[perp] Cancel failed for order ${orderId}:`, message);
1655
- return false;
1656
- }
1657
- }
1658
- async closePosition(instrument, positionSize) {
1659
- const isLong = positionSize > 0;
1660
- return this.placeOrder({
1661
- action: isLong ? "close_long" : "close_short",
1662
- instrument,
1663
- size: Math.abs(positionSize),
1664
- price: 0,
1665
- leverage: 1,
1666
- orderType: "market",
1667
- reduceOnly: true,
1668
- confidence: 1,
1669
- reasoning: "Position close"
1670
- });
1671
- }
1672
- async updateLeverage(instrument, leverage, isCross = true) {
1673
- try {
1674
- const assetIndex = await this.client.getAssetIndex(instrument);
1675
- const action = {
1676
- type: "updateLeverage",
1677
- asset: assetIndex,
1678
- isCross,
1679
- leverage
1680
- };
1681
- const nonce = getNextNonce();
1682
- const { signature } = await this.signer.signAction(action, nonce);
1683
- await this.exchangeRequest({
1684
- action,
1685
- nonce: Number(nonce),
1686
- signature: {
1687
- r: signature.slice(0, 66),
1688
- s: `0x${signature.slice(66, 130)}`,
1689
- v: parseInt(signature.slice(130, 132), 16)
1690
- },
1691
- vaultAddress: null
1692
- });
1693
- console.log(`[perp] Leverage set for ${instrument}: ${leverage}x (${isCross ? "cross" : "isolated"})`);
1694
- return true;
1695
- } catch (error) {
1696
- const message = error instanceof Error ? error.message : String(error);
1697
- console.error(`[perp] Leverage update failed for ${instrument}:`, message);
1698
- return false;
1699
- }
1700
- }
1701
- // ── PRIVATE ────────────────────────────────────────────────
1702
- getMarketPrice(signal) {
1703
- const isBuy = signal.action === "open_long" || signal.action === "close_short";
1704
- if (signal.price > 0) {
1705
- const slippage = isBuy ? 1.005 : 0.995;
1706
- return (signal.price * slippage).toString();
1707
- }
1708
- return "0";
1709
- }
1710
- parseOrderResponse(resp) {
1711
- if (resp?.status === "ok" && resp?.response?.type === "order") {
1712
- const statuses = resp.response.data?.statuses || [];
1713
- if (statuses.length > 0) {
1714
- const status = statuses[0];
1715
- if (status.filled) {
1716
- return {
1717
- success: true,
1718
- orderId: status.filled.oid,
1719
- status: "filled",
1720
- avgPrice: status.filled.avgPx,
1721
- filledSize: status.filled.totalSz
1722
- };
1723
- }
1724
- if (status.resting) {
1725
- return {
1726
- success: true,
1727
- orderId: status.resting.oid,
1728
- status: "resting"
1729
- };
1730
- }
1731
- if (status.error) {
1732
- return { success: false, status: "error", error: status.error };
1733
- }
1734
- }
1735
- }
1736
- return {
1737
- success: false,
1738
- status: "error",
1739
- error: `Unexpected response: ${JSON.stringify(resp)}`
1740
- };
1741
- }
1742
- async exchangeRequest(body) {
1743
- const resp = await fetch(`${this.config.apiUrl}/exchange`, {
1744
- method: "POST",
1745
- headers: { "Content-Type": "application/json" },
1746
- body: JSON.stringify(body)
1747
- });
1748
- if (!resp.ok) {
1749
- throw new Error(`Hyperliquid Exchange API error: ${resp.status} ${await resp.text()}`);
1750
- }
1751
- return resp.json();
1752
- }
1753
- };
1754
-
1755
- // src/perp/positions.ts
1756
- var HyperliquidPositionManager = class {
1757
- client;
1758
- userAddress;
1759
- cachedPositions = [];
1760
- cachedAccount = null;
1761
- lastRefreshAt = 0;
1762
- cacheTtlMs = 5e3;
1763
- constructor(client, userAddress) {
1764
- this.client = client;
1765
- this.userAddress = userAddress;
1766
- }
1767
- // ── POSITION QUERIES ───────────────────────────────────────
1768
- async getPositions(forceRefresh = false) {
1769
- if (!forceRefresh && this.isCacheFresh()) return this.cachedPositions;
1770
- await this.refresh();
1771
- return this.cachedPositions;
1772
- }
1773
- async getPosition(instrument) {
1774
- const positions = await this.getPositions();
1775
- return positions.find((p) => p.instrument === instrument) ?? null;
1776
- }
1777
- async getAccountSummary(forceRefresh = false) {
1778
- if (!forceRefresh && this.isCacheFresh() && this.cachedAccount) {
1779
- return this.cachedAccount;
1780
- }
1781
- await this.refresh();
1782
- return this.cachedAccount;
1783
- }
1784
- // ── LIQUIDATION MONITORING ─────────────────────────────────
1785
- async getLiquidationProximity() {
1786
- const positions = await this.getPositions();
1787
- const proximities = /* @__PURE__ */ new Map();
1788
- for (const pos of positions) {
1789
- if (pos.liquidationPrice <= 0 || pos.markPrice <= 0) {
1790
- proximities.set(pos.instrument, 0);
1791
- continue;
1792
- }
1793
- let proximity;
1794
- if (pos.size > 0) {
1795
- if (pos.markPrice <= pos.liquidationPrice) {
1796
- proximity = 1;
1797
- } else {
1798
- const distanceToLiq = pos.markPrice - pos.liquidationPrice;
1799
- const entryToLiq = pos.entryPrice - pos.liquidationPrice;
1800
- proximity = entryToLiq > 0 ? 1 - distanceToLiq / entryToLiq : 0;
1801
- }
1802
- } else {
1803
- if (pos.markPrice >= pos.liquidationPrice) {
1804
- proximity = 1;
1805
- } else {
1806
- const distanceToLiq = pos.liquidationPrice - pos.markPrice;
1807
- const entryToLiq = pos.liquidationPrice - pos.entryPrice;
1808
- proximity = entryToLiq > 0 ? 1 - distanceToLiq / entryToLiq : 0;
1809
- }
1810
- }
1811
- proximities.set(pos.instrument, Math.max(0, Math.min(1, proximity)));
1812
- }
1813
- return proximities;
1814
- }
1815
- async getDangerousPositions(threshold = 0.7) {
1816
- const positions = await this.getPositions();
1817
- const proximities = await this.getLiquidationProximity();
1818
- return positions.filter((p) => (proximities.get(p.instrument) ?? 0) > threshold);
1819
- }
1820
- // ── SUMMARY HELPERS ────────────────────────────────────────
1821
- async getTotalUnrealizedPnl() {
1822
- const positions = await this.getPositions();
1823
- return positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
1824
- }
1825
- async getTotalNotional() {
1826
- const positions = await this.getPositions();
1827
- return positions.reduce((sum, p) => sum + p.notionalUSD, 0);
1828
- }
1829
- async getPositionCount() {
1830
- return (await this.getPositions()).length;
1831
- }
1832
- // ── CACHE ──────────────────────────────────────────────────
1833
- async refresh() {
1834
- try {
1835
- const [positions, account] = await Promise.all([
1836
- this.client.getPositions(this.userAddress),
1837
- this.client.getAccountSummary(this.userAddress)
1838
- ]);
1839
- this.cachedPositions = positions;
1840
- this.cachedAccount = account;
1841
- this.lastRefreshAt = Date.now();
1842
- } catch (error) {
1843
- const message = error instanceof Error ? error.message : String(error);
1844
- console.error("[perp] Failed to refresh positions:", message);
1845
- }
1846
- }
1847
- isCacheFresh() {
1848
- return Date.now() - this.lastRefreshAt < this.cacheTtlMs;
1849
- }
1850
- };
1851
-
1852
- // src/perp/websocket.ts
1853
- import WebSocket2 from "ws";
1854
- var HyperliquidWebSocket = class {
1855
- wsUrl;
1856
- userAddress;
1857
- client;
1858
- ws = null;
1859
- reconnectAttempts = 0;
1860
- maxReconnectAttempts = 20;
1861
- baseReconnectMs = 1e3;
1862
- maxReconnectMs = 6e4;
1863
- reconnectTimer = null;
1864
- pingTimer = null;
1865
- isConnecting = false;
1866
- shouldReconnect = true;
1867
- lastProcessedFillTime = 0;
1868
- onFill = null;
1869
- onFunding = null;
1870
- onLiquidation = null;
1871
- constructor(config, userAddress, client) {
1872
- this.wsUrl = config.wsUrl;
1873
- this.userAddress = userAddress;
1874
- this.client = client;
1875
- }
1876
- // ── CONNECTION ─────────────────────────────────────────────
1877
- async connect() {
1878
- if (this.ws?.readyState === WebSocket2.OPEN || this.isConnecting) return;
1879
- this.isConnecting = true;
1880
- this.shouldReconnect = true;
1881
- return new Promise((resolve2, reject) => {
1882
- try {
1883
- this.ws = new WebSocket2(this.wsUrl);
1884
- this.ws.on("open", () => {
1885
- this.isConnecting = false;
1886
- this.reconnectAttempts = 0;
1887
- console.log("[perp-ws] Connected");
1888
- this.subscribe({
1889
- type: "subscribe",
1890
- subscription: { type: "userFills", user: this.userAddress }
1891
- });
1892
- this.subscribe({
1893
- type: "subscribe",
1894
- subscription: { type: "userFundings", user: this.userAddress }
1895
- });
1896
- this.startPing();
1897
- this.backfillMissedFills().catch((err) => {
1898
- console.warn("[perp-ws] Backfill failed:", err instanceof Error ? err.message : err);
1899
- });
1900
- resolve2();
1901
- });
1902
- this.ws.on("message", (data) => {
1903
- this.handleMessage(data);
1904
- });
1905
- this.ws.on("close", (code, reason) => {
1906
- this.isConnecting = false;
1907
- console.log(`[perp-ws] Closed: ${code} ${reason.toString()}`);
1908
- this.stopPing();
1909
- this.scheduleReconnect();
1910
- });
1911
- this.ws.on("error", (error) => {
1912
- this.isConnecting = false;
1913
- console.error("[perp-ws] Error:", error.message);
1914
- if (this.reconnectAttempts === 0) reject(error);
1915
- });
1916
- } catch (error) {
1917
- this.isConnecting = false;
1918
- reject(error);
1919
- }
1920
- });
1921
- }
1922
- disconnect() {
1923
- this.shouldReconnect = false;
1924
- if (this.reconnectTimer) {
1925
- clearTimeout(this.reconnectTimer);
1926
- this.reconnectTimer = null;
1927
- }
1928
- this.stopPing();
1929
- if (this.ws) {
1930
- this.ws.removeAllListeners();
1931
- if (this.ws.readyState === WebSocket2.OPEN) {
1932
- this.ws.close(1e3, "Client disconnect");
1933
- }
1934
- this.ws = null;
1935
- }
1936
- console.log("[perp-ws] Disconnected");
1937
- }
1938
- get isConnected() {
1939
- return this.ws?.readyState === WebSocket2.OPEN;
1940
- }
1941
- // ── EVENT HANDLERS ─────────────────────────────────────────
1942
- onFillReceived(callback) {
1943
- this.onFill = callback;
1944
- }
1945
- onFundingReceived(callback) {
1946
- this.onFunding = callback;
1947
- }
1948
- onLiquidationDetected(callback) {
1949
- this.onLiquidation = callback;
1950
- }
1951
- getLastProcessedFillTime() {
1952
- return this.lastProcessedFillTime;
1953
- }
1954
- // ── MESSAGE HANDLING ───────────────────────────────────────
1955
- handleMessage(data) {
1956
- try {
1957
- const msg = JSON.parse(data.toString());
1958
- if (msg.channel === "userFills") {
1959
- this.handleFillMessage(msg.data);
1960
- } else if (msg.channel === "userFundings") {
1961
- this.handleFundingMessage(msg.data);
1962
- }
1963
- } catch {
1964
- }
1965
- }
1966
- handleFillMessage(fills) {
1967
- if (!Array.isArray(fills) || !this.onFill) return;
1968
- for (const rawFill of fills) {
1969
- const fill = this.client.parseFill(rawFill);
1970
- if (fill.time > this.lastProcessedFillTime) {
1971
- this.lastProcessedFillTime = fill.time;
1972
- }
1973
- if (fill.liquidation && this.onLiquidation) {
1974
- this.onLiquidation(fill.coin, parseFloat(fill.sz));
1975
- }
1976
- this.onFill(fill);
1977
- }
1978
- }
1979
- handleFundingMessage(fundings) {
1980
- if (!Array.isArray(fundings) || !this.onFunding) return;
1981
- for (const funding of fundings) {
1982
- this.onFunding({
1983
- time: funding.time,
1984
- coin: funding.coin,
1985
- usdc: funding.usdc,
1986
- szi: funding.szi,
1987
- fundingRate: funding.fundingRate
1988
- });
1989
- }
1990
- }
1991
- // ── BACKFILL ───────────────────────────────────────────────
1992
- async backfillMissedFills() {
1993
- if (this.lastProcessedFillTime === 0 || !this.onFill) return;
1994
- console.log(`[perp-ws] Backfilling fills since ${new Date(this.lastProcessedFillTime).toISOString()}`);
1995
- const fills = await this.client.getUserFillsByTime(
1996
- this.userAddress,
1997
- this.lastProcessedFillTime + 1
1998
- );
1999
- if (fills.length > 0) {
2000
- console.log(`[perp-ws] Backfilled ${fills.length} missed fills`);
2001
- for (const rawFill of fills) {
2002
- const fill = this.client.parseFill(rawFill);
2003
- if (fill.time > this.lastProcessedFillTime) {
2004
- this.lastProcessedFillTime = fill.time;
2005
- }
2006
- if (fill.liquidation && this.onLiquidation) {
2007
- this.onLiquidation(fill.coin, parseFloat(fill.sz));
2008
- }
2009
- this.onFill(fill);
2010
- }
2011
- }
2012
- }
2013
- // ── RECONNECTION ───────────────────────────────────────────
2014
- scheduleReconnect() {
2015
- if (!this.shouldReconnect || this.reconnectAttempts >= this.maxReconnectAttempts) {
2016
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
2017
- console.error(`[perp-ws] Max reconnect attempts (${this.maxReconnectAttempts}) reached`);
2018
- }
2019
- return;
2020
- }
2021
- const delay = Math.min(
2022
- this.baseReconnectMs * Math.pow(2, this.reconnectAttempts),
2023
- this.maxReconnectMs
2024
- );
2025
- this.reconnectAttempts++;
2026
- console.log(`[perp-ws] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
2027
- this.reconnectTimer = setTimeout(() => {
2028
- this.connect().catch((err) => {
2029
- console.error("[perp-ws] Reconnect failed:", err instanceof Error ? err.message : err);
2030
- });
2031
- }, delay);
2032
- }
2033
- // ── KEEPALIVE ──────────────────────────────────────────────
2034
- startPing() {
2035
- this.stopPing();
2036
- this.pingTimer = setInterval(() => {
2037
- if (this.ws?.readyState === WebSocket2.OPEN) {
2038
- this.ws.send(JSON.stringify({ method: "ping" }));
2039
- }
2040
- }, 25e3);
2041
- }
2042
- stopPing() {
2043
- if (this.pingTimer) {
2044
- clearInterval(this.pingTimer);
2045
- this.pingTimer = null;
2046
- }
2047
- }
2048
- // ── HELPERS ────────────────────────────────────────────────
2049
- subscribe(msg) {
2050
- if (this.ws?.readyState === WebSocket2.OPEN) {
2051
- this.ws.send(JSON.stringify(msg));
2052
- }
2053
- }
2054
- };
2055
-
2056
- // src/prediction/types.ts
2057
- var DEFAULT_PREDICTION_CONFIG = {
2058
- enabled: false,
2059
- clobApiUrl: "https://clob.polymarket.com",
2060
- gammaApiUrl: "https://gamma-api.polymarket.com",
2061
- maxNotionalUSD: 1e3,
2062
- maxTotalExposureUSD: 5e3
2063
- };
2064
- var POLYGON_CHAIN_ID = 137;
2065
- var PREDICTION_INSTRUMENT_PREFIX = "POLY:";
2066
- function encodePredictionInstrument(conditionId, outcomeIndex) {
2067
- return `${PREDICTION_INSTRUMENT_PREFIX}${conditionId}:${outcomeIndex}`;
2068
- }
2069
- function decodePredictionInstrument(instrument) {
2070
- if (!instrument.startsWith(PREDICTION_INSTRUMENT_PREFIX)) return null;
2071
- const parts = instrument.slice(PREDICTION_INSTRUMENT_PREFIX.length).split(":");
2072
- if (parts.length !== 2) return null;
2073
- return { conditionId: parts[0], outcomeIndex: parseInt(parts[1], 10) };
2074
- }
2075
-
2076
- // src/prediction/client.ts
2077
- import { ClobClient } from "@polymarket/clob-client";
2078
- import { Wallet } from "ethers";
2079
- var PolymarketClient = class {
2080
- config;
2081
- clobClient = null;
2082
- apiCreds = null;
2083
- signer;
2084
- walletAddress;
2085
- marketCache = /* @__PURE__ */ new Map();
2086
- CACHE_TTL_MS = 6e4;
2087
- constructor(privateKey, config) {
2088
- this.config = { ...DEFAULT_PREDICTION_CONFIG, ...config };
2089
- this.signer = new Wallet(privateKey);
2090
- this.walletAddress = this.signer.address;
2091
- }
2092
- // ── INITIALIZATION ─────────────────────────────────────────
2093
- async initialize() {
2094
- const initClient = new ClobClient(
2095
- this.config.clobApiUrl,
2096
- POLYGON_CHAIN_ID,
2097
- this.signer
2098
- );
2099
- this.apiCreds = await initClient.createOrDeriveApiKey();
2100
- this.clobClient = new ClobClient(
2101
- this.config.clobApiUrl,
2102
- POLYGON_CHAIN_ID,
2103
- this.signer,
2104
- this.apiCreds,
2105
- 0
2106
- );
2107
- console.log(`[prediction] CLOB initialized for ${this.walletAddress}`);
2108
- }
2109
- get isInitialized() {
2110
- return this.clobClient !== null && this.apiCreds !== null;
2111
- }
2112
- // ── CLOB API — ORDER BOOK & PRICES ─────────────────────────
2113
- async getOrderBook(tokenId) {
2114
- this.ensureInitialized();
2115
- const book = await this.clobClient.getOrderBook(tokenId);
2116
- return {
2117
- bids: (book.bids || []).map((b) => ({ price: parseFloat(b.price), size: parseFloat(b.size) })),
2118
- asks: (book.asks || []).map((a) => ({ price: parseFloat(a.price), size: parseFloat(a.size) }))
2119
- };
2120
- }
2121
- async getMidpointPrice(tokenId) {
2122
- const book = await this.getOrderBook(tokenId);
2123
- if (book.bids.length === 0 || book.asks.length === 0) return 0;
2124
- return (book.bids[0].price + book.asks[0].price) / 2;
2125
- }
2126
- async getLastTradePrice(tokenId) {
2127
- this.ensureInitialized();
2128
- const resp = await this.clobClient.getLastTradePrice(tokenId);
2129
- return parseFloat(resp?.price || "0");
2130
- }
2131
- // ── CLOB API — ORDERS ──────────────────────────────────────
2132
- async placeLimitOrder(params) {
2133
- this.ensureInitialized();
2134
- const order = await this.clobClient.createAndPostOrder({
2135
- tokenID: params.tokenId,
2136
- price: params.price,
2137
- side: params.side,
2138
- size: params.size
2139
- });
2140
- return {
2141
- orderId: order?.orderID || "",
2142
- success: !!order?.orderID
2143
- };
2144
- }
2145
- async placeMarketOrder(params) {
2146
- this.ensureInitialized();
2147
- const order = await this.clobClient.createMarketOrder({
2148
- tokenID: params.tokenId,
2149
- amount: params.amount,
2150
- side: params.side
2151
- });
2152
- const result = await this.clobClient.postOrder(order);
2153
- return {
2154
- orderId: result?.orderID || "",
2155
- success: !!result?.orderID
2156
- };
2157
- }
2158
- async cancelOrder(orderId) {
2159
- this.ensureInitialized();
2160
- try {
2161
- await this.clobClient.cancelOrder({ orderID: orderId });
2162
- return true;
2163
- } catch {
2164
- return false;
2165
- }
2166
- }
2167
- async cancelAllOrders() {
2168
- this.ensureInitialized();
2169
- try {
2170
- await this.clobClient.cancelAll();
2171
- return true;
2172
- } catch {
2173
- return false;
2174
- }
2175
- }
2176
- async getOpenOrders() {
2177
- this.ensureInitialized();
2178
- return this.clobClient.getOpenOrders();
2179
- }
2180
- async getTradeHistory() {
2181
- this.ensureInitialized();
2182
- const trades = await this.clobClient.getTrades();
2183
- return (trades || []).map((t) => this.parseRawFill(t));
2184
- }
2185
- // ── GAMMA API — MARKET DISCOVERY (public) ──────────────────
2186
- async getMarkets(params) {
2187
- const query = new URLSearchParams();
2188
- if (params?.limit) query.set("limit", String(params.limit));
2189
- if (params?.offset) query.set("offset", String(params.offset));
2190
- if (params?.active !== void 0) query.set("active", String(params.active));
2191
- if (params?.category) query.set("tag", params.category);
2192
- const url = `${this.config.gammaApiUrl}/markets?${query.toString()}`;
2193
- const resp = await fetch(url);
2194
- if (!resp.ok) throw new Error(`Gamma API error: ${resp.status} ${await resp.text()}`);
2195
- const raw = await resp.json();
2196
- return (raw || []).map((m) => this.parseGammaMarket(m));
2197
- }
2198
- async getMarketByConditionId(conditionId) {
2199
- const cached = this.marketCache.get(conditionId);
2200
- if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
2201
- return cached.market;
2202
- }
2203
- const url = `${this.config.gammaApiUrl}/markets?condition_id=${conditionId}`;
2204
- const resp = await fetch(url);
2205
- if (!resp.ok) return null;
2206
- const raw = await resp.json();
2207
- if (!raw || raw.length === 0) return null;
2208
- const market = this.parseGammaMarket(raw[0]);
2209
- this.marketCache.set(conditionId, { market, cachedAt: Date.now() });
2210
- return market;
2211
- }
2212
- async searchMarkets(query, limit = 20) {
2213
- const url = `${this.config.gammaApiUrl}/markets?_q=${encodeURIComponent(query)}&limit=${limit}&active=true`;
2214
- const resp = await fetch(url);
2215
- if (!resp.ok) return [];
2216
- const raw = await resp.json();
2217
- return (raw || []).map((m) => this.parseGammaMarket(m));
2218
- }
2219
- async getTrendingMarkets(limit = 10) {
2220
- const url = `${this.config.gammaApiUrl}/markets?active=true&limit=${limit}&order=volume24hr&ascending=false`;
2221
- const resp = await fetch(url);
2222
- if (!resp.ok) return [];
2223
- const raw = await resp.json();
2224
- return (raw || []).map((m) => this.parseGammaMarket(m));
2225
- }
2226
- // ── WALLET ─────────────────────────────────────────────────
2227
- getWalletAddress() {
2228
- return this.walletAddress;
2229
- }
2230
- // ── PRIVATE ────────────────────────────────────────────────
2231
- ensureInitialized() {
2232
- if (!this.clobClient) {
2233
- throw new Error("PolymarketClient not initialized. Call initialize() first.");
2234
- }
2235
- }
2236
- parseGammaMarket(raw) {
2237
- const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ["Yes", "No"];
2238
- const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
2239
- const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
2240
- return {
2241
- conditionId: raw.conditionId || raw.condition_id || "",
2242
- question: raw.question || "",
2243
- description: raw.description || "",
2244
- category: raw.groupItemTitle || raw.category || "Other",
2245
- outcomes,
2246
- outcomeTokenIds,
2247
- outcomePrices: outcomePrices.map((p) => parseFloat(p) || 0),
2248
- volume24h: parseFloat(raw.volume24hr || raw.volume24h || "0"),
2249
- liquidity: parseFloat(raw.liquidity || "0"),
2250
- endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1e3 : 0,
2251
- active: raw.active !== false && raw.closed !== true,
2252
- resolved: raw.resolved === true,
2253
- winningOutcome: raw.winningOutcome,
2254
- resolutionSource: raw.resolutionSource || void 0,
2255
- uniqueTraders: raw.uniqueTraders || void 0
2256
- };
2257
- }
2258
- parseRawFill(raw) {
2259
- const tokenId = raw.asset_id || void 0;
2260
- const marketConditionId = raw.market || raw.asset_id || "";
2261
- return {
2262
- orderId: raw.orderId || raw.order_id || "",
2263
- tradeId: raw.id || raw.tradeId || "",
2264
- marketConditionId,
2265
- outcomeIndex: raw.outcome_index ?? (raw.side === "BUY" ? 0 : 1),
2266
- side: raw.side === "BUY" || raw.side === "buy" ? "BUY" : "SELL",
2267
- price: String(raw.price || "0"),
2268
- size: String(raw.size || "0"),
2269
- fee: String(raw.fee || "0"),
2270
- timestamp: raw.timestamp || raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
2271
- isMaker: raw.maker_order || raw.is_maker || false,
2272
- tokenId
2273
- };
2274
- }
2275
- };
2276
-
2277
- // src/prediction/order-manager.ts
2278
- var PolymarketOrderManager = class {
2279
- client;
2280
- config;
2281
- seenFillIds = /* @__PURE__ */ new Set();
2282
- recentFills = [];
2283
- positions = /* @__PURE__ */ new Map();
2284
- lastPriceRefresh = 0;
2285
- PRICE_REFRESH_MS = 1e4;
2286
- constructor(client, config) {
2287
- this.client = client;
2288
- this.config = config;
2289
- }
2290
- // ── SIGNAL EXECUTION ───────────────────────────────────────
2291
- async executeSignal(signal) {
2292
- if (signal.action === "hold") {
2293
- return { success: true, status: "cancelled", error: "Hold signal \u2014 no action" };
2294
- }
2295
- const violation = this.checkRiskLimits(signal);
2296
- if (violation) {
2297
- return { success: false, status: "error", error: violation };
2298
- }
2299
- try {
2300
- const market = await this.client.getMarketByConditionId(signal.marketConditionId);
2301
- if (!market) {
2302
- return { success: false, status: "error", error: `Market not found: ${signal.marketConditionId}` };
2303
- }
2304
- if (!market.active) {
2305
- return { success: false, status: "error", error: `Market is closed: ${market.question}` };
2306
- }
2307
- const tokenId = market.outcomeTokenIds[signal.outcomeIndex];
2308
- if (!tokenId) {
2309
- return { success: false, status: "error", error: `Invalid outcome index: ${signal.outcomeIndex}` };
2310
- }
2311
- const isBuy = signal.action === "buy_yes" || signal.action === "buy_no";
2312
- const side = isBuy ? "BUY" : "SELL";
2313
- let result;
2314
- if (signal.orderType === "market") {
2315
- result = await this.client.placeMarketOrder({
2316
- tokenId,
2317
- amount: signal.amount,
2318
- side
2319
- });
2320
- } else {
2321
- result = await this.client.placeLimitOrder({
2322
- tokenId,
2323
- price: signal.limitPrice,
2324
- size: signal.amount,
2325
- side
2326
- });
2327
- }
2328
- if (result.success) {
2329
- console.log(`[prediction] Order placed: ${signal.action} ${signal.amount} on "${market.question}" @ ${signal.limitPrice}`);
2330
- return { success: true, orderId: result.orderId, status: "resting" };
2331
- }
2332
- return { success: false, status: "error", error: "Order placement failed" };
2333
- } catch (error) {
2334
- const message = error instanceof Error ? error.message : String(error);
2335
- console.error("[prediction] Order execution error:", message);
2336
- return { success: false, status: "error", error: message };
2337
- }
2338
- }
2339
- // ── FILL POLLING ───────────────────────────────────────────
2340
- async pollNewFills() {
2341
- try {
2342
- const allFills = await this.client.getTradeHistory();
2343
- const newFills = [];
2344
- for (const fill of allFills) {
2345
- if (!this.seenFillIds.has(fill.tradeId)) {
2346
- this.seenFillIds.add(fill.tradeId);
2347
- newFills.push(fill);
2348
- this.processFill(fill);
2349
- this.recentFills.push(fill);
2350
- }
2351
- }
2352
- if (this.recentFills.length > 100) {
2353
- this.recentFills = this.recentFills.slice(-100);
2354
- }
2355
- return newFills;
2356
- } catch (error) {
2357
- const message = error instanceof Error ? error.message : String(error);
2358
- console.warn("[prediction] Fill polling error:", message);
2359
- return [];
2360
- }
2361
- }
2362
- getRecentFills() {
2363
- return this.recentFills;
2364
- }
2365
- // ── POSITION QUERIES ───────────────────────────────────────
2366
- async getPositions(forceRefresh = false) {
2367
- if (forceRefresh || Date.now() - this.lastPriceRefresh > this.PRICE_REFRESH_MS) {
2368
- await this.refreshPrices();
2369
- }
2370
- return Array.from(this.positions.values()).filter((p) => p.balance > 0).map((p) => this.toExternalPosition(p));
2371
- }
2372
- async getAccountSummary() {
2373
- const positions = await this.getPositions();
2374
- const totalExposure = positions.reduce((sum, p) => sum + p.costBasis, 0);
2375
- const totalUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
2376
- const openMarkets = new Set(positions.map((p) => p.marketConditionId));
2377
- return {
2378
- totalExposure,
2379
- totalUnrealizedPnl,
2380
- openMarketCount: openMarkets.size,
2381
- openPositionCount: positions.length
2382
- };
2383
- }
2384
- getTotalExposure() {
2385
- let total = 0;
2386
- for (const pos of this.positions.values()) {
2387
- if (pos.balance > 0) total += pos.totalCostBasis;
2388
- }
2389
- return total;
2390
- }
2391
- // ── CANCELLATION ───────────────────────────────────────────
2392
- async cancelOrder(orderId) {
2393
- return this.client.cancelOrder(orderId);
2394
- }
2395
- async cancelAllOrders() {
2396
- return this.client.cancelAllOrders();
2397
- }
2398
- // ── PRIVATE ────────────────────────────────────────────────
2399
- checkRiskLimits(signal) {
2400
- if (signal.amount > this.config.maxNotionalUSD) {
2401
- return `Trade exceeds max notional: $${signal.amount} > $${this.config.maxNotionalUSD}`;
2402
- }
2403
- if (signal.limitPrice <= 0 || signal.limitPrice >= 1) {
2404
- return `Invalid price: ${signal.limitPrice} (must be 0.01-0.99)`;
2405
- }
2406
- if (signal.confidence < 0.3) {
2407
- return `Low confidence: ${signal.confidence} < 0.3`;
2408
- }
2409
- const isBuy = signal.action === "buy_yes" || signal.action === "buy_no";
2410
- if (isBuy) {
2411
- const currentExposure = this.getTotalExposure();
2412
- if (currentExposure + signal.amount > this.config.maxTotalExposureUSD) {
2413
- return `Total exposure exceeded: $${currentExposure + signal.amount} > $${this.config.maxTotalExposureUSD}`;
2414
- }
2415
- }
2416
- return null;
2417
- }
2418
- processFill(fill) {
2419
- const key = `${fill.marketConditionId}:${fill.outcomeIndex}`;
2420
- let pos = this.positions.get(key);
2421
- const price = parseFloat(fill.price);
2422
- const size = parseFloat(fill.size);
2423
- if (!pos) {
2424
- pos = {
2425
- marketConditionId: fill.marketConditionId,
2426
- outcomeIndex: fill.outcomeIndex,
2427
- marketQuestion: fill.marketQuestion || "",
2428
- tokenId: fill.tokenId || "",
2429
- balance: 0,
2430
- totalCostBasis: 0,
2431
- totalProceeds: 0,
2432
- averageEntryPrice: 0,
2433
- currentPrice: price,
2434
- category: void 0,
2435
- endDate: void 0
2436
- };
2437
- this.positions.set(key, pos);
2438
- } else if (!pos.tokenId && fill.tokenId) {
2439
- pos.tokenId = fill.tokenId;
2440
- }
2441
- if (fill.side === "BUY") {
2442
- const oldCost = pos.averageEntryPrice * pos.balance;
2443
- const newCost = price * size;
2444
- pos.balance += size;
2445
- pos.totalCostBasis += newCost;
2446
- pos.averageEntryPrice = pos.balance > 0 ? (oldCost + newCost) / pos.balance : 0;
2447
- } else {
2448
- pos.balance -= size;
2449
- pos.totalProceeds += price * size;
2450
- if (pos.balance < 0) pos.balance = 0;
2451
- }
2452
- }
2453
- async refreshPrices() {
2454
- const openPositions = Array.from(this.positions.values()).filter((p) => p.balance > 0);
2455
- await Promise.all(
2456
- openPositions.map(async (pos) => {
2457
- try {
2458
- if (!pos.tokenId) {
2459
- const market = await this.client.getMarketByConditionId(pos.marketConditionId);
2460
- if (market && market.outcomeTokenIds[pos.outcomeIndex]) {
2461
- pos.tokenId = market.outcomeTokenIds[pos.outcomeIndex];
2462
- }
2463
- }
2464
- if (pos.tokenId) {
2465
- const price = await this.client.getMidpointPrice(pos.tokenId);
2466
- if (price > 0) pos.currentPrice = price;
2467
- }
2468
- } catch {
2469
- }
2470
- })
2471
- );
2472
- this.lastPriceRefresh = Date.now();
2473
- }
2474
- toExternalPosition(pos) {
2475
- const unrealizedPnl = pos.balance > 0 ? (pos.currentPrice - pos.averageEntryPrice) * pos.balance : 0;
2476
- return {
2477
- marketConditionId: pos.marketConditionId,
2478
- marketQuestion: pos.marketQuestion,
2479
- outcomeIndex: pos.outcomeIndex,
2480
- outcomeLabel: pos.outcomeIndex === 0 ? "Yes" : "No",
2481
- tokenId: pos.tokenId,
2482
- balance: pos.balance,
2483
- averageEntryPrice: pos.averageEntryPrice,
2484
- currentPrice: pos.currentPrice,
2485
- unrealizedPnl,
2486
- costBasis: pos.totalCostBasis - pos.totalProceeds,
2487
- endDate: pos.endDate,
2488
- category: pos.category
2489
- };
2490
- }
2491
- };
2492
-
2493
- // src/runtime.ts
2494
- import { createWalletClient, http } from "viem";
2495
- import { privateKeyToAccount } from "viem/accounts";
2496
- import { arbitrum } from "viem/chains";
2497
- var SDK_VERSION = "0.1.0";
2498
- var AgentRuntime = class {
2499
- config;
2500
- relay;
2501
- signal;
2502
- store;
2503
- positions;
2504
- llm;
2505
- strategy = null;
2506
- risk;
2507
- market;
2508
- paper = null;
2509
- // Venue clients
2510
- hlClient = null;
2511
- hlSigner = null;
2512
- hlOrders = null;
2513
- hlPositions = null;
2514
- hlWs = null;
2515
- pmClient = null;
2516
- pmOrders = null;
2517
- mode = "idle";
2518
- cycleCount = 0;
2519
- lastCycleAt = 0;
2520
- tradingInterval = null;
2521
- running = false;
2522
- constructor(config) {
2523
- this.config = config;
2524
- this.store = new FileStore(`data/${config.agentId}-store.json`);
2525
- this.positions = new PositionTracker(this.store);
2526
- this.market = new MarketDataService();
2527
- this.risk = new RiskManager(
2528
- {
2529
- maxPositionSizeBps: config.trading.maxPositionSizeBps,
2530
- maxDailyLossBps: config.trading.maxDailyLossBps,
2531
- maxConcurrentPositions: config.trading.maxConcurrentPositions,
2532
- maxSlippageBps: config.trading.maxSlippageBps,
2533
- minTradeValueUSD: config.trading.minTradeValueUSD
2534
- },
2535
- config.trading.initialCapitalUSD
2536
- );
2537
- this.llm = createLLMAdapter(config.llm);
2538
- this.relay = new RelayClient({
2539
- url: config.relay.url,
2540
- agentId: config.agentId,
2541
- token: config.apiToken,
2542
- heartbeatIntervalMs: config.relay.heartbeatIntervalMs,
2543
- reconnectMaxAttempts: config.relay.reconnectMaxAttempts,
2544
- onCommand: (cmd) => this.handleCommand(cmd),
2545
- onConnected: () => {
2546
- console.log(`[runtime] Connected to command center`);
2547
- this.sendStatus();
2548
- },
2549
- onDisconnected: () => {
2550
- console.log(`[runtime] Disconnected from command center`);
2551
- }
2552
- });
2553
- this.signal = new SignalReporter(this.relay);
2554
- if (config.trading.mode === "paper") {
2555
- this.paper = new PaperExecutor(config.trading.initialCapitalUSD ?? 1e4);
2556
- }
2557
- }
2558
- async start() {
2559
- console.log(`[runtime] Starting agent ${this.config.agentId}`);
2560
- console.log(`[runtime] Mode: ${this.config.trading.mode}`);
2561
- console.log(`[runtime] LLM: ${this.config.llm.provider}/${this.config.llm.model}`);
2562
- try {
2563
- this.strategy = await loadStrategy(this.config.strategy);
2564
- console.log(`[runtime] Strategy loaded`);
2565
- } catch (err) {
2566
- console.error(`[runtime] Failed to load strategy:`, err.message);
2567
- this.strategy = null;
2568
- }
2569
- if (this.config.trading.mode === "live") {
2570
- await this.initializeVenues();
2571
- }
2572
- try {
2573
- await this.relay.connect();
2574
- this.signal.reportInfo("Agent Started", `Agent ${this.config.agentId} connected to command center`);
2575
- } catch (err) {
2576
- console.error(`[runtime] Failed to connect to relay:`, err.message);
2577
- console.log(`[runtime] Will retry in background...`);
2578
- }
2579
- this.running = true;
2580
- this.mode = "idle";
2581
- this.sendStatus();
2582
- if (this.config.trading.mode === "paper" || this.config.trading.mode === "live") {
2583
- this.startTrading();
2584
- }
2585
- process.on("SIGTERM", () => this.stop());
2586
- process.on("SIGINT", () => this.stop());
2587
- }
2588
- async stop() {
2589
- console.log(`[runtime] Stopping agent ${this.config.agentId}`);
2590
- this.running = false;
2591
- this.stopTrading();
2592
- if (this.hlWs) {
2593
- this.hlWs.disconnect();
2594
- }
2595
- this.signal.reportInfo("Agent Stopped", `Agent ${this.config.agentId} shutting down`);
2596
- this.relay.disconnect();
2597
- console.log(`[runtime] Agent stopped`);
2598
- process.exit(0);
2599
- }
2600
- // ── VENUE INITIALIZATION ───────────────────────────────────
2601
- async initializeVenues() {
2602
- const privateKey = this.config.wallet?.privateKey;
2603
- if (!privateKey) {
2604
- console.log("[runtime] No wallet configured \u2014 live trading disabled");
2605
- return;
2606
- }
2607
- const hlConfig = this.config.venues?.hyperliquid_perp;
2608
- if (hlConfig?.enabled) {
2609
- try {
2610
- const account = privateKeyToAccount(privateKey);
2611
- const walletClient = createWalletClient({
2612
- account,
2613
- chain: arbitrum,
2614
- transport: http()
2615
- });
2616
- const perpConfig = {
2617
- enabled: true,
2618
- apiUrl: hlConfig.apiUrl,
2619
- wsUrl: hlConfig.wsUrl,
2620
- maxLeverage: hlConfig.maxLeverage,
2621
- maxNotionalUSD: hlConfig.maxNotionalUSD,
2622
- allowedInstruments: hlConfig.allowedInstruments
2623
- };
2624
- this.hlClient = new HyperliquidClient(perpConfig);
2625
- this.hlSigner = new HyperliquidSigner(walletClient);
2626
- this.hlOrders = new HyperliquidOrderManager(this.hlClient, this.hlSigner, perpConfig);
2627
- this.hlPositions = new HyperliquidPositionManager(this.hlClient, account.address);
2628
- this.hlWs = new HyperliquidWebSocket(perpConfig, account.address, this.hlClient);
2629
- await this.hlClient.getMeta();
2630
- this.hlWs.onFillReceived((fill) => {
2631
- this.handleHyperliquidFill(fill);
2632
- });
2633
- this.hlWs.onLiquidationDetected((instrument, size) => {
2634
- this.signal.reportError("Liquidation Detected", `${instrument}: ${size} liquidated`);
2635
- });
2636
- await this.hlWs.connect();
2637
- console.log(`[runtime] Hyperliquid initialized (${account.address})`);
2638
- } catch (err) {
2639
- console.error("[runtime] Failed to initialize Hyperliquid:", err.message);
2640
- }
2641
- }
2642
- const pmConfig = this.config.venues?.polymarket;
2643
- if (pmConfig?.enabled) {
2644
- try {
2645
- const predConfig = {
2646
- enabled: true,
2647
- clobApiUrl: pmConfig.clobApiUrl,
2648
- gammaApiUrl: pmConfig.gammaApiUrl,
2649
- maxNotionalUSD: pmConfig.maxNotionalUSD,
2650
- maxTotalExposureUSD: pmConfig.maxTotalExposureUSD,
2651
- allowedCategories: pmConfig.allowedCategories
2652
- };
2653
- this.pmClient = new PolymarketClient(privateKey, predConfig);
2654
- await this.pmClient.initialize();
2655
- this.pmOrders = new PolymarketOrderManager(this.pmClient, predConfig);
2656
- console.log(`[runtime] Polymarket initialized`);
2657
- } catch (err) {
2658
- console.error("[runtime] Failed to initialize Polymarket:", err.message);
2659
- }
2660
- }
2661
- }
2662
- // ── TRADING LOOP ───────────────────────────────────────────
2663
- startTrading() {
2664
- if (this.tradingInterval) return;
2665
- this.mode = this.config.trading.mode === "paper" ? "paper" : "trading";
2666
- console.log(`[runtime] Trading started (${this.mode}, interval: ${this.config.trading.tradingIntervalMs}ms)`);
2667
- this.sendStatus();
2668
- this.signal.reportInfo("Trading Started", `Mode: ${this.mode}`);
2669
- this.runCycle();
2670
- this.tradingInterval = setInterval(() => {
2671
- this.runCycle();
2672
- }, this.config.trading.tradingIntervalMs);
2673
- }
2674
- stopTrading() {
2675
- if (this.tradingInterval) {
2676
- clearInterval(this.tradingInterval);
2677
- this.tradingInterval = null;
2678
- }
2679
- this.mode = "idle";
2680
- this.sendStatus();
2681
- console.log(`[runtime] Trading stopped`);
2682
- }
2683
- async runCycle() {
2684
- if (!this.running || !this.strategy) return;
2685
- this.cycleCount++;
2686
- this.lastCycleAt = Date.now();
2687
- try {
2688
- const prices = this.market.getPrices();
2689
- const positionSummary = this.positions.getSummary(prices);
2690
- const signals = await this.strategy({
2691
- llm: this.llm,
2692
- market: this.market,
2693
- position: positionSummary,
2694
- store: this.store,
2695
- config: this.config.trading,
2696
- log: (msg) => {
2697
- console.log(`[strategy] ${msg}`);
2698
- this.signal.reportInfo("Strategy Log", msg);
2699
- }
2700
- });
2701
- if (!Array.isArray(signals) || signals.length === 0) {
2702
- await this.pollPredictionFills();
2703
- this.sendStatus();
2704
- return;
2705
- }
2706
- const filtered = this.risk.filterSignals(signals, this.market, positionSummary.openPositions.length);
2707
- for (const sig of filtered) {
2708
- try {
2709
- await this.executeSignal(sig);
2710
- } catch (err) {
2711
- console.error(`[runtime] Trade execution error:`, err.message);
2712
- this.signal.reportError("Trade Error", err.message);
2713
- }
2714
- }
2715
- await this.pollPredictionFills();
2716
- } catch (err) {
2717
- console.error(`[runtime] Cycle error:`, err.message);
2718
- this.signal.reportError("Cycle Error", err.message);
2719
- this.mode = "idle";
2720
- }
2721
- this.sendStatus();
2722
- }
2723
- // ── SIGNAL EXECUTION ───────────────────────────────────────
2724
- async executeSignal(sig) {
2725
- const venue = sig.venue;
2726
- if (this.paper) {
2727
- const trade = this.paper.execute(sig, this.market);
2728
- if (trade) {
2729
- this.signal.reportTrade({
2730
- ...sig,
2731
- price: trade.entryPrice,
2732
- fee: trade.fee,
2733
- venue: trade.venue,
2734
- venueFillId: trade.id,
2735
- venueTimestamp: new Date(trade.timestamp).toISOString()
2736
- });
2737
- }
2738
- return;
2739
- }
2740
- if (venue === "hyperliquid_perp" && this.hlOrders) {
2741
- await this.executeHyperliquidSignal(sig);
2742
- } else if (venue === "polymarket" && this.pmOrders) {
2743
- await this.executePolymarketSignal(sig);
2744
- } else {
2745
- console.log(`[runtime] No executor for venue "${venue}" \u2014 signal dropped`);
2746
- }
2747
- }
2748
- async executeHyperliquidSignal(sig) {
2749
- if (!this.hlOrders) return;
2750
- const action = sig.side === "long" ? "open_long" : sig.side === "short" ? "open_short" : sig.side === "buy" ? "open_long" : "close_long";
2751
- const result = await this.hlOrders.placeOrder({
2752
- action,
2753
- instrument: sig.symbol,
2754
- size: sig.size,
2755
- price: sig.price,
2756
- leverage: sig.leverage ?? 1,
2757
- orderType: sig.orderType ?? "market",
2758
- reduceOnly: action.startsWith("close"),
2759
- confidence: sig.confidence ?? 1,
2760
- reasoning: sig.reasoning
2761
- });
2762
- if (result.success && result.status === "filled") {
2763
- console.log(`[runtime] HL order filled: ${sig.symbol} ${result.avgPrice}`);
2764
- } else if (result.success && result.status === "resting") {
2765
- console.log(`[runtime] HL order resting: ${sig.symbol} (oid: ${result.orderId})`);
2766
- } else {
2767
- console.warn(`[runtime] HL order failed: ${result.error}`);
2768
- this.signal.reportError("Perp Order Failed", `${sig.symbol}: ${result.error}`);
2769
- }
2770
- }
2771
- async executePolymarketSignal(sig) {
2772
- if (!this.pmOrders) return;
2773
- const action = sig.side === "buy" ? "buy_yes" : sig.side === "sell" ? "sell_yes" : sig.side === "long" ? "buy_yes" : "sell_yes";
2774
- const result = await this.pmOrders.executeSignal({
2775
- action,
2776
- marketConditionId: sig.symbol,
2777
- marketQuestion: sig.reasoning ?? sig.symbol,
2778
- outcomeIndex: 0,
2779
- amount: sig.size,
2780
- limitPrice: sig.price,
2781
- orderType: sig.orderType ?? "limit",
2782
- confidence: sig.confidence ?? 1,
2783
- reasoning: sig.reasoning
2784
- });
2785
- if (result.success) {
2786
- console.log(`[runtime] PM order placed: ${sig.symbol}`);
2787
- } else {
2788
- console.warn(`[runtime] PM order failed: ${result.error}`);
2789
- this.signal.reportError("Prediction Order Failed", `${sig.symbol}: ${result.error}`);
2790
- }
2791
- }
2792
- // ── FILL HANDLING ──────────────────────────────────────────
2793
- handleHyperliquidFill(fill) {
2794
- const tradeSignal = {
2795
- venue: "hyperliquid_perp",
2796
- symbol: fill.coin,
2797
- side: fill.side === "B" ? "long" : "short",
2798
- size: parseFloat(fill.sz),
2799
- price: parseFloat(fill.px),
2800
- fee: parseFloat(fill.fee),
2801
- venueFillId: fill.hash,
2802
- venueTimestamp: new Date(fill.time).toISOString(),
2803
- orderType: fill.isMaker ? "limit" : "market"
2804
- };
2805
- const action = fill.side === "B" ? "buy" : "sell";
2806
- if (action === "buy") {
2807
- this.positions.recordBuy(
2808
- fill.coin,
2809
- parseFloat(fill.sz),
2810
- parseFloat(fill.px),
2811
- parseFloat(fill.fee),
2812
- "hyperliquid_perp",
2813
- void 0,
2814
- fill.hash
2815
- );
2816
- } else {
2817
- this.positions.recordSell(
2818
- fill.coin,
2819
- parseFloat(fill.sz),
2820
- parseFloat(fill.px),
2821
- parseFloat(fill.fee),
2822
- "hyperliquid_perp",
2823
- void 0,
2824
- fill.hash
2825
- );
2826
- }
2827
- this.signal.reportPerpFill(tradeSignal);
2828
- }
2829
- async pollPredictionFills() {
2830
- if (!this.pmOrders) return;
2831
- try {
2832
- const newFills = await this.pmOrders.pollNewFills();
2833
- for (const fill of newFills) {
2834
- const tradeSignal = {
2835
- venue: "polymarket",
2836
- chain: "polygon",
2837
- symbol: encodePredictionInstrument(fill.marketConditionId, fill.outcomeIndex),
2838
- side: fill.side === "BUY" ? "buy" : "sell",
2839
- size: parseFloat(fill.size),
2840
- price: parseFloat(fill.price),
2841
- fee: parseFloat(fill.fee),
2842
- venueFillId: fill.tradeId,
2843
- venueTimestamp: new Date(fill.timestamp).toISOString()
2844
- };
2845
- this.signal.reportPredictionFill(tradeSignal);
2846
- }
2847
- } catch (err) {
2848
- console.warn("[runtime] Prediction fill poll error:", err.message);
2849
- }
2850
- }
2851
- // ── COMMANDS ───────────────────────────────────────────────
2852
- handleCommand(command) {
2853
- console.log(`[runtime] Command received: ${command.type}`);
2854
- switch (command.type) {
2855
- case "start_trading":
2856
- this.startTrading();
2857
- break;
2858
- case "stop_trading":
2859
- this.stopTrading();
2860
- break;
2861
- case "update_risk_params":
2862
- if (command.params) {
2863
- console.log(`[runtime] Risk params updated`);
2864
- }
2865
- break;
2866
- case "get_status":
2867
- this.sendStatus();
2868
- break;
2869
- case "reload_config":
2870
- console.log(`[runtime] Config reload requested`);
2871
- break;
2872
- default:
2873
- console.warn(`[runtime] Unknown command: ${command.type}`);
2874
- }
2875
- }
2876
- // ── STATUS ─────────────────────────────────────────────────
2877
- sendStatus() {
2878
- const status = {
2879
- mode: this.mode,
2880
- agentId: this.config.agentId,
2881
- sdkVersion: SDK_VERSION,
2882
- cycleCount: this.cycleCount,
2883
- lastCycleAt: this.lastCycleAt,
2884
- tradingIntervalMs: this.config.trading.tradingIntervalMs,
2885
- llm: {
2886
- provider: this.config.llm.provider,
2887
- model: this.config.llm.model || "default"
2888
- },
2889
- risk: {
2890
- dailyPnL: this.risk.getDailyPnL(),
2891
- dailyLossLimit: this.risk.getDailyLossLimit(),
2892
- isLimitHit: this.risk.isDailyLossLimitHit()
2893
- },
2894
- positions: {
2895
- openPositions: this.positions.getPositions().length,
2896
- totalUnrealizedPnL: 0,
2897
- totalRealizedPnL: this.positions.getRealizedPnL()
2898
- },
2899
- venues: {
2900
- hyperliquid: {
2901
- enabled: !!this.hlClient,
2902
- trading: !!this.hlOrders && this.mode === "trading"
2903
- },
2904
- polymarket: {
2905
- enabled: !!this.pmClient,
2906
- trading: !!this.pmOrders && this.mode === "trading"
2907
- }
2908
- }
2909
- };
2910
- if (this.paper) {
2911
- const metrics = this.paper.getMetrics();
2912
- status.paper = {
2913
- active: true,
2914
- simulatedValue: this.paper.getEquity(),
2915
- simulatedPnLPct: metrics.totalReturn * 100
2916
- };
2917
- status.portfolioValue = this.paper.getEquity();
2918
- }
2919
- this.relay.sendHeartbeat(status);
2920
- }
2921
- };
2922
-
2923
- export {
2924
- loadConfig,
2925
- generateSampleConfig,
2926
- writeSampleConfig,
2927
- RelayClient,
2928
- SignalReporter,
2929
- FileStore,
2930
- PositionTracker,
2931
- createLLMAdapter,
2932
- getTemplate,
2933
- listTemplates,
2934
- loadStrategy,
2935
- RiskManager,
2936
- MarketDataService,
2937
- PaperExecutor,
2938
- HyperliquidClient,
2939
- HYPERLIQUID_DOMAIN,
2940
- getNextNonce,
2941
- HyperliquidSigner,
2942
- HyperliquidOrderManager,
2943
- HyperliquidPositionManager,
2944
- HyperliquidWebSocket,
2945
- encodePredictionInstrument,
2946
- decodePredictionInstrument,
2947
- PolymarketClient,
2948
- PolymarketOrderManager,
2949
- AgentRuntime
2950
- };