diff --git a/src/bot.ts b/src/bot.ts index 3ee9d29..b8adcc1 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -102,6 +102,7 @@ class PolymarketCopyTradingBot { market: trade.marketName || trade.market, shares: trade.size, price: trade.price, + txHash: trade.txHash, }); // Get current balance with timeout diff --git a/src/tui-dashboard.ts b/src/tui-dashboard.ts index f3900b2..c5acda0 100644 --- a/src/tui-dashboard.ts +++ b/src/tui-dashboard.ts @@ -18,6 +18,7 @@ interface DashboardData { shares: number; price: number; status: 'PENDING' | 'SUCCESS' | 'FAILED' | 'SIMULATED'; + txHash?: string; }; recentTrades: Array<{ timestamp: string; @@ -26,6 +27,7 @@ interface DashboardData { shares: number; price: number; status: 'SUCCESS' | 'FAILED' | 'SIMULATED'; + txHash?: string; }>; heartbeat: string; } @@ -39,6 +41,15 @@ export class TUIDashboard { private tradesHistoryBox: blessed.Widgets.BoxElement; private statusBox: blessed.Widgets.BoxElement; private maxHistory: number; + private tradeQueue: Array<{ + timestamp: string; + type: 'BUY' | 'SELL'; + outcome: string; + market: string; + shares: number; + price: number; + txHash?: string; + }> = []; private data: DashboardData = { status: 'RUNNING', botAddress: '', @@ -212,13 +223,40 @@ export class TUIDashboard { market: string; shares: number; price: number; + txHash?: string; }) { + // Add to queue to handle multiple trades per second + this.tradeQueue.push(trade); + // Always update currentTrade for display in the current trade panel this.data.currentTrade = { ...trade, status: 'PENDING', }; } + processTradeQueue() { + // Move all queued trades to recent trades history + while (this.tradeQueue.length > 0) { + const trade = this.tradeQueue.shift(); + if (trade) { + this.data.recentTrades.unshift({ + timestamp: trade.timestamp, + type: trade.type, + outcome: trade.outcome, + shares: trade.shares, + price: trade.price, + status: 'SIMULATED', + txHash: trade.txHash, + }); + } + } + + // Keep last N trades (screen-aware) so the history panel can fill the page + if (this.data.recentTrades.length > (this.maxHistory || 100)) { + this.data.recentTrades.splice(this.maxHistory || 100); + } + } + updateTradeStatus(status: 'SUCCESS' | 'FAILED') { if (this.data.currentTrade) { // Allow SIMULATED flows by accepting a string and mapping accordingly @@ -243,6 +281,7 @@ export class TUIDashboard { price: this.data.currentTrade.price, // If status is not SUCCESS/FAILED, keep it as-is (e.g., SIMULATED) status: (this.data.currentTrade as any).status as 'SUCCESS' | 'FAILED' | 'SIMULATED', + txHash: this.data.currentTrade.txHash, }); // Keep last N trades (screen-aware) so the history panel can fill the page @@ -314,16 +353,23 @@ export class TUIDashboard { for (let i = 0; i < 2; i++) { const ws = currentWindow - i * 900; const summary = storage.getWindowSummary(ws); - const up = summary['Up'] ? `${summary['Up'].count} @ ${summary['Up'].avgPrice?.toFixed(4)}` : '0'; - const down = summary['Down'] ? `${summary['Down'].count} @ ${summary['Down'].avgPrice?.toFixed(4)}` : '0'; + // Check both capitalized and original case for outcomes + const upData = summary['Up'] || summary['up'] || summary['UP']; + const downData = summary['Down'] || summary['down'] || summary['DOWN']; + const up = upData ? `{bold}${upData.count.toFixed(2)}{/} @ ${upData.avgPrice?.toFixed(4)}` : '{dim}0{/}'; + const down = downData ? `{bold}${downData.count.toFixed(2)}{/} @ ${downData.avgPrice?.toFixed(4)}` : '{dim}0{/}'; const label = new Date(ws * 1000).toLocaleTimeString(); - lines.push(`{bold}Window:{/} ${label}`); - lines.push(`{green-fg}Up:{/} ${up} {red-fg}Down:{/} ${down}`); + lines.push(`{cyan-fg}{bold}Window:{/}{/} ${label}`); + lines.push(`{green-fg}↑ Up:{/} ${up} {red-fg}↓ Down:{/} ${down}`); if (i < 1) lines.push(''); } - this.windowSummaryBox.setContent(lines.join('\n')); + if (lines.length === 0) { + this.windowSummaryBox.setContent('{grey-fg}Waiting for trades...{/}'); + } else { + this.windowSummaryBox.setContent(lines.join('\n')); + } } catch (e) { - this.windowSummaryBox.setContent('{grey-fg}No window data{/}'); + this.windowSummaryBox.setContent(`{grey-fg}Error loading window data{/}`); } if (this.data.recentTrades.length === 0) { @@ -335,11 +381,12 @@ export class TUIDashboard { const statusColor = trade.status === 'SUCCESS' ? 'green-fg' : trade.status === 'SIMULATED' ? 'cyan-fg' : 'red-fg'; const typeSymbol = trade.type === 'BUY' ? '▲' : '▼'; const typeColor = trade.type === 'BUY' ? 'green-fg' : 'red-fg'; + const txId = trade.txHash ? trade.txHash.substring(0, 10) + '...' : 'N/A'; return ( `${statusSymbol} {${statusColor}}${trade.status}{/} | ` + `{${typeColor}}${typeSymbol} ${trade.type}{/} {bold}${trade.shares.toFixed(2)}{/} ${trade.outcome} @ ${trade.price.toFixed(4)} | ` + - `{grey-fg}${trade.timestamp}{/}` + `{grey-fg}${trade.timestamp} | Tx: ${txId}{/}` ); }) .join('\n'); @@ -365,6 +412,7 @@ export class TUIDashboard { tick() { this.updateHeartbeat(); + this.processTradeQueue(); this.render(); }