@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,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
- }