@exagent/agent 0.3.6 → 0.3.8

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 (69) 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-GYYW4EKM.js +6756 -0
  5. package/dist/chunk-IVA2SCSN.js +6756 -0
  6. package/dist/chunk-JHXCSGPC.js +6352 -0
  7. package/dist/chunk-V6O4UXVN.js +6345 -0
  8. package/dist/chunk-WTECTX2Z.js +6345 -0
  9. package/dist/cli.js +2 -2
  10. package/dist/index.d.ts +24 -2
  11. package/dist/index.js +1 -1
  12. package/package.json +12 -9
  13. package/src/bridge/across.ts +0 -240
  14. package/src/bridge/bridge-manager.ts +0 -87
  15. package/src/bridge/index.ts +0 -9
  16. package/src/bridge/types.ts +0 -77
  17. package/src/chains.ts +0 -105
  18. package/src/cli.ts +0 -250
  19. package/src/config.ts +0 -502
  20. package/src/diagnostics.ts +0 -335
  21. package/src/index.ts +0 -98
  22. package/src/llm/anthropic.ts +0 -63
  23. package/src/llm/base.ts +0 -264
  24. package/src/llm/deepseek.ts +0 -48
  25. package/src/llm/google.ts +0 -63
  26. package/src/llm/groq.ts +0 -48
  27. package/src/llm/index.ts +0 -42
  28. package/src/llm/mistral.ts +0 -48
  29. package/src/llm/ollama.ts +0 -52
  30. package/src/llm/openai.ts +0 -94
  31. package/src/llm/together.ts +0 -48
  32. package/src/llm-providers.ts +0 -8
  33. package/src/logger.ts +0 -137
  34. package/src/paper/executor.ts +0 -201
  35. package/src/paper/index.ts +0 -1
  36. package/src/perp/client.ts +0 -200
  37. package/src/perp/index.ts +0 -12
  38. package/src/perp/msgpack.ts +0 -272
  39. package/src/perp/orders.ts +0 -234
  40. package/src/perp/positions.ts +0 -126
  41. package/src/perp/signer.ts +0 -277
  42. package/src/perp/types.ts +0 -192
  43. package/src/perp/websocket.ts +0 -274
  44. package/src/position-tracker.ts +0 -243
  45. package/src/prediction/client.ts +0 -288
  46. package/src/prediction/index.ts +0 -3
  47. package/src/prediction/order-manager.ts +0 -297
  48. package/src/prediction/types.ts +0 -151
  49. package/src/relay.ts +0 -254
  50. package/src/runtime.ts +0 -1755
  51. package/src/scrub-secrets.ts +0 -39
  52. package/src/setup.ts +0 -392
  53. package/src/signal.ts +0 -212
  54. package/src/spot/aerodrome.ts +0 -158
  55. package/src/spot/client.ts +0 -138
  56. package/src/spot/index.ts +0 -11
  57. package/src/spot/swap-manager.ts +0 -219
  58. package/src/spot/types.ts +0 -203
  59. package/src/spot/uniswap.ts +0 -150
  60. package/src/store.ts +0 -50
  61. package/src/strategy/index.ts +0 -2
  62. package/src/strategy/loader.ts +0 -265
  63. package/src/strategy/templates.ts +0 -74
  64. package/src/trading/index.ts +0 -2
  65. package/src/trading/market.ts +0 -120
  66. package/src/trading/risk.ts +0 -107
  67. package/src/ui.ts +0 -75
  68. package/test/strategy-loader.test.ts +0 -150
  69. package/tsconfig.json +0 -8
