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

View File

@@ -0,0 +1,184 @@
/**
* Unit Tests for Demo Trader
*/
import { describe, test, expect, beforeEach } from 'bun:test';
import { DemoTrader } from '../../src/lib/trading/demo/demo-trader';
import { TradeAction, OrderType } from '../../src/lib/trading/core/types';
describe('DemoTrader', () => {
let demoTrader: DemoTrader;
beforeEach(() => {
demoTrader = new DemoTrader(10000);
});
describe('initialization', () => {
test('should initialize with correct portfolio', () => {
const portfolio = demoTrader.getPortfolio();
expect(portfolio.totalValue).toBe(10000);
expect(portfolio.cashBalance).toBe(10000);
expect(portfolio.realizedPnL).toBe(0);
expect(portfolio.unrealizedPnL).toBe(0);
});
test('should have no positions initially', () => {
const positions = demoTrader.getPositions();
expect(positions.length).toBe(0);
});
});
describe('placeOrder', () => {
test('should place a market buy order', () => {
// Set price first
demoTrader.updatePrice('BTCUSDT', 45000);
const order = demoTrader.placeOrder({
symbol: 'BTCUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 0.1
});
expect(order).toBeDefined();
expect(order.status).toBe('FILLED');
expect(order.symbol).toBe('BTCUSDT');
expect(order.side).toBe(TradeAction.BUY);
});
test('should update cash balance after buy order', () => {
demoTrader.updatePrice('ETHUSDT', 2500);
demoTrader.placeOrder({
symbol: 'ETHUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 2
});
const portfolio = demoTrader.getPortfolio();
expect(portfolio.cashBalance).toBe(10000 - (2500 * 2));
});
test('should create a position after buy order', () => {
demoTrader.updatePrice('SOLUSDT', 100);
demoTrader.placeOrder({
symbol: 'SOLUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 10
});
const positions = demoTrader.getPositions();
expect(positions.length).toBe(1);
expect(positions[0].symbol).toBe('SOLUSDT');
expect(positions[0].side).toBe('LONG');
expect(positions[0].quantity).toBe(10);
});
test('should throw error for insufficient capital', () => {
demoTrader.updatePrice('BTCUSDT', 45000);
expect(() => {
demoTrader.placeOrder({
symbol: 'BTCUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 1 // Would cost 45000, but we only have 10000
});
}).toThrow('Insufficient capital');
});
});
describe('closePosition', () => {
test('should close an existing position', () => {
demoTrader.updatePrice('BTCUSDT', 45000);
demoTrader.placeOrder({
symbol: 'BTCUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 0.1
});
const closeOrder = demoTrader.closePosition('BTCUSDT');
expect(closeOrder).toBeDefined();
expect(closeOrder?.side).toBe(TradeAction.SELL);
const positions = demoTrader.getPositions();
expect(positions.length).toBe(0);
});
test('should return null when closing non-existent position', () => {
const result = demoTrader.closePosition('NONEXISTENT');
expect(result).toBeNull();
});
});
describe('updatePrice', () => {
test('should update position PnL when price changes', () => {
demoTrader.updatePrice('ETHUSDT', 2500);
demoTrader.placeOrder({
symbol: 'ETHUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 2
});
// Price goes up
demoTrader.updatePrice('ETHUSDT', 2600);
const positions = demoTrader.getPositions();
expect(positions[0].unrealizedPnL).toBeGreaterThan(0);
expect(positions[0].currentPrice).toBe(2600);
});
});
describe('reset', () => {
test('should reset portfolio to initial state', () => {
demoTrader.updatePrice('BTCUSDT', 45000);
demoTrader.placeOrder({
symbol: 'BTCUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 0.1
});
demoTrader.reset(15000);
const portfolio = demoTrader.getPortfolio();
expect(portfolio.totalValue).toBe(15000);
expect(portfolio.cashBalance).toBe(15000);
const positions = demoTrader.getPositions();
expect(positions.length).toBe(0);
});
});
describe('getStatistics', () => {
test('should return correct trade statistics', () => {
demoTrader.updatePrice('BTCUSDT', 45000);
// Open and close a position
demoTrader.placeOrder({
symbol: 'BTCUSDT',
side: TradeAction.BUY,
type: OrderType.MARKET,
quantity: 0.1
});
demoTrader.updatePrice('BTCUSDT', 46000); // Price up
demoTrader.closePosition('BTCUSDT');
const stats = demoTrader.getStatistics();
expect(stats.totalTrades).toBe(2); // Open and close
expect(stats.winningTrades + stats.losingTrades).toBe(stats.totalTrades);
});
});
});

View File

@@ -0,0 +1,114 @@
/**
* Unit Tests for Signal Engine
*/
import { describe, test, expect, beforeEach } from 'bun:test';
import { SignalEngine } from '../../src/lib/trading/signals/signal-engine';
import { TimeFrame, TradeAction, MarketDataPoint } from '../../src/lib/trading/core/types';
describe('SignalEngine', () => {
let signalEngine: SignalEngine;
beforeEach(() => {
signalEngine = new SignalEngine();
});
describe('generateSignal', () => {
test('should generate a signal with required fields', async () => {
const marketData = generateTestMarketData('BTCUSDT', 100);
const result = await signalEngine.generateSignal({
symbol: 'BTCUSDT',
timeframe: TimeFrame.ONE_HOUR,
marketData,
newsArticles: []
});
expect(result).toBeDefined();
expect(result.signal).toBeDefined();
expect(result.signal.symbol).toBe('BTCUSDT');
expect(result.signal.action).toBeDefined();
expect([TradeAction.BUY, TradeAction.SELL, TradeAction.HOLD]).toContain(result.signal.action);
expect(result.signal.confidence).toBeGreaterThanOrEqual(0);
expect(result.signal.confidence).toBeLessThanOrEqual(1);
});
test('should include technical analysis', async () => {
const marketData = generateTestMarketData('ETHUSDT', 200);
const result = await signalEngine.generateSignal({
symbol: 'ETHUSDT',
timeframe: TimeFrame.ONE_HOUR,
marketData,
newsArticles: []
});
expect(result.analysis.technicalAnalysis).toBeDefined();
expect(result.analysis.technicalAnalysis.trend).toBeDefined();
expect(['BULLISH', 'BEARISH', 'SIDEWAYS']).toContain(result.analysis.technicalAnalysis.trend);
expect(result.analysis.technicalAnalysis.indicators).toBeDefined();
});
test('should include risk assessment', async () => {
const marketData = generateTestMarketData('SOLUSDT', 150);
const result = await signalEngine.generateSignal({
symbol: 'SOLUSDT',
timeframe: TimeFrame.ONE_HOUR,
marketData,
newsArticles: []
});
expect(result.riskAssessment).toBeDefined();
expect(result.riskAssessment.riskScore).toBeGreaterThanOrEqual(0);
expect(result.riskAssessment.riskScore).toBeLessThanOrEqual(1);
expect(result.riskAssessment.riskLevel).toBeDefined();
});
test('should handle insufficient data gracefully', async () => {
const marketData = generateTestMarketData('BTCUSDT', 10);
const result = await signalEngine.generateSignal({
symbol: 'BTCUSDT',
timeframe: TimeFrame.ONE_HOUR,
marketData,
newsArticles: []
});
expect(result).toBeDefined();
expect(result.analysis.technicalAnalysis.score).toBe(0.5); // Default for insufficient data
});
});
});
// Helper function to generate test market data
function generateTestMarketData(symbol: string, count: number): MarketDataPoint[] {
const data: MarketDataPoint[] = [];
let price = 40000 + Math.random() * 5000;
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;
}