From 2924ff60986c6f94fb225027e4ae31728e0050a6 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Sun, 15 Mar 2026 13:09:12 -0700 Subject: [PATCH] =?UTF-8?q?Feat:=20Background=20worker=20=E2=80=94=20marke?= =?UTF-8?q?t=20tracking=20+=20paper=20trading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- worker.js | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 worker.js diff --git a/worker.js b/worker.js new file mode 100644 index 0000000..6e616d2 --- /dev/null +++ b/worker.js @@ -0,0 +1,121 @@ +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'; + +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(); + + // On every market update, run strategies + tracker.on('update', async (state) => { + if (!state) return; + + // Write state to file for frontend + writeState(state, paper, strategies); + + // 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(state, 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) { + for (const trade of settledPositions) { + strategy.onSettlement(result, trade); + } + } + } + + await notify( + `Market ${ticker} settled: ${result?.toUpperCase() || 'unknown'}`, + 'Market Settled', + 'default', + 'chart_with_upwards_trend' + ); + }); + + // Start tracking + await tracker.start(); + await notify('๐Ÿค– Kalbot Worker started!', 'Kalbot Online', 'low', 'robot,green_circle'); + + console.log('[Worker] Running. Press Ctrl+C to stop.'); + + // Graceful shutdown + process.on('SIGINT', async () => { + console.log('\n[Worker] Shutting down...'); + tracker.stop(); + await notify('๐Ÿ”ด Kalbot Worker stopped', 'Kalbot Offline', 'high', 'robot,red_circle'); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + tracker.stop(); + process.exit(0); + }); +} + +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); +});