Files
wifi-densepose/examples/neural-trader/tests/neural-trader.test.js
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

542 lines
17 KiB
JavaScript

/**
* Neural-Trader Test Suite
*
* Comprehensive tests for all production modules
*/
import { describe, it, expect, beforeEach } from '@jest/globals';
// Mock performance for Node.js
if (typeof performance === 'undefined') {
global.performance = { now: () => Date.now() };
}
// ============================================================================
// FRACTIONAL KELLY TESTS
// ============================================================================
describe('Fractional Kelly Engine', () => {
let KellyCriterion, TradingKelly, SportsBettingKelly;
beforeEach(async () => {
const module = await import('../production/fractional-kelly.js');
KellyCriterion = module.KellyCriterion;
TradingKelly = module.TradingKelly;
SportsBettingKelly = module.SportsBettingKelly;
});
describe('KellyCriterion', () => {
it('should calculate full Kelly correctly', () => {
const kelly = new KellyCriterion();
const result = kelly.calculateFullKelly(0.55, 2.0);
expect(result).toBeCloseTo(0.10, 2); // 10% for 55% win rate at even odds
});
it('should return 0 for negative edge', () => {
const kelly = new KellyCriterion();
const result = kelly.calculateFullKelly(0.40, 2.0);
expect(result).toBeLessThanOrEqual(0);
});
it('should apply fractional Kelly correctly', () => {
const kelly = new KellyCriterion();
const result = kelly.calculateFractionalKelly(0.55, 2.0, 'conservative');
expect(result.stake).toBeLessThan(kelly.calculateFullKelly(0.55, 2.0) * 10000);
});
it('should handle edge cases', () => {
const kelly = new KellyCriterion();
expect(kelly.calculateFullKelly(0, 2.0)).toBeLessThanOrEqual(0);
expect(kelly.calculateFullKelly(1, 2.0)).toBeGreaterThan(0);
});
});
describe('TradingKelly', () => {
it('should calculate position size', () => {
const kelly = new TradingKelly();
const result = kelly.calculatePositionSize(100000, 0.55, 0.02, 0.015);
expect(result.positionSize).toBeGreaterThan(0);
expect(result.positionSize).toBeLessThan(100000);
});
it('should respect max position limits', () => {
const kelly = new TradingKelly();
const result = kelly.calculatePositionSize(100000, 0.99, 0.10, 0.01);
expect(result.positionSize).toBeLessThanOrEqual(100000 * 0.20);
});
});
});
// ============================================================================
// LSTM-TRANSFORMER TESTS
// ============================================================================
describe('Hybrid LSTM-Transformer', () => {
let HybridLSTMTransformer, FeatureExtractor, LSTMCell;
beforeEach(async () => {
const module = await import('../production/hybrid-lstm-transformer.js');
HybridLSTMTransformer = module.HybridLSTMTransformer;
FeatureExtractor = module.FeatureExtractor;
LSTMCell = module.LSTMCell;
});
describe('LSTMCell', () => {
it('should forward pass correctly', () => {
const cell = new LSTMCell(10, 64);
const x = new Array(10).fill(0.1);
const h = new Array(64).fill(0);
const c = new Array(64).fill(0);
const result = cell.forward(x, h, c);
expect(result.h).toHaveLength(64);
expect(result.c).toHaveLength(64);
expect(result.h.every(v => !isNaN(v))).toBe(true);
});
it('should handle zero inputs', () => {
const cell = new LSTMCell(10, 64);
const x = new Array(10).fill(0);
const h = new Array(64).fill(0);
const c = new Array(64).fill(0);
const result = cell.forward(x, h, c);
expect(result.h.every(v => isFinite(v))).toBe(true);
});
});
describe('FeatureExtractor', () => {
it('should extract features from candles', () => {
const extractor = new FeatureExtractor();
const candles = [];
for (let i = 0; i < 100; i++) {
candles.push({
open: 100 + Math.random() * 10,
high: 105 + Math.random() * 10,
low: 95 + Math.random() * 10,
close: 100 + Math.random() * 10,
volume: 1000000
});
}
const features = extractor.extract(candles);
expect(features.length).toBe(99); // One less than candles
expect(features[0].length).toBe(10); // 10 features
});
});
describe('HybridLSTMTransformer', () => {
it('should produce valid predictions', () => {
const model = new HybridLSTMTransformer();
const features = [];
for (let i = 0; i < 50; i++) {
features.push(new Array(10).fill(0).map(() => Math.random() - 0.5));
}
const result = model.predict(features);
expect(result).toHaveProperty('prediction');
expect(result).toHaveProperty('confidence');
expect(result).toHaveProperty('signal');
expect(['BUY', 'SELL', 'HOLD']).toContain(result.signal);
});
});
});
// ============================================================================
// DRL PORTFOLIO MANAGER TESTS
// ============================================================================
describe('DRL Portfolio Manager', () => {
let EnsemblePortfolioManager, ReplayBuffer, NeuralNetwork;
beforeEach(async () => {
const module = await import('../production/drl-portfolio-manager.js');
EnsemblePortfolioManager = module.EnsemblePortfolioManager;
ReplayBuffer = module.ReplayBuffer;
NeuralNetwork = module.NeuralNetwork;
});
describe('ReplayBuffer', () => {
it('should store and sample experiences', () => {
const buffer = new ReplayBuffer(100);
for (let i = 0; i < 50; i++) {
buffer.push(
new Array(10).fill(i),
new Array(5).fill(0.2),
Math.random(),
new Array(10).fill(i + 1),
false
);
}
expect(buffer.length).toBe(50);
const batch = buffer.sample(16);
expect(batch).toHaveLength(16);
});
it('should respect max size', () => {
const buffer = new ReplayBuffer(10);
for (let i = 0; i < 20; i++) {
buffer.push([i], [0.5], 1, [i + 1], false);
}
expect(buffer.length).toBe(10);
});
});
describe('NeuralNetwork', () => {
it('should forward pass correctly', () => {
const net = new NeuralNetwork([10, 32, 5]);
const input = new Array(10).fill(0.5);
const result = net.forward(input);
expect(result.output).toHaveLength(5);
expect(result.output.every(v => isFinite(v))).toBe(true);
});
});
describe('EnsemblePortfolioManager', () => {
it('should produce valid portfolio weights', () => {
const manager = new EnsemblePortfolioManager(5);
const state = new Array(62).fill(0).map(() => Math.random());
const action = manager.getEnsembleAction(state);
expect(action).toHaveLength(5);
const sum = action.reduce((a, b) => a + b, 0);
expect(sum).toBeCloseTo(1, 1); // Weights should sum to ~1
});
});
});
// ============================================================================
// SENTIMENT ALPHA TESTS
// ============================================================================
describe('Sentiment Alpha Pipeline', () => {
let LexiconAnalyzer, EmbeddingAnalyzer, SentimentAggregator;
beforeEach(async () => {
const module = await import('../production/sentiment-alpha.js');
LexiconAnalyzer = module.LexiconAnalyzer;
EmbeddingAnalyzer = module.EmbeddingAnalyzer;
SentimentAggregator = module.SentimentAggregator;
});
describe('LexiconAnalyzer', () => {
it('should detect positive sentiment', () => {
const analyzer = new LexiconAnalyzer();
const result = analyzer.analyze('Strong growth and profit beat expectations');
expect(result.score).toBeGreaterThan(0);
expect(result.positiveCount).toBeGreaterThan(0);
});
it('should detect negative sentiment', () => {
const analyzer = new LexiconAnalyzer();
const result = analyzer.analyze('Losses and decline amid recession fears');
expect(result.score).toBeLessThan(0);
expect(result.negativeCount).toBeGreaterThan(0);
});
it('should handle neutral text', () => {
const analyzer = new LexiconAnalyzer();
const result = analyzer.analyze('The company released quarterly results');
expect(Math.abs(result.score)).toBeLessThan(0.5);
});
it('should handle negators', () => {
const analyzer = new LexiconAnalyzer();
const positive = analyzer.analyze('Strong growth');
const negated = analyzer.analyze('Not strong growth');
expect(negated.score).toBeLessThan(positive.score);
});
});
describe('SentimentAggregator', () => {
it('should aggregate multiple sentiments', () => {
const aggregator = new SentimentAggregator();
aggregator.addSentiment('AAPL', { source: 'news', score: 0.5, confidence: 0.8 });
aggregator.addSentiment('AAPL', { source: 'social', score: 0.3, confidence: 0.6 });
const result = aggregator.getAggregatedSentiment('AAPL');
expect(result.score).toBeGreaterThan(0);
expect(result.count).toBe(2);
});
});
});
// ============================================================================
// BACKTESTING TESTS
// ============================================================================
describe('Backtesting Framework', () => {
let PerformanceMetrics, BacktestEngine;
beforeEach(async () => {
const module = await import('../system/backtesting.js');
PerformanceMetrics = module.PerformanceMetrics;
BacktestEngine = module.BacktestEngine;
});
describe('PerformanceMetrics', () => {
it('should calculate metrics from equity curve', () => {
const metrics = new PerformanceMetrics(0.05);
const equityCurve = [100000];
// Generate random walk
for (let i = 0; i < 252; i++) {
const lastValue = equityCurve[equityCurve.length - 1];
equityCurve.push(lastValue * (1 + (Math.random() - 0.48) * 0.02));
}
const result = metrics.calculate(equityCurve);
expect(result).toHaveProperty('totalReturn');
expect(result).toHaveProperty('sharpeRatio');
expect(result).toHaveProperty('maxDrawdown');
expect(result.tradingDays).toBe(252);
});
it('should handle edge cases', () => {
const metrics = new PerformanceMetrics();
// Empty curve
expect(metrics.calculate([]).totalReturn).toBe(0);
// Single value
expect(metrics.calculate([100]).totalReturn).toBe(0);
});
it('should compute drawdown correctly', () => {
const metrics = new PerformanceMetrics();
const equityCurve = [100, 110, 105, 95, 100, 90, 95];
const result = metrics.calculate(equityCurve);
expect(result.maxDrawdown).toBeCloseTo(0.182, 2); // (110-90)/110
});
});
});
// ============================================================================
// RISK MANAGEMENT TESTS
// ============================================================================
describe('Risk Management', () => {
let RiskManager, CircuitBreaker, StopLossManager;
beforeEach(async () => {
const module = await import('../system/risk-management.js');
RiskManager = module.RiskManager;
CircuitBreaker = module.CircuitBreaker;
StopLossManager = module.StopLossManager;
});
describe('StopLossManager', () => {
it('should set and check stops', () => {
const manager = new StopLossManager();
manager.setStop('AAPL', 150, 'fixed');
const check = manager.checkStop('AAPL', 140);
expect(check.triggered).toBe(true);
});
it('should update trailing stops', () => {
const manager = new StopLossManager();
manager.setStop('AAPL', 100, 'trailing');
manager.updateTrailingStop('AAPL', 110);
const stop = manager.stops.get('AAPL');
expect(stop.highWaterMark).toBe(110);
expect(stop.stopPrice).toBeGreaterThan(100 * 0.97);
});
});
describe('CircuitBreaker', () => {
it('should trip on consecutive losses', () => {
const breaker = new CircuitBreaker({ consecutiveLosses: 3 });
breaker.recordTrade(-100);
breaker.recordTrade(-100);
expect(breaker.canTrade().allowed).toBe(true);
breaker.recordTrade(-100);
expect(breaker.canTrade().allowed).toBe(false);
});
it('should reset after cooldown', () => {
const breaker = new CircuitBreaker({
consecutiveLosses: 2,
drawdownCooldown: 100 // 100ms for testing
});
breaker.recordTrade(-100);
breaker.recordTrade(-100);
expect(breaker.canTrade().allowed).toBe(false);
// Wait for cooldown
return new Promise(resolve => {
setTimeout(() => {
expect(breaker.canTrade().allowed).toBe(true);
resolve();
}, 150);
});
});
it('should trip on drawdown', () => {
const breaker = new CircuitBreaker({ drawdownThreshold: 0.10 });
breaker.updateEquity(100000);
breaker.updateEquity(88000); // 12% drawdown
expect(breaker.canTrade().allowed).toBe(false);
});
});
describe('RiskManager', () => {
it('should check trade limits', () => {
const manager = new RiskManager();
manager.startDay(100000);
const portfolio = {
equity: 100000,
cash: 50000,
positions: {}
};
const trade = { symbol: 'AAPL', side: 'buy', value: 5000 };
const result = manager.canTrade('AAPL', trade, portfolio);
expect(result.allowed).toBe(true);
});
it('should block oversized trades', () => {
const manager = new RiskManager();
const portfolio = {
equity: 100000,
cash: 50000,
positions: {}
};
const trade = { symbol: 'AAPL', side: 'buy', value: 60000 };
const result = manager.canTrade('AAPL', trade, portfolio);
expect(result.checks.positionSize.violations.length).toBeGreaterThan(0);
});
});
});
// ============================================================================
// TRADING PIPELINE TESTS
// ============================================================================
describe('Trading Pipeline', () => {
let TradingPipeline, TradingDag;
beforeEach(async () => {
const module = await import('../system/trading-pipeline.js');
TradingPipeline = module.TradingPipeline;
TradingDag = module.TradingDag;
});
describe('TradingDag', () => {
it('should execute nodes in order', async () => {
const dag = new TradingDag();
const order = [];
dag.addNode({
id: 'a',
name: 'Node A',
dependencies: [],
status: 'pending',
executor: async () => { order.push('a'); return 'A'; }
});
dag.addNode({
id: 'b',
name: 'Node B',
dependencies: ['a'],
status: 'pending',
executor: async (ctx, deps) => { order.push('b'); return deps.a + 'B'; }
});
await dag.execute({});
expect(order).toEqual(['a', 'b']);
expect(dag.results.get('b')).toBe('AB');
});
it('should execute parallel nodes concurrently', async () => {
const dag = new TradingDag({ parallelExecution: true, maxConcurrency: 2 });
const timestamps = {};
dag.addNode({
id: 'a',
dependencies: [],
status: 'pending',
executor: async () => { timestamps.a = Date.now(); return 'A'; }
});
dag.addNode({
id: 'b',
dependencies: [],
status: 'pending',
executor: async () => { timestamps.b = Date.now(); return 'B'; }
});
await dag.execute({});
// Both should start at nearly the same time
expect(Math.abs(timestamps.a - timestamps.b)).toBeLessThan(50);
});
});
});
// ============================================================================
// INTEGRATION TESTS
// ============================================================================
describe('Integration Tests', () => {
it('should run complete pipeline', async () => {
const { createTradingPipeline } = await import('../system/trading-pipeline.js');
const pipeline = createTradingPipeline();
const generateCandles = (n) => {
const candles = [];
let price = 100;
for (let i = 0; i < n; i++) {
price *= (1 + (Math.random() - 0.5) * 0.02);
candles.push({
open: price * 0.99,
high: price * 1.01,
low: price * 0.98,
close: price,
volume: 1000000
});
}
return candles;
};
const context = {
marketData: generateCandles(100),
newsData: [
{ symbol: 'TEST', text: 'Strong earnings growth', source: 'news' }
],
symbols: ['TEST'],
portfolio: { equity: 100000, cash: 50000, positions: {}, assets: ['TEST'] },
prices: { TEST: 100 }
};
const result = await pipeline.execute(context);
expect(result).toHaveProperty('signals');
expect(result).toHaveProperty('positions');
expect(result).toHaveProperty('orders');
expect(result).toHaveProperty('metrics');
});
});
export {};