@exagent/agent 0.3.6 → 0.3.7

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 (68) hide show
  1. package/dist/chunk-7UGLJO6W.js +6392 -0
  2. package/dist/chunk-EHAOPCTJ.js +6406 -0
  3. package/dist/chunk-FGMXTW5I.js +6540 -0
  4. package/dist/chunk-IVA2SCSN.js +6756 -0
  5. package/dist/chunk-JHXCSGPC.js +6352 -0
  6. package/dist/chunk-V6O4UXVN.js +6345 -0
  7. package/dist/chunk-WTECTX2Z.js +6345 -0
  8. package/dist/cli.js +2 -2
  9. package/dist/index.d.ts +24 -2
  10. package/dist/index.js +1 -1
  11. package/package.json +12 -9
  12. package/src/bridge/across.ts +0 -240
  13. package/src/bridge/bridge-manager.ts +0 -87
  14. package/src/bridge/index.ts +0 -9
  15. package/src/bridge/types.ts +0 -77
  16. package/src/chains.ts +0 -105
  17. package/src/cli.ts +0 -250
  18. package/src/config.ts +0 -502
  19. package/src/diagnostics.ts +0 -335
  20. package/src/index.ts +0 -98
  21. package/src/llm/anthropic.ts +0 -63
  22. package/src/llm/base.ts +0 -264
  23. package/src/llm/deepseek.ts +0 -48
  24. package/src/llm/google.ts +0 -63
  25. package/src/llm/groq.ts +0 -48
  26. package/src/llm/index.ts +0 -42
  27. package/src/llm/mistral.ts +0 -48
  28. package/src/llm/ollama.ts +0 -52
  29. package/src/llm/openai.ts +0 -94
  30. package/src/llm/together.ts +0 -48
  31. package/src/llm-providers.ts +0 -8
  32. package/src/logger.ts +0 -137
  33. package/src/paper/executor.ts +0 -201
  34. package/src/paper/index.ts +0 -1
  35. package/src/perp/client.ts +0 -200
  36. package/src/perp/index.ts +0 -12
  37. package/src/perp/msgpack.ts +0 -272
  38. package/src/perp/orders.ts +0 -234
  39. package/src/perp/positions.ts +0 -126
  40. package/src/perp/signer.ts +0 -277
  41. package/src/perp/types.ts +0 -192
  42. package/src/perp/websocket.ts +0 -274
  43. package/src/position-tracker.ts +0 -243
  44. package/src/prediction/client.ts +0 -288
  45. package/src/prediction/index.ts +0 -3
  46. package/src/prediction/order-manager.ts +0 -297
  47. package/src/prediction/types.ts +0 -151
  48. package/src/relay.ts +0 -254
  49. package/src/runtime.ts +0 -1755
  50. package/src/scrub-secrets.ts +0 -39
  51. package/src/setup.ts +0 -392
  52. package/src/signal.ts +0 -212
  53. package/src/spot/aerodrome.ts +0 -158
  54. package/src/spot/client.ts +0 -138
  55. package/src/spot/index.ts +0 -11
  56. package/src/spot/swap-manager.ts +0 -219
  57. package/src/spot/types.ts +0 -203
  58. package/src/spot/uniswap.ts +0 -150
  59. package/src/store.ts +0 -50
  60. package/src/strategy/index.ts +0 -2
  61. package/src/strategy/loader.ts +0 -265
  62. package/src/strategy/templates.ts +0 -74
  63. package/src/trading/index.ts +0 -2
  64. package/src/trading/market.ts +0 -120
  65. package/src/trading/risk.ts +0 -107
  66. package/src/ui.ts +0 -75
  67. package/test/strategy-loader.test.ts +0 -150
  68. package/tsconfig.json +0 -8
