Feat: Add martingale-alpha, per-strategy state output

This commit is contained in:
2026-03-15 16:50:45 -07:00
parent c377c56975
commit 2cd79d45d1

View File

@@ -1,51 +1,48 @@
import { MarketTracker } from './lib/market/tracker.js'; import { MarketTracker } from './lib/market/tracker.js';
import { PaperEngine } from './lib/paper/engine.js'; import { PaperEngine } from './lib/paper/engine.js';
import { MartingaleStrategy } from './lib/strategies/martingale.js'; import { MartingaleStrategy } from './lib/strategies/martingale.js';
import { MartingaleAlphaStrategy } from './lib/strategies/martingale-alpha.js';
import { ThresholdStrategy } from './lib/strategies/threshold.js'; import { ThresholdStrategy } from './lib/strategies/threshold.js';
import { db } from './lib/db.js'; import { db } from './lib/db.js';
import { notify } from './lib/notify.js'; import { notify } from './lib/notify.js';
import fs from 'fs'; import fs from 'fs';
// Shared state file for the Next.js frontend to read
const STATE_FILE = '/tmp/kalbot-state.json'; const STATE_FILE = '/tmp/kalbot-state.json';
const HEARTBEAT_MS = 2000; const HEARTBEAT_MS = 2000;
async function main() { async function main() {
console.log('=== Kalbot Worker Starting ==='); console.log('=== Kalbot Worker Starting ===');
// Connect to SurrealDB
await db.connect(); await db.connect();
// Initialize paper engine
const paper = new PaperEngine(1000); const paper = new PaperEngine(1000);
await paper.init(); await paper.init();
// Initialize strategies
const strategies = [ const strategies = [
new MartingaleStrategy({ threshold: 70, baseBet: 1, maxDoublings: 5 }), new MartingaleStrategy({ threshold: 70, baseBet: 1, maxDoublings: 5 }),
new MartingaleAlphaStrategy({ minPct: 40, maxPct: 60, baseBet: 1, maxRounds: 3 }),
new ThresholdStrategy({ triggerPct: 65, betSize: 1 }) new ThresholdStrategy({ triggerPct: 65, betSize: 1 })
]; ];
// Ensure each strategy has a paper account initialized
for (const s of strategies) {
paper._getAccount(s.name);
}
console.log(`[Worker] Loaded ${strategies.length} strategies: ${strategies.map((s) => s.name).join(', ')}`); console.log(`[Worker] Loaded ${strategies.length} strategies: ${strategies.map((s) => s.name).join(', ')}`);
// Initialize market tracker
const tracker = new MarketTracker(); const tracker = new MarketTracker();
let latestMarketState = null; let latestMarketState = null;
let heartbeatTimer = null; let heartbeatTimer = null;
// Write initial heartbeat immediately so dashboard never shows "Updated: never"
writeState(latestMarketState, paper, strategies); writeState(latestMarketState, paper, strategies);
// On every market update, run strategies
tracker.on('update', async (state) => { tracker.on('update', async (state) => {
latestMarketState = state || null; latestMarketState = state || null;
// Write state to file for frontend
writeState(latestMarketState, paper, strategies); writeState(latestMarketState, paper, strategies);
if (!state) return; if (!state) return;
// Run each strategy
for (const strategy of strategies) { for (const strategy of strategies) {
if (!strategy.enabled) continue; if (!strategy.enabled) continue;
@@ -56,21 +53,17 @@ async function main() {
if (strategy.mode === 'paper') { if (strategy.mode === 'paper') {
await paper.executeTrade(signal, state); await paper.executeTrade(signal, state);
} }
// TODO: Live mode — use placeOrder() from rest.js
} }
} }
// Update state file after potential trades
writeState(latestMarketState, paper, strategies); writeState(latestMarketState, paper, strategies);
}); });
// On market settlement, settle paper positions and notify strategies
tracker.on('settled', async ({ ticker, result }) => { tracker.on('settled', async ({ ticker, result }) => {
console.log(`[Worker] Market ${ticker} settled: ${result}`); console.log(`[Worker] Market ${ticker} settled: ${result}`);
const settledPositions = await paper.settle(ticker, result); const settledPositions = await paper.settle(ticker, result);
// Notify strategies about settlement
for (const strategy of strategies) { for (const strategy of strategies) {
if (!settledPositions) continue; if (!settledPositions) continue;
for (const trade of settledPositions) { for (const trade of settledPositions) {
@@ -88,11 +81,9 @@ async function main() {
writeState(latestMarketState, paper, strategies); writeState(latestMarketState, paper, strategies);
}); });
// Start tracking
await tracker.start(); await tracker.start();
await notify('🤖 Kalbot Worker started!', 'Kalbot Online', 'low', 'robot,green_circle'); await notify('🤖 Kalbot Worker started!', 'Kalbot Online', 'low', 'robot,green_circle');
// Keep state fresh even if market API is temporarily quiet
heartbeatTimer = setInterval(() => { heartbeatTimer = setInterval(() => {
writeState(latestMarketState, paper, strategies); writeState(latestMarketState, paper, strategies);
}, HEARTBEAT_MS); }, HEARTBEAT_MS);
@@ -107,7 +98,6 @@ async function main() {
process.exit(0); process.exit(0);
}; };
// Graceful shutdown
process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGTERM', () => shutdown('SIGTERM'));
} }
@@ -116,6 +106,7 @@ function writeState(marketState, paper, strategies) {
const data = { const data = {
market: marketState, market: marketState,
paper: paper.getStats(), paper: paper.getStats(),
paperByStrategy: paper.getPerStrategyStats(),
strategies: strategies.map((s) => s.toJSON()), strategies: strategies.map((s) => s.toJSON()),
workerUptime: process.uptime(), workerUptime: process.uptime(),
lastUpdate: Date.now() lastUpdate: Date.now()