git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
490 lines
16 KiB
JavaScript
490 lines
16 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Production Module Benchmark Suite
|
|
*
|
|
* Comprehensive benchmarks for:
|
|
* - Fractional Kelly Engine
|
|
* - Hybrid LSTM-Transformer
|
|
* - DRL Portfolio Manager
|
|
* - Sentiment Alpha Pipeline
|
|
*
|
|
* Measures: latency, throughput, accuracy, memory usage
|
|
*/
|
|
|
|
import { performance } from 'perf_hooks';
|
|
|
|
// Benchmark configuration
|
|
const benchConfig = {
|
|
iterations: 100,
|
|
warmupIterations: 10,
|
|
dataPoints: {
|
|
small: 100,
|
|
medium: 500,
|
|
large: 1000
|
|
}
|
|
};
|
|
|
|
// Memory tracking
|
|
function getMemoryMB() {
|
|
const usage = process.memoryUsage();
|
|
return {
|
|
heap: Math.round(usage.heapUsed / 1024 / 1024 * 100) / 100,
|
|
total: Math.round(usage.heapTotal / 1024 / 1024 * 100) / 100
|
|
};
|
|
}
|
|
|
|
// Benchmark runner
|
|
async function benchmark(name, fn, iterations = benchConfig.iterations) {
|
|
// Warmup
|
|
for (let i = 0; i < benchConfig.warmupIterations; i++) {
|
|
await fn();
|
|
}
|
|
|
|
if (global.gc) global.gc();
|
|
const memBefore = getMemoryMB();
|
|
const times = [];
|
|
|
|
for (let i = 0; i < iterations; i++) {
|
|
const start = performance.now();
|
|
await fn();
|
|
times.push(performance.now() - start);
|
|
}
|
|
|
|
const memAfter = getMemoryMB();
|
|
times.sort((a, b) => a - b);
|
|
|
|
return {
|
|
name,
|
|
iterations,
|
|
min: times[0].toFixed(3),
|
|
max: times[times.length - 1].toFixed(3),
|
|
mean: (times.reduce((a, b) => a + b, 0) / times.length).toFixed(3),
|
|
median: times[Math.floor(times.length / 2)].toFixed(3),
|
|
p95: times[Math.floor(times.length * 0.95)].toFixed(3),
|
|
p99: times[Math.floor(times.length * 0.99)].toFixed(3),
|
|
throughput: (iterations / (times.reduce((a, b) => a + b, 0) / 1000)).toFixed(1),
|
|
memDelta: (memAfter.heap - memBefore.heap).toFixed(2)
|
|
};
|
|
}
|
|
|
|
// ============= Kelly Criterion Benchmarks =============
|
|
function benchmarkKelly() {
|
|
// Inline implementation for isolated benchmarking
|
|
class KellyCriterion {
|
|
calculateFullKelly(winProbability, decimalOdds) {
|
|
const b = decimalOdds - 1;
|
|
const p = winProbability;
|
|
const q = 1 - p;
|
|
return Math.max(0, (b * p - q) / b);
|
|
}
|
|
|
|
calculateFractionalKelly(winProbability, decimalOdds, fraction = 0.2) {
|
|
const fullKelly = this.calculateFullKelly(winProbability, decimalOdds);
|
|
if (fullKelly <= 0) return { stake: 0, edge: 0 };
|
|
|
|
const adjustedKelly = Math.min(fullKelly * fraction, 0.05);
|
|
const edge = (winProbability * decimalOdds) - 1;
|
|
|
|
return {
|
|
stake: adjustedKelly * 10000,
|
|
stakePercent: adjustedKelly * 100,
|
|
edge: edge * 100
|
|
};
|
|
}
|
|
|
|
calculateMultiBetKelly(bets, fraction = 0.2) {
|
|
const results = bets.map(bet => ({
|
|
...bet,
|
|
kelly: this.calculateFractionalKelly(bet.winProbability, bet.decimalOdds, fraction)
|
|
}));
|
|
|
|
const totalKelly = results.reduce((sum, b) => sum + (b.kelly.stakePercent || 0), 0);
|
|
const scaleFactor = totalKelly > 5 ? 5 / totalKelly : 1;
|
|
|
|
return results.map(r => ({
|
|
...r,
|
|
kelly: {
|
|
...r.kelly,
|
|
stake: (r.kelly.stake || 0) * scaleFactor
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
|
|
const kelly = new KellyCriterion();
|
|
|
|
return {
|
|
single: () => kelly.calculateFractionalKelly(0.55, 2.0),
|
|
multi10: () => kelly.calculateMultiBetKelly(
|
|
Array(10).fill(null).map(() => ({
|
|
winProbability: 0.5 + Math.random() * 0.1,
|
|
decimalOdds: 1.8 + Math.random() * 0.4
|
|
}))
|
|
),
|
|
multi100: () => kelly.calculateMultiBetKelly(
|
|
Array(100).fill(null).map(() => ({
|
|
winProbability: 0.5 + Math.random() * 0.1,
|
|
decimalOdds: 1.8 + Math.random() * 0.4
|
|
}))
|
|
)
|
|
};
|
|
}
|
|
|
|
// ============= LSTM-Transformer Benchmarks =============
|
|
function benchmarkLSTMTransformer() {
|
|
class LSTMCell {
|
|
constructor(inputSize, hiddenSize) {
|
|
this.inputSize = inputSize;
|
|
this.hiddenSize = hiddenSize;
|
|
const scale = Math.sqrt(2.0 / (inputSize + hiddenSize));
|
|
this.Wf = Array(hiddenSize).fill(null).map(() =>
|
|
Array(inputSize + hiddenSize).fill(null).map(() => (Math.random() - 0.5) * 2 * scale)
|
|
);
|
|
}
|
|
|
|
sigmoid(x) { return 1 / (1 + Math.exp(-Math.max(-500, Math.min(500, x)))); }
|
|
|
|
forward(x, hPrev) {
|
|
const combined = [...x, ...hPrev];
|
|
const h = this.Wf.map(row =>
|
|
this.sigmoid(row.reduce((sum, w, j) => sum + w * combined[j], 0))
|
|
);
|
|
return { h, c: h };
|
|
}
|
|
}
|
|
|
|
class LSTMLayer {
|
|
constructor(inputSize, hiddenSize) {
|
|
this.cell = new LSTMCell(inputSize, hiddenSize);
|
|
this.hiddenSize = hiddenSize;
|
|
}
|
|
|
|
forward(sequence) {
|
|
let h = new Array(this.hiddenSize).fill(0);
|
|
for (const x of sequence) {
|
|
const result = this.cell.forward(x, h);
|
|
h = result.h;
|
|
}
|
|
return h;
|
|
}
|
|
}
|
|
|
|
function softmax(arr) {
|
|
let max = arr[0];
|
|
for (let i = 1; i < arr.length; i++) if (arr[i] > max) max = arr[i];
|
|
const exp = arr.map(x => Math.exp(x - max));
|
|
const sum = exp.reduce((a, b) => a + b, 0);
|
|
return exp.map(x => x / sum);
|
|
}
|
|
|
|
function attention(Q, K, V, dim) {
|
|
const scale = Math.sqrt(dim);
|
|
const scores = Q.map((q, i) =>
|
|
K.map((k, j) => q.reduce((sum, qv, idx) => sum + qv * k[idx], 0) / scale)
|
|
);
|
|
const weights = scores.map(row => softmax(row));
|
|
return weights.map((row, i) =>
|
|
V[0].map((_, j) => row.reduce((sum, w, k) => sum + w * V[k][j], 0))
|
|
);
|
|
}
|
|
|
|
const lstm = new LSTMLayer(10, 64);
|
|
|
|
return {
|
|
lstmSmall: () => lstm.forward(Array(10).fill(null).map(() =>
|
|
Array(10).fill(null).map(() => Math.random())
|
|
)),
|
|
lstmMedium: () => lstm.forward(Array(50).fill(null).map(() =>
|
|
Array(10).fill(null).map(() => Math.random())
|
|
)),
|
|
lstmLarge: () => lstm.forward(Array(100).fill(null).map(() =>
|
|
Array(10).fill(null).map(() => Math.random())
|
|
)),
|
|
attention: () => {
|
|
const seq = Array(20).fill(null).map(() =>
|
|
Array(64).fill(null).map(() => Math.random())
|
|
);
|
|
return attention(seq, seq, seq, 64);
|
|
}
|
|
};
|
|
}
|
|
|
|
// ============= DRL Benchmarks =============
|
|
function benchmarkDRL() {
|
|
class NeuralNetwork {
|
|
constructor(inputDim, hiddenDim, outputDim) {
|
|
const scale = Math.sqrt(2.0 / (inputDim + hiddenDim));
|
|
this.W1 = Array(inputDim).fill(null).map(() =>
|
|
Array(hiddenDim).fill(null).map(() => (Math.random() - 0.5) * 2 * scale)
|
|
);
|
|
this.W2 = Array(hiddenDim).fill(null).map(() =>
|
|
Array(outputDim).fill(null).map(() => (Math.random() - 0.5) * 2 * scale)
|
|
);
|
|
this.inputDim = inputDim;
|
|
this.hiddenDim = hiddenDim;
|
|
this.outputDim = outputDim;
|
|
}
|
|
|
|
forward(input) {
|
|
// Layer 1 with ReLU
|
|
const h = new Array(this.hiddenDim).fill(0);
|
|
for (let i = 0; i < this.hiddenDim; i++) {
|
|
for (let j = 0; j < this.inputDim; j++) {
|
|
h[i] += input[j] * this.W1[j][i];
|
|
}
|
|
h[i] = Math.max(0, h[i]);
|
|
}
|
|
|
|
// Output layer
|
|
const output = new Array(this.outputDim).fill(0);
|
|
for (let i = 0; i < this.outputDim; i++) {
|
|
for (let j = 0; j < this.hiddenDim; j++) {
|
|
output[i] += h[j] * this.W2[j][i];
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
}
|
|
|
|
class ReplayBuffer {
|
|
constructor(capacity) {
|
|
this.capacity = capacity;
|
|
this.buffer = [];
|
|
this.position = 0;
|
|
}
|
|
|
|
push(data) {
|
|
if (this.buffer.length < this.capacity) this.buffer.push(null);
|
|
this.buffer[this.position] = data;
|
|
this.position = (this.position + 1) % this.capacity;
|
|
}
|
|
|
|
sample(batchSize) {
|
|
const batch = [];
|
|
for (let i = 0; i < Math.min(batchSize, this.buffer.length); i++) {
|
|
batch.push(this.buffer[Math.floor(Math.random() * this.buffer.length)]);
|
|
}
|
|
return batch;
|
|
}
|
|
}
|
|
|
|
const network = new NeuralNetwork(100, 128, 10);
|
|
const buffer = new ReplayBuffer(10000);
|
|
|
|
// Pre-fill buffer
|
|
for (let i = 0; i < 1000; i++) {
|
|
buffer.push({ state: Array(100).fill(Math.random()), reward: Math.random() });
|
|
}
|
|
|
|
return {
|
|
networkForward: () => network.forward(Array(100).fill(null).map(() => Math.random())),
|
|
bufferSample: () => buffer.sample(64),
|
|
bufferPush: () => buffer.push({ state: Array(100).fill(Math.random()), reward: Math.random() }),
|
|
fullStep: () => {
|
|
const state = Array(100).fill(null).map(() => Math.random());
|
|
const action = network.forward(state);
|
|
buffer.push({ state, action, reward: Math.random() });
|
|
return action;
|
|
}
|
|
};
|
|
}
|
|
|
|
// ============= Sentiment Benchmarks =============
|
|
function benchmarkSentiment() {
|
|
const positiveWords = new Set([
|
|
'growth', 'profit', 'gains', 'bullish', 'upgrade', 'beat', 'exceeded',
|
|
'outperform', 'strong', 'surge', 'rally', 'breakthrough', 'innovation'
|
|
]);
|
|
|
|
const negativeWords = new Set([
|
|
'loss', 'decline', 'bearish', 'downgrade', 'miss', 'below', 'weak',
|
|
'underperform', 'crash', 'plunge', 'risk', 'concern', 'warning'
|
|
]);
|
|
|
|
function lexiconAnalyze(text) {
|
|
const words = text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/);
|
|
let score = 0;
|
|
let count = 0;
|
|
|
|
for (const word of words) {
|
|
if (positiveWords.has(word)) { score++; count++; }
|
|
else if (negativeWords.has(word)) { score--; count++; }
|
|
}
|
|
|
|
return {
|
|
score: count > 0 ? score / count : 0,
|
|
confidence: Math.min(1, count / 5)
|
|
};
|
|
}
|
|
|
|
function hashEmbed(text, dim = 64) {
|
|
const words = text.toLowerCase().split(/\s+/);
|
|
const embedding = new Array(dim).fill(0);
|
|
|
|
for (const word of words) {
|
|
let hash = 0;
|
|
for (let i = 0; i < word.length; i++) {
|
|
hash = ((hash << 5) - hash) + word.charCodeAt(i);
|
|
hash = hash & hash;
|
|
}
|
|
for (let i = 0; i < dim; i++) {
|
|
embedding[i] += Math.sin(hash * (i + 1)) / words.length;
|
|
}
|
|
}
|
|
|
|
return embedding;
|
|
}
|
|
|
|
const sampleTexts = [
|
|
'Strong quarterly earnings beat analyst expectations with record revenue growth',
|
|
'Company warns of significant losses amid declining market demand',
|
|
'Neutral outlook as market conditions remain stable',
|
|
'Major breakthrough innovation drives optimistic investor sentiment'
|
|
];
|
|
|
|
return {
|
|
lexiconSingle: () => lexiconAnalyze(sampleTexts[0]),
|
|
lexiconBatch: () => sampleTexts.map(t => lexiconAnalyze(t)),
|
|
embedSingle: () => hashEmbed(sampleTexts[0]),
|
|
embedBatch: () => sampleTexts.map(t => hashEmbed(t)),
|
|
fullPipeline: () => {
|
|
const results = sampleTexts.map(text => ({
|
|
lexicon: lexiconAnalyze(text),
|
|
embedding: hashEmbed(text)
|
|
}));
|
|
// Aggregate
|
|
const scores = results.map(r => 0.4 * r.lexicon.score + 0.6 * Math.tanh(
|
|
r.embedding.reduce((a, b) => a + b, 0) * 0.1
|
|
));
|
|
return scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
}
|
|
};
|
|
}
|
|
|
|
// ============= Main Benchmark Runner =============
|
|
async function runBenchmarks() {
|
|
console.log('═'.repeat(70));
|
|
console.log('PRODUCTION MODULE BENCHMARK SUITE');
|
|
console.log('═'.repeat(70));
|
|
console.log();
|
|
console.log(`Iterations: ${benchConfig.iterations} | Warmup: ${benchConfig.warmupIterations}`);
|
|
console.log();
|
|
|
|
const results = [];
|
|
|
|
// 1. Kelly Criterion Benchmarks
|
|
console.log('1. FRACTIONAL KELLY ENGINE');
|
|
console.log('─'.repeat(70));
|
|
|
|
const kellyBench = benchmarkKelly();
|
|
|
|
const kellySingle = await benchmark('Kelly Single Bet', kellyBench.single, 1000);
|
|
const kellyMulti10 = await benchmark('Kelly Multi (10 bets)', kellyBench.multi10);
|
|
const kellyMulti100 = await benchmark('Kelly Multi (100 bets)', kellyBench.multi100);
|
|
|
|
console.log(` Single bet: ${kellySingle.mean}ms (${kellySingle.throughput}/s)`);
|
|
console.log(` 10 bets: ${kellyMulti10.mean}ms (${kellyMulti10.throughput}/s)`);
|
|
console.log(` 100 bets: ${kellyMulti100.mean}ms (${kellyMulti100.throughput}/s)`);
|
|
results.push(kellySingle, kellyMulti10, kellyMulti100);
|
|
console.log();
|
|
|
|
// 2. LSTM-Transformer Benchmarks
|
|
console.log('2. HYBRID LSTM-TRANSFORMER');
|
|
console.log('─'.repeat(70));
|
|
|
|
const lstmBench = benchmarkLSTMTransformer();
|
|
|
|
const lstmSmall = await benchmark('LSTM (seq=10)', lstmBench.lstmSmall);
|
|
const lstmMedium = await benchmark('LSTM (seq=50)', lstmBench.lstmMedium);
|
|
const lstmLarge = await benchmark('LSTM (seq=100)', lstmBench.lstmLarge);
|
|
const attention = await benchmark('Attention (seq=20)', lstmBench.attention);
|
|
|
|
console.log(` LSTM seq=10: ${lstmSmall.mean}ms (${lstmSmall.throughput}/s)`);
|
|
console.log(` LSTM seq=50: ${lstmMedium.mean}ms (${lstmMedium.throughput}/s)`);
|
|
console.log(` LSTM seq=100: ${lstmLarge.mean}ms (${lstmLarge.throughput}/s)`);
|
|
console.log(` Attention: ${attention.mean}ms (${attention.throughput}/s)`);
|
|
results.push(lstmSmall, lstmMedium, lstmLarge, attention);
|
|
console.log();
|
|
|
|
// 3. DRL Benchmarks
|
|
console.log('3. DRL PORTFOLIO MANAGER');
|
|
console.log('─'.repeat(70));
|
|
|
|
const drlBench = benchmarkDRL();
|
|
|
|
const networkFwd = await benchmark('Network Forward', drlBench.networkForward, 1000);
|
|
const bufferSample = await benchmark('Buffer Sample (64)', drlBench.bufferSample, 1000);
|
|
const bufferPush = await benchmark('Buffer Push', drlBench.bufferPush, 1000);
|
|
const fullStep = await benchmark('Full RL Step', drlBench.fullStep);
|
|
|
|
console.log(` Network fwd: ${networkFwd.mean}ms (${networkFwd.throughput}/s)`);
|
|
console.log(` Buffer sample: ${bufferSample.mean}ms (${bufferSample.throughput}/s)`);
|
|
console.log(` Buffer push: ${bufferPush.mean}ms (${bufferPush.throughput}/s)`);
|
|
console.log(` Full RL step: ${fullStep.mean}ms (${fullStep.throughput}/s)`);
|
|
results.push(networkFwd, bufferSample, bufferPush, fullStep);
|
|
console.log();
|
|
|
|
// 4. Sentiment Benchmarks
|
|
console.log('4. SENTIMENT ALPHA PIPELINE');
|
|
console.log('─'.repeat(70));
|
|
|
|
const sentBench = benchmarkSentiment();
|
|
|
|
const lexSingle = await benchmark('Lexicon Single', sentBench.lexiconSingle, 1000);
|
|
const lexBatch = await benchmark('Lexicon Batch (4)', sentBench.lexiconBatch);
|
|
const embedSingle = await benchmark('Embedding Single', sentBench.embedSingle, 1000);
|
|
const embedBatch = await benchmark('Embedding Batch (4)', sentBench.embedBatch);
|
|
const fullPipe = await benchmark('Full Pipeline', sentBench.fullPipeline);
|
|
|
|
console.log(` Lexicon: ${lexSingle.mean}ms (${lexSingle.throughput}/s)`);
|
|
console.log(` Lexicon batch: ${lexBatch.mean}ms (${lexBatch.throughput}/s)`);
|
|
console.log(` Embedding: ${embedSingle.mean}ms (${embedSingle.throughput}/s)`);
|
|
console.log(` Embed batch: ${embedBatch.mean}ms (${embedBatch.throughput}/s)`);
|
|
console.log(` Full pipeline: ${fullPipe.mean}ms (${fullPipe.throughput}/s)`);
|
|
results.push(lexSingle, lexBatch, embedSingle, embedBatch, fullPipe);
|
|
console.log();
|
|
|
|
// Summary
|
|
console.log('═'.repeat(70));
|
|
console.log('BENCHMARK SUMMARY');
|
|
console.log('═'.repeat(70));
|
|
console.log();
|
|
|
|
// Find fastest and slowest
|
|
const sorted = [...results].sort((a, b) => parseFloat(a.mean) - parseFloat(b.mean));
|
|
|
|
console.log('Fastest Operations:');
|
|
for (const r of sorted.slice(0, 5)) {
|
|
console.log(` ${r.name.padEnd(25)} ${r.mean}ms (${r.throughput}/s)`);
|
|
}
|
|
console.log();
|
|
|
|
console.log('Production Readiness:');
|
|
console.log('─'.repeat(70));
|
|
console.log(' Module │ Latency │ Throughput │ Status');
|
|
console.log('─'.repeat(70));
|
|
|
|
const modules = [
|
|
{ name: 'Kelly Engine', latency: kellyMulti10.mean, throughput: kellyMulti10.throughput },
|
|
{ name: 'LSTM-Transformer', latency: lstmMedium.mean, throughput: lstmMedium.throughput },
|
|
{ name: 'DRL Portfolio', latency: fullStep.mean, throughput: fullStep.throughput },
|
|
{ name: 'Sentiment Alpha', latency: fullPipe.mean, throughput: fullPipe.throughput }
|
|
];
|
|
|
|
for (const m of modules) {
|
|
const status = parseFloat(m.latency) < 10 ? '✓ Ready' : parseFloat(m.latency) < 50 ? '⚠ Acceptable' : '✗ Optimize';
|
|
console.log(` ${m.name.padEnd(24)} │ ${m.latency.padStart(6)}ms │ ${m.throughput.padStart(8)}/s │ ${status}`);
|
|
}
|
|
console.log();
|
|
|
|
console.log('═'.repeat(70));
|
|
console.log('Benchmark suite completed');
|
|
console.log('═'.repeat(70));
|
|
|
|
return results;
|
|
}
|
|
|
|
// Run benchmarks
|
|
runBenchmarks().catch(console.error);
|