@@ -1,274 +0,0 @@
1
- /**
2
- * Hyperliquid WebSocket Client
3
- *
4
- * Real-time fill and funding event subscription.
5
- * Auto-reconnects with exponential backoff.
6
- * Checkpoints last processed fill time for REST backfill on reconnect.
7
- */
8
-
9
- import WebSocket from 'ws';
10
- import type { PerpConfig, PerpFill } from './types.js';
11
- import { HyperliquidClient } from './client.js';
12
-
13
- export type FillCallback = (fill: PerpFill) => void;
14
- export type FundingCallback = (funding: FundingPayment) => void;
15
- export type LiquidationCallback = (instrument: string, size: number) => void;
16
-
17
- export interface FundingPayment {
18
- time: number;
19
- coin: string;
20
- usdc: string;
21
- szi: string;
22
- fundingRate: string;
23
- }
24
-
25
- export class HyperliquidWebSocket {
26
- private readonly wsUrl: string;
27
- private readonly userAddress: string;
28
- private readonly client: HyperliquidClient;
29
-
30
- private ws: WebSocket | null = null;
31
- private reconnectAttempts: number = 0;
32
- private readonly maxReconnectAttempts: number = 20;
33
- private readonly baseReconnectMs: number = 1_000;
34
- private readonly maxReconnectMs: number = 60_000;
35
- private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
36
- private pingTimer: ReturnType<typeof setInterval> | null = null;
37
- private isConnecting: boolean = false;
38
- private shouldReconnect: boolean = true;
39
-
40
- private lastProcessedFillTime: number = 0;
41
-
42
- private onFill: FillCallback | null = null;
43
- private onFunding: FundingCallback | null = null;
44
- private onLiquidation: LiquidationCallback | null = null;
45
-
46
- constructor(config: PerpConfig, userAddress: string, client: HyperliquidClient) {
47
- this.wsUrl = config.wsUrl;
48
- this.userAddress = userAddress;
49
- this.client = client;
50
- }
51
-
52
- // ── CONNECTION ─────────────────────────────────────────────
53
-
54
- async connect(): Promise<void> {
55
- if (this.ws?.readyState === WebSocket.OPEN || this.isConnecting) return;
56
-
57
- this.isConnecting = true;
58
- this.shouldReconnect = true;
59
-
60
- return new Promise<void>((resolve, reject) => {
61
- try {
62
- this.ws = new WebSocket(this.wsUrl);
63
-
64
- this.ws.on('open', () => {
65
- this.isConnecting = false;
66
- this.reconnectAttempts = 0;
67
- console.log('[perp-ws] Connected');
68
-
69
- this.subscribe({
70
- type: 'subscribe',
71
- subscription: { type: 'userFills', user: this.userAddress },
72
- });
73
-
74
- this.subscribe({
75
- type: 'subscribe',
76
- subscription: { type: 'userFundings', user: this.userAddress },
77
- });
78
-
79
- this.startPing();
80
-
81
- this.backfillMissedFills().catch((err) => {
82
- console.warn('[perp-ws] Backfill failed:', err instanceof Error ? err.message : err);
83
- });
84
-
85
- resolve();
86
- });
87
-
88
- this.ws.on('message', (data: WebSocket.Data) => {
89
- this.handleMessage(data);
90
- });
91
-
92
- this.ws.on('close', (code: number, reason: Buffer) => {
93
- this.isConnecting = false;
94
- console.log(`[perp-ws] Closed: ${code} ${reason.toString()}`);
95
- this.stopPing();
96
- this.scheduleReconnect();
97
- });
98
-
99
- this.ws.on('error', (error: Error) => {
100
- this.isConnecting = false;
101
- console.error('[perp-ws] Error:', error.message);
102
- if (this.reconnectAttempts === 0) reject(error);
103
- });
104
- } catch (error) {
105
- this.isConnecting = false;
106
- reject(error);
107
- }
108
- });
109
- }
110
-
111
- disconnect(): void {
112
- this.shouldReconnect = false;
113
- if (this.reconnectTimer) {
114
- clearTimeout(this.reconnectTimer);
115
- this.reconnectTimer = null;
116
- }
117
- this.stopPing();
118
- if (this.ws) {
119
- this.ws.removeAllListeners();
120
- if (this.ws.readyState === WebSocket.OPEN) {
121
- this.ws.close(1000, 'Client disconnect');
122
- }
123
- this.ws = null;
124
- }
125
- console.log('[perp-ws] Disconnected');
126
- }
127
-
128
- get isConnected(): boolean {
129
- return this.ws?.readyState === WebSocket.OPEN;
130
- }
131
-
132
- // ── EVENT HANDLERS ─────────────────────────────────────────
133
-
134
- onFillReceived(callback: FillCallback): void {
135
- this.onFill = callback;
136
- }
137
-
138
- onFundingReceived(callback: FundingCallback): void {
139
- this.onFunding = callback;
140
- }
141
-
142
- onLiquidationDetected(callback: LiquidationCallback): void {
143
- this.onLiquidation = callback;
144
- }
145
-
146
- getLastProcessedFillTime(): number {
147
- return this.lastProcessedFillTime;
148
- }
149
-
150
- // ── MESSAGE HANDLING ───────────────────────────────────────
151
-
152
- private handleMessage(data: WebSocket.Data): void {
153
- try {
154
- const msg = JSON.parse(data.toString());
155
- if (msg.channel === 'userFills') {
156
- this.handleFillMessage(msg.data);
157
- } else if (msg.channel === 'userFundings') {
158
- this.handleFundingMessage(msg.data);
159
- }
160
- } catch {
161
- // Ignore parse errors (pong frames, etc.)
162
- }
163
- }
164
-
165
- private handleFillMessage(fills: any[]): void {
166
- if (!Array.isArray(fills) || !this.onFill) return;
167
-
168
- for (const rawFill of fills) {
169
- const fill = this.client.parseFill(rawFill);
170
-
171
- if (fill.time > this.lastProcessedFillTime) {
172
- this.lastProcessedFillTime = fill.time;
173
- }
174
-
175
- if (fill.liquidation && this.onLiquidation) {
176
- this.onLiquidation(fill.coin, parseFloat(fill.sz));
177
- }
178
-
179
- this.onFill(fill);
180
- }
181
- }
182
-
183
- private handleFundingMessage(fundings: any[]): void {
184
- if (!Array.isArray(fundings) || !this.onFunding) return;
185
-
186
- for (const funding of fundings) {
187
- this.onFunding({
188
- time: funding.time,
189
- coin: funding.coin,
190
- usdc: funding.usdc,
191
- szi: funding.szi,
192
- fundingRate: funding.fundingRate,
193
- });
194
- }
195
- }
196
-
197
- // ── BACKFILL ───────────────────────────────────────────────
198
-
199
- private async backfillMissedFills(): Promise<void> {
200
- if (this.lastProcessedFillTime === 0 || !this.onFill) return;
201
-
202
- console.log(`[perp-ws] Backfilling fills since ${new Date(this.lastProcessedFillTime).toISOString()}`);
203
-
204
- const fills = await this.client.getUserFillsByTime(
205
- this.userAddress,
206
- this.lastProcessedFillTime + 1,
207
- );
208
-
209
- if (fills.length > 0) {
210
- console.log(`[perp-ws] Backfilled ${fills.length} missed fills`);
211
- for (const rawFill of fills) {
212
- const fill = this.client.parseFill(rawFill);
213
- if (fill.time > this.lastProcessedFillTime) {
214
- this.lastProcessedFillTime = fill.time;
215
- }
216
- if (fill.liquidation && this.onLiquidation) {
217
- this.onLiquidation(fill.coin, parseFloat(fill.sz));
218
- }
219
- this.onFill(fill);
220
- }
221
- }
222
- }
223
-
224
- // ── RECONNECTION ───────────────────────────────────────────
225
-
226
- private scheduleReconnect(): void {
227
- if (!this.shouldReconnect || this.reconnectAttempts >= this.maxReconnectAttempts) {
228
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
229
- console.error(`[perp-ws] Max reconnect attempts (${this.maxReconnectAttempts}) reached`);
230
- }
231
- return;
232
- }
233
-
234
- const delay = Math.min(
235
- this.baseReconnectMs * Math.pow(2, this.reconnectAttempts),
236
- this.maxReconnectMs,
237
- );
238
-
239
- this.reconnectAttempts++;
240
- console.log(`[perp-ws] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
241
-
242
- this.reconnectTimer = setTimeout(() => {
243
- this.connect().catch((err) => {
244
- console.error('[perp-ws] Reconnect failed:', err instanceof Error ? err.message : err);
245
- });
246
- }, delay);
247
- }
248
-
249
- // ── KEEPALIVE ──────────────────────────────────────────────
250
-
251
- private startPing(): void {
252
- this.stopPing();
253
- this.pingTimer = setInterval(() => {
254
- if (this.ws?.readyState === WebSocket.OPEN) {
255
- this.ws.send(JSON.stringify({ method: 'ping' }));
256
- }
257
- }, 25_000);
258
- }
259
-
260
- private stopPing(): void {
261
- if (this.pingTimer) {
262
- clearInterval(this.pingTimer);
263
- this.pingTimer = null;
264
- }
265
- }
266
-
267
- // ── HELPERS ────────────────────────────────────────────────
268
-
269
- private subscribe(msg: Record<string, unknown>): void {
270
- if (this.ws?.readyState === WebSocket.OPEN) {
271
- this.ws.send(JSON.stringify(msg));
272
- }
273
- }
274
- }
@@ -1,243 +0,0 @@
1
- import type { TrackedPosition, TradeRecord, PositionSummary, StrategyStore } from '@exagent/sdk';
2
-
3
- /**
4
- * Stablecoins and quote assets that should NOT be tracked as positions
5
- * on spot DEX venues (you don't want a "USDC position" when swapping).
6
- * On perp venues, ALL instruments are tracked (ETH, BTC, etc.).
7
- */
8
- const QUOTE_ASSETS = new Set(['USDC', 'USDbC', 'DAI', 'USDT', 'EURC']);
9
-
10
- /** Perp venues where positions can be short (negative quantity) */
11
- const PERP_VENUES = new Set(['hyperliquid_perp']);
12
-
13
- /** Prediction market venues */
14
- const PREDICTION_VENUES = new Set(['polymarket']);
15
-
16
- export class PositionTracker {
17
- private positions: Map<string, TrackedPosition> = new Map();
18
- private trades: TradeRecord[] = [];
19
- private realizedPnL = 0;
20
- private store: StrategyStore;
21
-
22
- constructor(store: StrategyStore) {
23
- this.store = store;
24
- this.load();
25
- }
26
-
27
- /** Build a unique position key: venue-scoped when venue is provided */
28
- private positionKey(token: string, venue?: string): string {
29
- return venue ? `${venue}:${token}` : token;
30
- }
31
-
32
- private load(): void {
33
- const saved = this.store.get<{
34
- positions: [string, TrackedPosition][];
35
- trades: TradeRecord[];
36
- realizedPnL: number;
37
- }>('position_tracker');
38
-
39
- if (saved) {
40
- this.positions = new Map(saved.positions);
41
- this.trades = saved.trades;
42
- this.realizedPnL = saved.realizedPnL;
43
- }
44
- }
45
-
46
- private save(): void {
47
- this.store.set('position_tracker', {
48
- positions: Array.from(this.positions.entries()),
49
- trades: this.trades.slice(-1000),
50
- realizedPnL: this.realizedPnL,
51
- });
52
- }
53
-
54
- /** Returns realized PnL from this trade (non-zero when closing/reducing a position) */
55
- recordBuy(
56
- token: string,
57
- quantity: number,
58
- price: number,
59
- fee: number,
60
- venue?: string,
61
- chain?: string,
62
- venueFillId?: string,
63
- ): number {
64
- // Skip quote assets on spot venues (no "USDC position" from swaps).
65
- // Perp and prediction venues track all instruments.
66
- const isPerp = venue ? PERP_VENUES.has(venue) : false;
67
- const isPrediction = venue ? PREDICTION_VENUES.has(venue) : false;
68
- if (!isPerp && !isPrediction && QUOTE_ASSETS.has(token)) return 0;
69
-
70
- const key = this.positionKey(token, venue);
71
- const existing = this.positions.get(key);
72
- let tradePnL = 0;
73
-
74
- if (existing) {
75
- if (isPerp && existing.quantity < 0) {
76
- // Buying to close/reduce a short position
77
- const closedQty = Math.min(quantity, Math.abs(existing.quantity));
78
- tradePnL = (existing.costBasisPerUnit - price) * closedQty - fee;
79
- this.realizedPnL += tradePnL;
80
-
81
- existing.quantity += quantity;
82
- if (Math.abs(existing.quantity) <= 0.000001) {
83
- this.positions.delete(key);
84
- } else if (existing.quantity > 0) {
85
- // Flipped from short to long — reset cost basis
86
- existing.costBasisPerUnit = price;
87
- }
88
- } else {
89
- // Adding to a long position (or adding to perp long)
90
- const totalQty = existing.quantity + quantity;
91
- const totalCost = existing.costBasisPerUnit * existing.quantity + price * quantity;
92
- existing.costBasisPerUnit = totalCost / totalQty;
93
- existing.quantity = totalQty;
94
- }
95
- } else {
96
- this.positions.set(key, {
97
- token,
98
- quantity,
99
- costBasisPerUnit: price,
100
- entryTimestamp: Date.now(),
101
- venue,
102
- chain,
103
- });
104
- }
105
-
106
- this.trades.push({
107
- token,
108
- action: 'buy',
109
- quantity,
110
- price,
111
- fee,
112
- timestamp: Date.now(),
113
- venue,
114
- chain,
115
- venueFillId,
116
- });
117
-
118
- this.save();
119
- return tradePnL;
120
- }
121
-
122
- /** Returns realized PnL from this trade (non-zero when closing/reducing a position) */
123
- recordSell(
124
- token: string,
125
- quantity: number,
126
- price: number,
127
- fee: number,
128
- venue?: string,
129
- chain?: string,
130
- venueFillId?: string,
131
- ): number {
132
- // Skip quote assets on spot venues (no "USDC position" from swaps).
133
- // Perp and prediction venues track all instruments.
134
- const isPerp = venue ? PERP_VENUES.has(venue) : false;
135
- const isPrediction = venue ? PREDICTION_VENUES.has(venue) : false;
136
- if (!isPerp && !isPrediction && QUOTE_ASSETS.has(token)) return 0;
137
-
138
- const key = this.positionKey(token, venue);
139
- const existing = this.positions.get(key);
140
- let tradePnL = 0;
141
-
142
- if (existing) {
143
- if (isPerp && existing.quantity > 0) {
144
- // Selling to close/reduce a long position
145
- const closedQty = Math.min(quantity, existing.quantity);
146
- tradePnL = (price - existing.costBasisPerUnit) * closedQty - fee;
147
- this.realizedPnL += tradePnL;
148
-
149
- existing.quantity -= quantity;
150
- if (Math.abs(existing.quantity) <= 0.000001) {
151
- this.positions.delete(key);
152
- } else if (existing.quantity < 0) {
153
- // Flipped from long to short — reset cost basis
154
- existing.costBasisPerUnit = price;
155
- }
156
- } else if (isPerp && existing.quantity <= 0) {
157
- // Adding to a short position
158
- const totalQty = Math.abs(existing.quantity) + quantity;
159
- const totalCost = existing.costBasisPerUnit * Math.abs(existing.quantity) + price * quantity;
160
- existing.costBasisPerUnit = totalCost / totalQty;
161
- existing.quantity -= quantity;
162
- } else {
163
- // Spot sell — standard close logic
164
- tradePnL = (price - existing.costBasisPerUnit) * quantity - fee;
165
- this.realizedPnL += tradePnL;
166
-
167
- existing.quantity -= quantity;
168
- if (existing.quantity <= 0.000001) {
169
- this.positions.delete(key);
170
- }
171
- }
172
- } else if (isPerp) {
173
- // Opening a new short position (perps only)
174
- this.positions.set(key, {
175
- token,
176
- quantity: -quantity,
177
- costBasisPerUnit: price,
178
- entryTimestamp: Date.now(),
179
- venue,
180
- chain,
181
- });
182
- }
183
-
184
- this.trades.push({
185
- token,
186
- action: 'sell',
187
- quantity,
188
- price,
189
- fee,
190
- timestamp: Date.now(),
191
- venue,
192
- chain,
193
- venueFillId,
194
- });
195
-
196
- this.save();
197
- return tradePnL;
198
- }
199
-
200
- getSummary(prices: Record<string, number>): PositionSummary {
201
- let totalUnrealizedPnL = 0;
202
-
203
- const openPositions = Array.from(this.positions.values());
204
- for (const pos of openPositions) {
205
- const currentPrice = prices[pos.token];
206
- if (currentPrice) {
207
- if (pos.quantity >= 0) {
208
- // Long: profit when price goes up
209
- totalUnrealizedPnL += (currentPrice - pos.costBasisPerUnit) * pos.quantity;
210
- } else {
211
- // Short: profit when price goes down
212
- totalUnrealizedPnL += (pos.costBasisPerUnit - currentPrice) * Math.abs(pos.quantity);
213
- }
214
- }
215
- }
216
-
217
- return {
218
- openPositions,
219
- totalUnrealizedPnL,
220
- totalRealizedPnL: this.realizedPnL,
221
- };
222
- }
223
-
224
- getPositions(): TrackedPosition[] {
225
- return Array.from(this.positions.values());
226
- }
227
-
228
- getTrades(limit?: number): TradeRecord[] {
229
- if (limit) return this.trades.slice(-limit);
230
- return [...this.trades];
231
- }
232
-
233
- getRealizedPnL(): number {
234
- return this.realizedPnL;
235
- }
236
-
237
- reset(): void {
238
- this.positions.clear();
239
- this.trades = [];
240
- this.realizedPnL = 0;
241
- this.save();
242
- }
243
- }