@@ -1,288 +0,0 @@
1
- /**
2
- * Polymarket Client
3
- *
4
- * CLOB API (authenticated, Polygon) for orders and fills.
5
- * Gamma API (public) for market discovery, search, trending.
6
- *
7
- * Uses @polymarket/clob-client for L2 auth and order signing.
8
- * API key derived once per session from wallet signature.
9
- */
10
-
11
- import { ClobClient, Side } from '@polymarket/clob-client';
12
- import { createWalletClient, http, type WalletClient } from 'viem';
13
- import { privateKeyToAccount } from 'viem/accounts';
14
- import { polygon } from 'viem/chains';
15
- import type {
16
- PredictionConfig,
17
- PredictionMarket,
18
- PredictionFill,
19
- } from './types.js';
20
- import { DEFAULT_PREDICTION_CONFIG, POLYGON_CHAIN_ID } from './types.js';
21
-
22
- interface ApiKeyCreds {
23
- key: string;
24
- secret: string;
25
- passphrase: string;
26
- }
27
-
28
- interface OrderBookLevel {
29
- price: number;
30
- size: number;
31
- }
32
-
33
- export class PolymarketClient {
34
- private readonly config: PredictionConfig;
35
- private clobClient: ClobClient | null = null;
36
- private apiCreds: ApiKeyCreds | null = null;
37
- private readonly signer: WalletClient;
38
- private readonly walletAddress: string;
39
-
40
- private marketCache: Map<string, { market: PredictionMarket; cachedAt: number }> = new Map();
41
- private readonly CACHE_TTL_MS = 60_000;
42
-
43
- constructor(privateKey: string, config?: Partial<PredictionConfig>) {
44
- this.config = { ...DEFAULT_PREDICTION_CONFIG, ...config } as PredictionConfig;
45
- const account = privateKeyToAccount(privateKey as `0x${string}`);
46
- this.signer = createWalletClient({
47
- account,
48
- chain: polygon,
49
- transport: http(),
50
- });
51
- this.walletAddress = account.address;
52
- }
53
-
54
- // ── INITIALIZATION ─────────────────────────────────────────
55
-
56
- async initialize(): Promise<void> {
57
- const initClient = new ClobClient(
58
- this.config.clobApiUrl,
59
- POLYGON_CHAIN_ID,
60
- this.signer,
61
- );
62
-
63
- this.apiCreds = (await initClient.createOrDeriveApiKey()) as ApiKeyCreds;
64
-
65
- // EOA signature type (0)
66
- this.clobClient = new ClobClient(
67
- this.config.clobApiUrl,
68
- POLYGON_CHAIN_ID,
69
- this.signer,
70
- this.apiCreds,
71
- 0,
72
- );
73
-
74
- console.log(`[prediction] CLOB initialized for ${this.walletAddress}`);
75
- }
76
-
77
- get isInitialized(): boolean {
78
- return this.clobClient !== null && this.apiCreds !== null;
79
- }
80
-
81
- // ── CLOB API — ORDER BOOK & PRICES ─────────────────────────
82
-
83
- async getOrderBook(tokenId: string): Promise<{ bids: OrderBookLevel[]; asks: OrderBookLevel[] }> {
84
- this.ensureInitialized();
85
- const book = await this.clobClient!.getOrderBook(tokenId);
86
- return {
87
- bids: (book.bids || []).map((b: any) => ({ price: parseFloat(b.price), size: parseFloat(b.size) })),
88
- asks: (book.asks || []).map((a: any) => ({ price: parseFloat(a.price), size: parseFloat(a.size) })),
89
- };
90
- }
91
-
92
- async getMidpointPrice(tokenId: string): Promise<number> {
93
- const book = await this.getOrderBook(tokenId);
94
- if (book.bids.length === 0 || book.asks.length === 0) return 0;
95
- return (book.bids[0].price + book.asks[0].price) / 2;
96
- }
97
-
98
- async getLastTradePrice(tokenId: string): Promise<number> {
99
- this.ensureInitialized();
100
- const resp = await this.clobClient!.getLastTradePrice(tokenId);
101
- return parseFloat(resp?.price || '0');
102
- }
103
-
104
- // ── CLOB API — ORDERS ──────────────────────────────────────
105
-
106
- async placeLimitOrder(params: {
107
- tokenId: string;
108
- price: number;
109
- size: number;
110
- side: 'BUY' | 'SELL';
111
- }): Promise<{ orderId: string; success: boolean }> {
112
- this.ensureInitialized();
113
- const order = await this.clobClient!.createAndPostOrder({
114
- tokenID: params.tokenId,
115
- price: params.price,
116
- side: params.side as Side,
117
- size: params.size,
118
- });
119
- return {
120
- orderId: order?.orderID || '',
121
- success: !!order?.orderID,
122
- };
123
- }
124
-
125
- async placeMarketOrder(params: {
126
- tokenId: string;
127
- amount: number;
128
- side: 'BUY' | 'SELL';
129
- }): Promise<{ orderId: string; success: boolean }> {
130
- this.ensureInitialized();
131
- const order = await this.clobClient!.createMarketOrder({
132
- tokenID: params.tokenId,
133
- amount: params.amount,
134
- side: params.side as Side,
135
- });
136
- const result = await this.clobClient!.postOrder(order);
137
- return {
138
- orderId: result?.orderID || '',
139
- success: !!result?.orderID,
140
- };
141
- }
142
-
143
- async cancelOrder(orderId: string): Promise<boolean> {
144
- this.ensureInitialized();
145
- try {
146
- await this.clobClient!.cancelOrder({ orderID: orderId });
147
- return true;
148
- } catch {
149
- return false;
150
- }
151
- }
152
-
153
- async cancelAllOrders(): Promise<boolean> {
154
- this.ensureInitialized();
155
- try {
156
- await this.clobClient!.cancelAll();
157
- return true;
158
- } catch {
159
- return false;
160
- }
161
- }
162
-
163
- async getOpenOrders(): Promise<any[]> {
164
- this.ensureInitialized();
165
- return this.clobClient!.getOpenOrders();
166
- }
167
-
168
- async getTradeHistory(): Promise<PredictionFill[]> {
169
- this.ensureInitialized();
170
- const trades = await this.clobClient!.getTrades();
171
- return (trades || []).map((t: any) => this.parseRawFill(t));
172
- }
173
-
174
- // ── GAMMA API — MARKET DISCOVERY (public) ──────────────────
175
-
176
- async getMarkets(params?: {
177
- limit?: number;
178
- offset?: number;
179
- category?: string;
180
- active?: boolean;
181
- }): Promise<PredictionMarket[]> {
182
- const query = new URLSearchParams();
183
- if (params?.limit) query.set('limit', String(params.limit));
184
- if (params?.offset) query.set('offset', String(params.offset));
185
- if (params?.active !== undefined) query.set('active', String(params.active));
186
- if (params?.category) query.set('tag', params.category);
187
-
188
- const url = `${this.config.gammaApiUrl}/markets?${query.toString()}`;
189
- const resp = await fetch(url);
190
- if (!resp.ok) throw new Error(`Gamma API error: ${resp.status} ${await resp.text()}`);
191
-
192
- const raw = await resp.json();
193
- return (raw || []).map((m: any) => this.parseGammaMarket(m));
194
- }
195
-
196
- async getMarketByConditionId(conditionId: string): Promise<PredictionMarket | null> {
197
- const cached = this.marketCache.get(conditionId);
198
- if (cached && Date.now() - cached.cachedAt < this.CACHE_TTL_MS) {
199
- return cached.market;
200
- }
201
-
202
- const url = `${this.config.gammaApiUrl}/markets?condition_id=${conditionId}`;
203
- const resp = await fetch(url);
204
- if (!resp.ok) return null;
205
-
206
- const raw = await resp.json();
207
- if (!raw || raw.length === 0) return null;
208
-
209
- const market = this.parseGammaMarket(raw[0]);
210
- this.marketCache.set(conditionId, { market, cachedAt: Date.now() });
211
- return market;
212
- }
213
-
214
- async searchMarkets(query: string, limit = 20): Promise<PredictionMarket[]> {
215
- const url = `${this.config.gammaApiUrl}/markets?_q=${encodeURIComponent(query)}&limit=${limit}&active=true`;
216
- const resp = await fetch(url);
217
- if (!resp.ok) return [];
218
-
219
- const raw = await resp.json();
220
- return (raw || []).map((m: any) => this.parseGammaMarket(m));
221
- }
222
-
223
- async getTrendingMarkets(limit = 10): Promise<PredictionMarket[]> {
224
- const url = `${this.config.gammaApiUrl}/markets?active=true&limit=${limit}&order=volume24hr&ascending=false`;
225
- const resp = await fetch(url);
226
- if (!resp.ok) return [];
227
-
228
- const raw = await resp.json();
229
- return (raw || []).map((m: any) => this.parseGammaMarket(m));
230
- }
231
-
232
- // ── WALLET ─────────────────────────────────────────────────
233
-
234
- getWalletAddress(): string {
235
- return this.walletAddress;
236
- }
237
-
238
- // ── PRIVATE ────────────────────────────────────────────────
239
-
240
- private ensureInitialized(): void {
241
- if (!this.clobClient) {
242
- throw new Error('PolymarketClient not initialized. Call initialize() first.');
243
- }
244
- }
245
-
246
- private parseGammaMarket(raw: any): PredictionMarket {
247
- const outcomes = raw.outcomes ? JSON.parse(raw.outcomes) : ['Yes', 'No'];
248
- const outcomePrices = raw.outcomePrices ? JSON.parse(raw.outcomePrices) : [0, 0];
249
- const outcomeTokenIds = raw.clobTokenIds ? JSON.parse(raw.clobTokenIds) : [];
250
-
251
- return {
252
- conditionId: raw.conditionId || raw.condition_id || '',
253
- question: raw.question || '',
254
- description: raw.description || '',
255
- category: raw.groupItemTitle || raw.category || 'Other',
256
- outcomes,
257
- outcomeTokenIds,
258
- outcomePrices: outcomePrices.map((p: any) => parseFloat(p) || 0),
259
- volume24h: parseFloat(raw.volume24hr || raw.volume24h || '0'),
260
- liquidity: parseFloat(raw.liquidity || '0'),
261
- endDate: raw.endDate ? new Date(raw.endDate).getTime() / 1000 : 0,
262
- active: raw.active !== false && raw.closed !== true,
263
- resolved: raw.resolved === true,
264
- winningOutcome: raw.winningOutcome,
265
- resolutionSource: raw.resolutionSource || undefined,
266
- uniqueTraders: raw.uniqueTraders || undefined,
267
- };
268
- }
269
-
270
- private parseRawFill(raw: any): PredictionFill {
271
- const tokenId = raw.asset_id || undefined;
272
- const marketConditionId = raw.market || raw.asset_id || '';
273
-
274
- return {
275
- orderId: raw.orderId || raw.order_id || '',
276
- tradeId: raw.id || raw.tradeId || '',
277
- marketConditionId,
278
- outcomeIndex: raw.outcome_index ?? (raw.side === 'BUY' ? 0 : 1),
279
- side: raw.side === 'BUY' || raw.side === 'buy' ? 'BUY' : 'SELL',
280
- price: String(raw.price || '0'),
281
- size: String(raw.size || '0'),
282
- fee: String(raw.fee || '0'),
283
- timestamp: raw.timestamp || raw.created_at ? new Date(raw.created_at).getTime() : Date.now(),
284
- isMaker: raw.maker_order || raw.is_maker || false,
285
- tokenId,
286
- };
287
- }
288
- }
@@ -1,3 +0,0 @@
1
- export * from './types.js';
2
- export { PolymarketClient } from './client.js';
3
- export { PolymarketOrderManager } from './order-manager.js';
@@ -1,297 +0,0 @@
1
- /**
2
- * Polymarket Order Manager
3
- *
4
- * Executes prediction trade signals, polls for new fills,
5
- * and tracks local position state.
6
- */
7
-
8
- import type {
9
- PredictionConfig,
10
- PredictionTradeSignal,
11
- PredictionOrderResult,
12
- PredictionFill,
13
- PredictionPosition,
14
- PredictionAccountSummary,
15
- } from './types.js';
16
- import { PolymarketClient } from './client.js';
17
-
18
- // ── LOCAL POSITION TRACKING ──────────────────────────────────
19
-
20
- interface LocalPosition {
21
- marketConditionId: string;
22
- outcomeIndex: number;
23
- marketQuestion: string;
24
- tokenId: string;
25
- balance: number;
26
- totalCostBasis: number;
27
- totalProceeds: number;
28
- averageEntryPrice: number;
29
- currentPrice: number;
30
- category?: string;
31
- endDate?: number;
32
- }
33
-
34
- export class PolymarketOrderManager {
35
- private readonly client: PolymarketClient;
36
- private readonly config: PredictionConfig;
37
-
38
- private seenFillIds: Set<string> = new Set();
39
- private recentFills: PredictionFill[] = [];
40
- private positions: Map<string, LocalPosition> = new Map();
41
-
42
- private lastPriceRefresh: number = 0;
43
- private readonly PRICE_REFRESH_MS = 10_000;
44
-
45
- constructor(client: PolymarketClient, config: PredictionConfig) {
46
- this.client = client;
47
- this.config = config;
48
- }
49
-
50
- // ── SIGNAL EXECUTION ───────────────────────────────────────
51
-
52
- async executeSignal(signal: PredictionTradeSignal): Promise<PredictionOrderResult> {
53
- if (signal.action === 'hold') {
54
- return { success: true, status: 'cancelled', error: 'Hold signal — no action' };
55
- }
56
-
57
- // Risk checks
58
- const violation = this.checkRiskLimits(signal);
59
- if (violation) {
60
- return { success: false, status: 'error', error: violation };
61
- }
62
-
63
- try {
64
- const market = await this.client.getMarketByConditionId(signal.marketConditionId);
65
- if (!market) {
66
- return { success: false, status: 'error', error: `Market not found: ${signal.marketConditionId}` };
67
- }
68
-
69
- if (!market.active) {
70
- return { success: false, status: 'error', error: `Market is closed: ${market.question}` };
71
- }
72
-
73
- const tokenId = market.outcomeTokenIds[signal.outcomeIndex];
74
- if (!tokenId) {
75
- return { success: false, status: 'error', error: `Invalid outcome index: ${signal.outcomeIndex}` };
76
- }
77
-
78
- const isBuy = signal.action === 'buy_yes' || signal.action === 'buy_no';
79
- const side = isBuy ? 'BUY' as const : 'SELL' as const;
80
-
81
- let result: { orderId: string; success: boolean };
82
-
83
- if (signal.orderType === 'market') {
84
- result = await this.client.placeMarketOrder({
85
- tokenId,
86
- amount: signal.amount,
87
- side,
88
- });
89
- } else {
90
- result = await this.client.placeLimitOrder({
91
- tokenId,
92
- price: signal.limitPrice,
93
- size: signal.amount,
94
- side,
95
- });
96
- }
97
-
98
- if (result.success) {
99
- console.log(`[prediction] Order placed: ${signal.action} ${signal.amount} on "${market.question}" @ ${signal.limitPrice}`);
100
- return { success: true, orderId: result.orderId, status: 'resting' };
101
- }
102
-
103
- return { success: false, status: 'error', error: 'Order placement failed' };
104
- } catch (error) {
105
- const message = error instanceof Error ? error.message : String(error);
106
- console.error('[prediction] Order execution error:', message);
107
- return { success: false, status: 'error', error: message };
108
- }
109
- }
110
-
111
- // ── FILL POLLING ───────────────────────────────────────────
112
-
113
- async pollNewFills(): Promise<PredictionFill[]> {
114
- try {
115
- const allFills = await this.client.getTradeHistory();
116
- const newFills: PredictionFill[] = [];
117
-
118
- for (const fill of allFills) {
119
- if (!this.seenFillIds.has(fill.tradeId)) {
120
- this.seenFillIds.add(fill.tradeId);
121
- newFills.push(fill);
122
- this.processFill(fill);
123
- this.recentFills.push(fill);
124
- }
125
- }
126
-
127
- // Cap recent fills
128
- if (this.recentFills.length > 100) {
129
- this.recentFills = this.recentFills.slice(-100);
130
- }
131
-
132
- return newFills;
133
- } catch (error) {
134
- const message = error instanceof Error ? error.message : String(error);
135
- console.warn('[prediction] Fill polling error:', message);
136
- return [];
137
- }
138
- }
139
-
140
- getRecentFills(): PredictionFill[] {
141
- return this.recentFills;
142
- }
143
-
144
- // ── POSITION QUERIES ───────────────────────────────────────
145
-
146
- async getPositions(forceRefresh = false): Promise<PredictionPosition[]> {
147
- if (forceRefresh || Date.now() - this.lastPriceRefresh > this.PRICE_REFRESH_MS) {
148
- await this.refreshPrices();
149
- }
150
-
151
- return Array.from(this.positions.values())
152
- .filter((p) => p.balance > 0)
153
- .map((p) => this.toExternalPosition(p));
154
- }
155
-
156
- async getAccountSummary(): Promise<PredictionAccountSummary> {
157
- const positions = await this.getPositions();
158
- const totalExposure = positions.reduce((sum, p) => sum + p.costBasis, 0);
159
- const totalUnrealizedPnl = positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
160
- const openMarkets = new Set(positions.map((p) => p.marketConditionId));
161
-
162
- return {
163
- totalExposure,
164
- totalUnrealizedPnl,
165
- openMarketCount: openMarkets.size,
166
- openPositionCount: positions.length,
167
- };
168
- }
169
-
170
- getTotalExposure(): number {
171
- let total = 0;
172
- for (const pos of this.positions.values()) {
173
- if (pos.balance > 0) total += pos.totalCostBasis;
174
- }
175
- return total;
176
- }
177
-
178
- // ── CANCELLATION ───────────────────────────────────────────
179
-
180
- async cancelOrder(orderId: string): Promise<boolean> {
181
- return this.client.cancelOrder(orderId);
182
- }
183
-
184
- async cancelAllOrders(): Promise<boolean> {
185
- return this.client.cancelAllOrders();
186
- }
187
-
188
- // ── PRIVATE ────────────────────────────────────────────────
189
-
190
- private checkRiskLimits(signal: PredictionTradeSignal): string | null {
191
- if (signal.amount > this.config.maxNotionalUSD) {
192
- return `Trade exceeds max notional: $${signal.amount} > $${this.config.maxNotionalUSD}`;
193
- }
194
-
195
- if (signal.limitPrice <= 0 || signal.limitPrice >= 1) {
196
- return `Invalid price: ${signal.limitPrice} (must be 0.01-0.99)`;
197
- }
198
-
199
- if (signal.confidence < 0.3) {
200
- return `Low confidence: ${signal.confidence} < 0.3`;
201
- }
202
-
203
- const isBuy = signal.action === 'buy_yes' || signal.action === 'buy_no';
204
- if (isBuy) {
205
- const currentExposure = this.getTotalExposure();
206
- if (currentExposure + signal.amount > this.config.maxTotalExposureUSD) {
207
- return `Total exposure exceeded: $${currentExposure + signal.amount} > $${this.config.maxTotalExposureUSD}`;
208
- }
209
- }
210
-
211
- return null;
212
- }
213
-
214
- private processFill(fill: PredictionFill): void {
215
- const key = `${fill.marketConditionId}:${fill.outcomeIndex}`;
216
- let pos = this.positions.get(key);
217
-
218
- const price = parseFloat(fill.price);
219
- const size = parseFloat(fill.size);
220
-
221
- if (!pos) {
222
- pos = {
223
- marketConditionId: fill.marketConditionId,
224
- outcomeIndex: fill.outcomeIndex,
225
- marketQuestion: fill.marketQuestion || '',
226
- tokenId: fill.tokenId || '',
227
- balance: 0,
228
- totalCostBasis: 0,
229
- totalProceeds: 0,
230
- averageEntryPrice: 0,
231
- currentPrice: price,
232
- category: undefined,
233
- endDate: undefined,
234
- };
235
- this.positions.set(key, pos);
236
- } else if (!pos.tokenId && fill.tokenId) {
237
- pos.tokenId = fill.tokenId;
238
- }
239
-
240
- if (fill.side === 'BUY') {
241
- const oldCost = pos.averageEntryPrice * pos.balance;
242
- const newCost = price * size;
243
- pos.balance += size;
244
- pos.totalCostBasis += newCost;
245
- pos.averageEntryPrice = pos.balance > 0 ? (oldCost + newCost) / pos.balance : 0;
246
- } else {
247
- pos.balance -= size;
248
- pos.totalProceeds += price * size;
249
- if (pos.balance < 0) pos.balance = 0;
250
- }
251
- }
252
-
253
- private async refreshPrices(): Promise<void> {
254
- const openPositions = Array.from(this.positions.values()).filter((p) => p.balance > 0);
255
-
256
- await Promise.all(
257
- openPositions.map(async (pos) => {
258
- try {
259
- if (!pos.tokenId) {
260
- const market = await this.client.getMarketByConditionId(pos.marketConditionId);
261
- if (market && market.outcomeTokenIds[pos.outcomeIndex]) {
262
- pos.tokenId = market.outcomeTokenIds[pos.outcomeIndex];
263
- }
264
- }
265
- if (pos.tokenId) {
266
- const price = await this.client.getMidpointPrice(pos.tokenId);
267
- if (price > 0) pos.currentPrice = price;
268
- }
269
- } catch {
270
- // Keep stale price on error
271
- }
272
- }),
273
- );
274
-
275
- this.lastPriceRefresh = Date.now();
276
- }
277
-
278
- private toExternalPosition(pos: LocalPosition): PredictionPosition {
279
- const unrealizedPnl =
280
- pos.balance > 0 ? (pos.currentPrice - pos.averageEntryPrice) * pos.balance : 0;
281
-
282
- return {
283
- marketConditionId: pos.marketConditionId,
284
- marketQuestion: pos.marketQuestion,
285
- outcomeIndex: pos.outcomeIndex,
286
- outcomeLabel: pos.outcomeIndex === 0 ? 'Yes' : 'No',
287
- tokenId: pos.tokenId,
288
- balance: pos.balance,
289
- averageEntryPrice: pos.averageEntryPrice,
290
- currentPrice: pos.currentPrice,
291
- unrealizedPnl,
292
- costBasis: pos.totalCostBasis - pos.totalProceeds,
293
- endDate: pos.endDate,
294
- category: pos.category,
295
- };
296
- }
297
- }