Initial commit: Mantle AI Trading Bot

Features:
- AI-powered signal generation with multi-factor analysis
- Fundamental news aggregation from multiple sources
- Technical analysis with 6+ indicators
- VectorDB integration for semantic search
- Backtesting engine with performance metrics
- Demo/paper trading mode
- Real-time WebSocket updates
- Comprehensive dashboard UI

Built for Mantle Turing Test Hackathon
- AI Trading track
- AI Alpha & Data track
This commit is contained in:
Mantle AI Trader
2026-06-06 06:02:07 +00:00
Unverified
parent 6664758a6d
commit b1da4ee01d
100 changed files with 16113 additions and 0 deletions

0
mini-services/.gitkeep Normal file
View File

View File

@@ -0,0 +1,57 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "trading-service",
"dependencies": {
"socket.io": "^4.8.3",
},
},
},
"packages": {
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
"@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"engine.io": ["engine.io@6.6.8", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.20.1" } }, "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"socket.io": ["socket.io@4.8.3", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A=="],
"socket.io-adapter": ["socket.io-adapter@2.5.7", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.20.1" } }, "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg=="],
"socket.io-parser": ["socket.io-parser@4.2.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg=="],
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="],
}
}

View File

@@ -0,0 +1,319 @@
/**
* Trading WebSocket Service for Mantle AI Trading Bot
* Real-time updates for signals, prices, and portfolio
*/
import { Server as HttpServer } from 'http';
import { Server as SocketIOServer, Socket } from 'socket.io';
import { signalEngine } from '../../src/lib/trading/signals/signal-engine';
import { demoTrader } from '../../src/lib/trading/demo/demo-trader';
import { newsAggregator } from '../../src/lib/trading/news/news-aggregator';
import { TradeAction, TimeFrame } from '../../src/lib/trading/core/types';
interface SignalSubscription {
symbol: string;
timeframe: TimeFrame;
interval: NodeJS.Timeout;
}
export class TradingWebSocketService {
private io: SocketIOServer;
private subscriptions: Map<string, SignalSubscription> = new Map();
private priceUpdateInterval: NodeJS.Timeout | null = null;
constructor(httpServer: HttpServer) {
this.io = new SocketIOServer(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
this.setupEventHandlers();
this.startBackgroundServices();
}
private setupEventHandlers(): void {
this.io.on('connection', (socket: Socket) => {
console.log(`Client connected: ${socket.id}`);
// Send initial data
this.sendInitialData(socket);
// Handle signal generation request
socket.on('generate_signal', async (data: { symbol: string; timeframe: TimeFrame }) => {
try {
const signal = await this.generateSignal(data.symbol, data.timeframe);
socket.emit('signal_generated', signal);
} catch (error) {
socket.emit('error', { message: 'Failed to generate signal', error: String(error) });
}
});
// Handle signal subscription
socket.on('subscribe_signals', (data: { symbol: string; timeframe: TimeFrame }) => {
this.subscribeToSignals(socket, data.symbol, data.timeframe);
});
// Handle unsubscribe
socket.on('unsubscribe_signals', (data: { symbol: string }) => {
this.unsubscribeFromSignals(socket.id, data.symbol);
});
// Handle demo trading
socket.on('place_demo_order', (data: {
symbol: string;
side: TradeAction;
quantity: number;
type: 'MARKET' | 'LIMIT';
price?: number;
}) => {
try {
const order = demoTrader.placeOrder({
symbol: data.symbol,
side: data.side,
type: data.type,
quantity: data.quantity,
price: data.price
});
socket.emit('demo_order_placed', order);
this.broadcastPortfolio();
} catch (error) {
socket.emit('error', { message: 'Failed to place order', error: String(error) });
}
});
// Handle close position
socket.on('close_demo_position', (data: { symbol: string }) => {
const order = demoTrader.closePosition(data.symbol);
if (order) {
socket.emit('demo_order_placed', order);
this.broadcastPortfolio();
}
});
// Handle get portfolio
socket.on('get_portfolio', () => {
socket.emit('portfolio_update', demoTrader.getPortfolio());
});
// Handle get positions
socket.on('get_positions', () => {
socket.emit('positions_update', demoTrader.getPositions());
});
// Handle get news
socket.on('get_news', async (data: { symbol?: string; limit?: number }) => {
try {
const news = data.symbol
? await newsAggregator.getNewsForSymbol(data.symbol, data.limit || 20)
: await newsAggregator.fetchAllNews({ limit: data.limit || 50 });
socket.emit('news_update', news);
} catch (error) {
socket.emit('error', { message: 'Failed to fetch news', error: String(error) });
}
});
// Handle get sentiment
socket.on('get_sentiment', async (data: { symbol: string }) => {
try {
const sentiment = await newsAggregator.getSymbolSentiment(data.symbol);
socket.emit('sentiment_update', { symbol: data.symbol, ...sentiment });
} catch (error) {
socket.emit('error', { message: 'Failed to fetch sentiment', error: String(error) });
}
});
// Handle reset demo
socket.on('reset_demo', (data: { initialCapital?: number }) => {
demoTrader.reset(data.initialCapital || 10000);
this.broadcastPortfolio();
socket.emit('demo_reset', { success: true });
});
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
// Clean up subscriptions
for (const [key, sub] of this.subscriptions) {
if (key.startsWith(socket.id)) {
clearInterval(sub.interval);
this.subscriptions.delete(key);
}
}
});
});
}
private async sendInitialData(socket: Socket): Promise<void> {
// Send current portfolio
socket.emit('portfolio_update', demoTrader.getPortfolio());
// Send positions
socket.emit('positions_update', demoTrader.getPositions());
// Send recent news
try {
const news = await newsAggregator.fetchAllNews({ limit: 20 });
socket.emit('news_update', news);
} catch (error) {
console.error('Failed to fetch initial news:', error);
}
}
private async generateSignal(symbol: string, timeframe: TimeFrame) {
// Generate simulated market data for demo
const marketData = this.generateDemoMarketData(symbol, 200);
// Fetch news for the symbol
const news = await newsAggregator.getNewsForSymbol(symbol, 10);
const signalOutput = await signalEngine.generateSignal({
symbol,
timeframe,
marketData,
newsArticles: news
});
return signalOutput;
}
private subscribeToSignals(socket: Socket, symbol: string, timeframe: TimeFrame): void {
const key = `${socket.id}-${symbol}`;
// Remove existing subscription
const existing = this.subscriptions.get(key);
if (existing) {
clearInterval(existing.interval);
}
// Create new subscription (check every 5 minutes)
const interval = setInterval(async () => {
try {
const signal = await this.generateSignal(symbol, timeframe);
socket.emit('signal_update', { symbol, signal });
} catch (error) {
console.error(`Error generating signal for ${symbol}:`, error);
}
}, 5 * 60 * 1000);
this.subscriptions.set(key, {
symbol,
timeframe,
interval
});
socket.emit('subscribed', { symbol, timeframe });
}
private unsubscribeFromSignals(socketId: string, symbol: string): void {
const key = `${socketId}-${symbol}`;
const sub = this.subscriptions.get(key);
if (sub) {
clearInterval(sub.interval);
this.subscriptions.delete(key);
}
}
private startBackgroundServices(): void {
// Simulate price updates every 5 seconds
this.priceUpdateInterval = setInterval(() => {
this.simulatePriceUpdates();
}, 5000);
// Subscribe to demo trader events
demoTrader.subscribe((event, data) => {
this.io.emit(event, data);
});
}
private simulatePriceUpdates(): void {
const symbols = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT'];
const updates: Array<{ symbol: string; price: number; change: number }> = [];
symbols.forEach(symbol => {
// Simulate price movement
const basePrice = this.getBasePrice(symbol);
const change = (Math.random() - 0.5) * basePrice * 0.002; // 0.2% max change
const newPrice = basePrice + change;
demoTrader.updatePrice(symbol, newPrice);
updates.push({ symbol, price: newPrice, change: change / basePrice * 100 });
});
this.io.emit('price_updates', updates);
this.broadcastPortfolio();
}
private getBasePrice(symbol: string): number {
const prices: Record<string, number> = {
BTCUSDT: 45000,
ETHUSDT: 2500,
SOLUSDT: 100,
BNBUSDT: 300,
XRPUSDT: 0.5
};
return prices[symbol] || 100;
}
private broadcastPortfolio(): void {
this.io.emit('portfolio_update', demoTrader.getPortfolio());
this.io.emit('positions_update', demoTrader.getPositions());
}
private generateDemoMarketData(symbol: string, count: number) {
const data = [];
let price = this.getBasePrice(symbol);
const now = Date.now();
const hourMs = 60 * 60 * 1000;
for (let i = count; i >= 0; i--) {
const change = (Math.random() - 0.5) * price * 0.02;
const open = price;
const close = price + change;
const high = Math.max(open, close) + Math.random() * Math.abs(change) * 0.5;
const low = Math.min(open, close) - Math.random() * Math.abs(change) * 0.5;
const volume = 1000 + Math.random() * 5000;
data.push({
symbol,
timeframe: TimeFrame.ONE_HOUR,
timestamp: new Date(now - i * hourMs),
open,
high,
low,
close,
volume
});
price = close;
}
return data;
}
public getIO(): SocketIOServer {
return this.io;
}
public close(): void {
if (this.priceUpdateInterval) {
clearInterval(this.priceUpdateInterval);
}
for (const sub of this.subscriptions.values()) {
clearInterval(sub.interval);
}
this.io.close();
}
}
// Create HTTP server and WebSocket service
const httpServer = new HttpServer();
const tradingService = new TradingWebSocketService(httpServer);
const PORT = 3003;
httpServer.listen(PORT, () => {
console.log(`Trading WebSocket Service running on port ${PORT}`);
});
export default tradingService;

View File

@@ -0,0 +1,11 @@
{
"name": "trading-service",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "bun --hot index.ts"
},
"dependencies": {
"socket.io": "^4.8.3"
}
}