import { MarketTracker } from './lib/market/tracker.js'; import { PaperEngine } from './lib/paper/engine.js'; import { MartingaleStrategy } from './lib/strategies/martingale.js'; import { ThresholdStrategy } from './lib/strategies/threshold.js'; import { db } from './lib/db.js'; import { notify } from './lib/notify.js'; import fs from 'fs'; // Shared state file for the Next.js frontend to read const STATE_FILE = '/tmp/kalbot-state.json'; const HEARTBEAT_MS = 2000; async function main() { console.log('=== Kalbot Worker Starting ==='); // Connect to SurrealDB await db.connect(); // Initialize paper engine const paper = new PaperEngine(1000); await paper.init(); // Initialize strategies const strategies = [ new MartingaleStrategy({ threshold: 70, baseBet: 1, maxDoublings: 5 }), new ThresholdStrategy({ triggerPct: 65, betSize: 1 }) ]; console.log(`[Worker] Loaded ${strategies.length} strategies: ${strategies.map((s) => s.name).join(', ')}`); // Initialize market tracker const tracker = new MarketTracker(); let latestMarketState = null; let heartbeatTimer = null; // Write initial heartbeat immediately so dashboard never shows "Updated: never" writeState(latestMarketState, paper, strategies); // On every market update, run strategies tracker.on('update', async (state) => { latestMarketState = state || null; // Write state to file for frontend writeState(latestMarketState, paper, strategies); if (!state) return; // Run each strategy for (const strategy of strategies) { if (!strategy.enabled) continue; const signal = strategy.evaluate(state); if (signal) { console.log(`[Worker] Signal from ${strategy.name}: ${signal.side} @ ${signal.price}ยข โ€” ${signal.reason}`); if (strategy.mode === 'paper') { await paper.executeTrade(signal, state); } // TODO: Live mode โ€” use placeOrder() from rest.js } } // Update state file after potential trades writeState(latestMarketState, paper, strategies); }); // On market settlement, settle paper positions and notify strategies tracker.on('settled', async ({ ticker, result }) => { console.log(`[Worker] Market ${ticker} settled: ${result}`); const settledPositions = await paper.settle(ticker, result); // Notify strategies about settlement for (const strategy of strategies) { if (!settledPositions) continue; for (const trade of settledPositions) { strategy.onSettlement(result, trade); } } await notify( `Market ${ticker} settled: ${result?.toUpperCase() || 'unknown'}`, 'Market Settled', 'default', 'chart_with_upwards_trend' ); writeState(latestMarketState, paper, strategies); }); // Start tracking await tracker.start(); await notify('๐Ÿค– Kalbot Worker started!', 'Kalbot Online', 'low', 'robot,green_circle'); // Keep state fresh even if market API is temporarily quiet heartbeatTimer = setInterval(() => { writeState(latestMarketState, paper, strategies); }, HEARTBEAT_MS); console.log('[Worker] Running. Press Ctrl+C to stop.'); const shutdown = async (signal) => { console.log(`\n[Worker] ${signal} received. Shutting down...`); clearInterval(heartbeatTimer); tracker.stop(); await notify('๐Ÿ”ด Kalbot Worker stopped', 'Kalbot Offline', 'high', 'robot,red_circle'); process.exit(0); }; // Graceful shutdown process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); } function writeState(marketState, paper, strategies) { const data = { market: marketState, paper: paper.getStats(), strategies: strategies.map((s) => s.toJSON()), workerUptime: process.uptime(), lastUpdate: Date.now() }; try { fs.writeFileSync(STATE_FILE, JSON.stringify(data)); } catch (e) { // Non-critical } } main().catch((err) => { console.error('[Worker] Fatal error:', err); process.exit(1); });