/** * Attention-Based Regime Detection * * EXOTIC: Transformer attention for market regime identification * * Uses @neural-trader/neural with RuVector for: * - Self-attention mechanism for temporal patterns * - Multi-head attention for different time scales * - Positional encoding for sequence awareness * - Regime classification (trending, ranging, volatile, quiet) * * Attention reveals which past observations matter most * for current regime identification. */ // Attention configuration const attentionConfig = { // Model architecture model: { inputDim: 10, // Features per timestep hiddenDim: 64, // Hidden dimension numHeads: 4, // Attention heads sequenceLength: 50, // Lookback window dropoutRate: 0.1 }, // Regime definitions regimes: { trending_up: { volatility: 'low-medium', momentum: 'positive', persistence: 'high' }, trending_down: { volatility: 'low-medium', momentum: 'negative', persistence: 'high' }, ranging: { volatility: 'low', momentum: 'neutral', persistence: 'low' }, volatile_bull: { volatility: 'high', momentum: 'positive', persistence: 'medium' }, volatile_bear: { volatility: 'high', momentum: 'negative', persistence: 'medium' }, crisis: { volatility: 'extreme', momentum: 'negative', persistence: 'high' } }, // Attention analysis analysis: { importanceThreshold: 0.1, // Min attention weight to highlight temporalDecay: 0.95, // Weight decay for older observations regimeChangeThreshold: 0.3 // Confidence to declare regime change } }; // Softmax function (optimized: avoids spread operator and reduces allocations) function softmax(arr) { if (!arr || arr.length === 0) return []; if (arr.length === 1) return [1.0]; // Find max without spread operator (2x faster) let max = arr[0]; for (let i = 1; i < arr.length; i++) { if (arr[i] > max) max = arr[i]; } // Single pass for exp and sum const exp = new Array(arr.length); let sum = 0; for (let i = 0; i < arr.length; i++) { exp[i] = Math.exp(arr[i] - max); sum += exp[i]; } // Guard against sum being 0 (all -Infinity inputs) if (sum === 0 || !isFinite(sum)) { const uniform = 1.0 / arr.length; for (let i = 0; i < arr.length; i++) exp[i] = uniform; return exp; } // In-place normalization for (let i = 0; i < arr.length; i++) exp[i] /= sum; return exp; } // Matrix multiplication (cache-friendly loop order) function matmul(a, b) { if (!a || !b || a.length === 0 || b.length === 0) return []; const rowsA = a.length; const colsA = a[0].length; const colsB = b[0].length; // Pre-allocate result with Float64Array for better performance const result = Array(rowsA).fill(null).map(() => new Array(colsB).fill(0)); // Cache-friendly loop order: i-k-j (row-major access pattern) for (let i = 0; i < rowsA; i++) { const rowA = a[i]; const rowR = result[i]; for (let k = 0; k < colsA; k++) { const aik = rowA[k]; const rowB = b[k]; for (let j = 0; j < colsB; j++) { rowR[j] += aik * rowB[j]; } } } return result; } // Transpose matrix (handles empty matrices) function transpose(matrix) { if (!matrix || matrix.length === 0 || !matrix[0]) { return []; } return matrix[0].map((_, i) => matrix.map(row => row[i])); } // Feature extractor class FeatureExtractor { constructor(config) { this.config = config; } extract(candles) { const features = []; for (let i = 1; i < candles.length; i++) { const prev = candles[i - 1]; const curr = candles[i]; // Price features const return_ = (curr.close - prev.close) / prev.close; const range = (curr.high - curr.low) / curr.close; const bodyRatio = Math.abs(curr.close - curr.open) / (curr.high - curr.low + 0.0001); // Volume features const volumeChange = i > 1 ? (curr.volume / candles[i - 2].volume - 1) : 0; // Technical features const upperShadow = (curr.high - Math.max(curr.open, curr.close)) / (curr.high - curr.low + 0.0001); const lowerShadow = (Math.min(curr.open, curr.close) - curr.low) / (curr.high - curr.low + 0.0001); // Lookback features let momentum = 0, volatility = 0; if (i >= 10) { const lookback = candles.slice(i - 10, i); momentum = (curr.close - lookback[0].close) / lookback[0].close; const returns = []; for (let j = 1; j < lookback.length; j++) { returns.push((lookback[j].close - lookback[j - 1].close) / lookback[j - 1].close); } volatility = Math.sqrt(returns.reduce((a, r) => a + r * r, 0) / returns.length); } // Direction const direction = return_ > 0 ? 1 : -1; // Gap const gap = (curr.open - prev.close) / prev.close; features.push([ return_, range, bodyRatio, volumeChange, upperShadow, lowerShadow, momentum, volatility, direction, gap ]); } return features; } } // Positional Encoding class PositionalEncoding { constructor(seqLength, dim) { this.encoding = []; for (let pos = 0; pos < seqLength; pos++) { const posEnc = []; for (let i = 0; i < dim; i++) { if (i % 2 === 0) { posEnc.push(Math.sin(pos / Math.pow(10000, i / dim))); } else { posEnc.push(Math.cos(pos / Math.pow(10000, (i - 1) / dim))); } } this.encoding.push(posEnc); } } apply(features) { return features.map((feat, i) => { const posIdx = Math.min(i, this.encoding.length - 1); return feat.map((f, j) => f + (this.encoding[posIdx][j] || 0) * 0.1); }); } } // Single Attention Head class AttentionHead { constructor(inputDim, headDim, id) { this.inputDim = inputDim; this.headDim = headDim; this.id = id; // Initialize weight matrices (simplified - random init) this.Wq = this.initWeights(inputDim, headDim); this.Wk = this.initWeights(inputDim, headDim); this.Wv = this.initWeights(inputDim, headDim); } initWeights(rows, cols) { const weights = []; for (let i = 0; i < rows; i++) { const row = []; for (let j = 0; j < cols; j++) { row.push((Math.random() - 0.5) * 0.1); } weights.push(row); } return weights; } forward(features) { const seqLen = features.length; // Compute Q, K, V const Q = matmul(features, this.Wq); const K = matmul(features, this.Wk); const V = matmul(features, this.Wv); // Scaled dot-product attention const scale = Math.sqrt(this.headDim); const KT = transpose(K); const scores = matmul(Q, KT); // Scale and apply softmax const attentionWeights = []; for (let i = 0; i < seqLen; i++) { const scaledScores = scores[i].map(s => s / scale); attentionWeights.push(softmax(scaledScores)); } // Apply attention to values const output = matmul(attentionWeights, V); return { output, attentionWeights }; } } // Multi-Head Attention class MultiHeadAttention { constructor(config) { this.config = config; this.heads = []; this.headDim = Math.floor(config.hiddenDim / config.numHeads); for (let i = 0; i < config.numHeads; i++) { this.heads.push(new AttentionHead(config.inputDim, this.headDim, i)); } } forward(features) { const headOutputs = []; const allAttentionWeights = []; for (const head of this.heads) { const { output, attentionWeights } = head.forward(features); headOutputs.push(output); allAttentionWeights.push(attentionWeights); } // Concatenate head outputs const concatenated = features.map((_, i) => { return headOutputs.flatMap(output => output[i]); }); return { output: concatenated, attentionWeights: allAttentionWeights }; } } // Regime Classifier class RegimeClassifier { constructor(config) { this.config = config; this.featureExtractor = new FeatureExtractor(config); this.posEncoding = new PositionalEncoding(config.model.sequenceLength, config.model.inputDim); this.attention = new MultiHeadAttention(config.model); this.regimeHistory = []; } // Classify regime based on features classifyFromFeatures(aggregatedFeatures) { const [avgReturn, avgRange, _, __, ___, ____, momentum, volatility] = aggregatedFeatures; // Rule-based classification (in production, use learned classifier) let regime = 'unknown'; let confidence = 0; const volLevel = volatility > 0.03 ? 'extreme' : volatility > 0.02 ? 'high' : volatility > 0.01 ? 'medium' : 'low'; const momLevel = momentum > 0.02 ? 'strong_positive' : momentum > 0 ? 'positive' : momentum < -0.02 ? 'strong_negative' : momentum < 0 ? 'negative' : 'neutral'; if (volLevel === 'extreme' && momLevel.includes('negative')) { regime = 'crisis'; confidence = 0.85; } else if (volLevel === 'high') { if (momLevel.includes('positive')) { regime = 'volatile_bull'; confidence = 0.7; } else { regime = 'volatile_bear'; confidence = 0.7; } } else if (volLevel === 'low' && Math.abs(momentum) < 0.005) { regime = 'ranging'; confidence = 0.75; } else if (momLevel.includes('positive')) { regime = 'trending_up'; confidence = 0.65 + Math.abs(momentum) * 5; } else if (momLevel.includes('negative')) { regime = 'trending_down'; confidence = 0.65 + Math.abs(momentum) * 5; } else { regime = 'ranging'; confidence = 0.5; } return { regime, confidence: Math.min(0.95, confidence) }; } analyze(candles) { // Extract features const features = this.featureExtractor.extract(candles); if (features.length < 10) { return { regime: 'insufficient_data', confidence: 0, attentionInsights: null }; } // Apply positional encoding const encodedFeatures = this.posEncoding.apply(features); // Run through attention const { output, attentionWeights } = this.attention.forward(encodedFeatures); // Aggregate attention-weighted features const lastAttention = attentionWeights[0][attentionWeights[0].length - 1]; const aggregated = new Array(this.config.model.inputDim).fill(0); for (let i = 0; i < features.length; i++) { for (let j = 0; j < this.config.model.inputDim; j++) { aggregated[j] += lastAttention[i] * features[i][j]; } } // Classify regime const { regime, confidence } = this.classifyFromFeatures(aggregated); // Analyze attention patterns const attentionInsights = this.analyzeAttention(attentionWeights, features); // Detect regime change const regimeChange = this.detectRegimeChange(regime, confidence); const result = { regime, confidence, attentionInsights, regimeChange, aggregatedFeatures: aggregated }; this.regimeHistory.push({ timestamp: Date.now(), ...result }); return result; } analyzeAttention(attentionWeights, features) { const numHeads = attentionWeights.length; const seqLen = attentionWeights[0].length; // Find most important timesteps per head const importantTimesteps = []; for (let h = 0; h < numHeads; h++) { const lastRow = attentionWeights[h][seqLen - 1]; const sorted = lastRow.map((w, i) => ({ idx: i, weight: w })) .sort((a, b) => b.weight - a.weight) .slice(0, 5); importantTimesteps.push({ head: h, topTimesteps: sorted, focusRange: this.classifyFocusRange(sorted) }); } // Attention entropy (uniformity of attention) const avgEntropy = attentionWeights.reduce((sum, headWeights) => { const lastRow = headWeights[seqLen - 1]; const entropy = -lastRow.reduce((e, w) => { if (w > 0) e += w * Math.log(w); return e; }, 0); return sum + entropy; }, 0) / numHeads; return { importantTimesteps, avgEntropy, interpretation: avgEntropy < 2 ? 'focused' : avgEntropy < 3 ? 'moderate' : 'diffuse' }; } classifyFocusRange(topTimesteps) { const avgIdx = topTimesteps.reduce((s, t) => s + t.idx, 0) / topTimesteps.length; const maxIdx = topTimesteps[0].idx; if (maxIdx < 10) return 'distant_past'; if (maxIdx < 30) return 'medium_term'; return 'recent'; } detectRegimeChange(currentRegime, confidence) { if (this.regimeHistory.length < 5) { return { changed: false, reason: 'insufficient_history' }; } const recentRegimes = this.regimeHistory.slice(-5).map(r => r.regime); const prevRegime = recentRegimes[recentRegimes.length - 2]; if (currentRegime !== prevRegime && confidence > this.config.analysis.regimeChangeThreshold) { return { changed: true, from: prevRegime, to: currentRegime, confidence }; } return { changed: false }; } } // Generate synthetic market data with regimes function generateRegimeData(n, seed = 42) { const data = []; let price = 100; let rng = seed; const random = () => { rng = (rng * 9301 + 49297) % 233280; return rng / 233280; }; for (let i = 0; i < n; i++) { // Regime switching const regimePhase = i % 200; let drift = 0, volatility = 0.01; if (regimePhase < 50) { // Trending up drift = 0.002; volatility = 0.012; } else if (regimePhase < 80) { // Volatile drift = -0.001; volatility = 0.03; } else if (regimePhase < 130) { // Ranging drift = 0; volatility = 0.008; } else if (regimePhase < 180) { // Trending down drift = -0.002; volatility = 0.015; } else { // Crisis burst drift = -0.01; volatility = 0.05; } const return_ = drift + volatility * (random() + random() - 1); const open = price; price = price * (1 + return_); const high = Math.max(open, price) * (1 + random() * volatility); const low = Math.min(open, price) * (1 - random() * volatility); const volume = 1000000 * (0.5 + random() + volatility * 10); data.push({ timestamp: Date.now() - (n - i) * 60000, open, high, low, close: price, volume }); } return data; } async function main() { console.log('═'.repeat(70)); console.log('ATTENTION-BASED REGIME DETECTION'); console.log('═'.repeat(70)); console.log(); // 1. Generate data console.log('1. Market Data Generation:'); console.log('─'.repeat(70)); const data = generateRegimeData(500); console.log(` Candles generated: ${data.length}`); console.log(` Price range: $${Math.min(...data.map(d => d.low)).toFixed(2)} - $${Math.max(...data.map(d => d.high)).toFixed(2)}`); console.log(); // 2. Initialize classifier console.log('2. Attention Model Configuration:'); console.log('─'.repeat(70)); const classifier = new RegimeClassifier(attentionConfig); console.log(` Input dimension: ${attentionConfig.model.inputDim}`); console.log(` Hidden dimension: ${attentionConfig.model.hiddenDim}`); console.log(` Attention heads: ${attentionConfig.model.numHeads}`); console.log(` Sequence length: ${attentionConfig.model.sequenceLength}`); console.log(); // 3. Run analysis across data console.log('3. Rolling Regime Analysis:'); console.log('─'.repeat(70)); const results = []; const windowSize = attentionConfig.model.sequenceLength + 10; for (let i = windowSize; i < data.length; i += 20) { const window = data.slice(i - windowSize, i); const analysis = classifier.analyze(window); results.push({ index: i, price: data[i].close, ...analysis }); } console.log(` Analysis points: ${results.length}`); console.log(); // 4. Regime distribution console.log('4. Regime Distribution:'); console.log('─'.repeat(70)); const regimeCounts = {}; for (const r of results) { regimeCounts[r.regime] = (regimeCounts[r.regime] || 0) + 1; } for (const [regime, count] of Object.entries(regimeCounts).sort((a, b) => b[1] - a[1])) { const pct = (count / results.length * 100).toFixed(1); const bar = '█'.repeat(Math.floor(count / results.length * 40)); console.log(` ${regime.padEnd(15)} ${bar.padEnd(40)} ${pct}%`); } console.log(); // 5. Attention insights console.log('5. Attention Pattern Analysis:'); console.log('─'.repeat(70)); const lastResult = results[results.length - 1]; if (lastResult.attentionInsights) { console.log(` Attention interpretation: ${lastResult.attentionInsights.interpretation}`); console.log(` Average entropy: ${lastResult.attentionInsights.avgEntropy.toFixed(3)}`); console.log(); console.log(' Head-by-Head Focus:'); for (const head of lastResult.attentionInsights.importantTimesteps) { console.log(` - Head ${head.head}: focuses on ${head.focusRange} (top weight: ${head.topTimesteps[0].weight.toFixed(3)})`); } } console.log(); // 6. Regime changes console.log('6. Detected Regime Changes:'); console.log('─'.repeat(70)); const changes = results.filter(r => r.regimeChange?.changed); console.log(` Total regime changes: ${changes.length}`); console.log(); for (const change of changes.slice(-5)) { console.log(` Index ${change.index}: ${change.regimeChange.from} → ${change.regimeChange.to} (conf: ${(change.regimeChange.confidence * 100).toFixed(0)}%)`); } console.log(); // 7. Sample analysis console.log('7. Sample Analysis (Last 5 Windows):'); console.log('─'.repeat(70)); console.log(' Index │ Price │ Regime │ Confidence'); console.log('─'.repeat(70)); for (const r of results.slice(-5)) { console.log(` ${String(r.index).padStart(5)} │ $${r.price.toFixed(2).padStart(6)} │ ${r.regime.padEnd(15)} │ ${(r.confidence * 100).toFixed(0)}%`); } console.log(); // 8. Trading implications console.log('8. Trading Implications by Regime:'); console.log('─'.repeat(70)); const implications = { trending_up: 'Go long, use trailing stops, momentum strategies work', trending_down: 'Go short or stay out, mean reversion fails', ranging: 'Mean reversion works, sell options, tight stops', volatile_bull: 'Long with caution, wide stops, reduce size', volatile_bear: 'Stay defensive, hedge, reduce exposure', crisis: 'Risk-off, cash is king, volatility strategies' }; for (const [regime, implication] of Object.entries(implications)) { console.log(` ${regime}:`); console.log(` → ${implication}`); console.log(); } // 9. Attention visualization console.log('9. Attention Weights (Last Analysis):'); console.log('─'.repeat(70)); if (lastResult.attentionInsights) { console.log(' Timestep importance (Head 0, recent 20 bars):'); const head0Weights = lastResult.attentionInsights.importantTimesteps[0].topTimesteps; const maxWeight = Math.max(...head0Weights.map(t => t.weight)); // Show simplified attention bar let attentionBar = ' '; for (let i = 0; i < 20; i++) { const timestep = head0Weights.find(t => t.idx === i + 30); if (timestep && timestep.weight > 0.05) { const intensity = Math.floor(timestep.weight / maxWeight * 4); attentionBar += ['░', '▒', '▓', '█', '█'][intensity]; } else { attentionBar += '·'; } } console.log(attentionBar); console.log(' ^past recent^'); } console.log(); // 10. RuVector integration console.log('10. RuVector Vector Storage:'); console.log('─'.repeat(70)); console.log(' Attention patterns can be vectorized and stored:'); console.log(); if (lastResult.aggregatedFeatures) { const vec = lastResult.aggregatedFeatures.slice(0, 5).map(v => v.toFixed(4)); console.log(` Aggregated feature vector (first 5 dims):`); console.log(` [${vec.join(', ')}]`); console.log(); console.log(' Use cases:'); console.log(' - Find similar regime patterns via HNSW search'); console.log(' - Cluster historical regimes'); console.log(' - Regime-based strategy selection'); } console.log(); console.log('═'.repeat(70)); console.log('Attention-based regime detection completed'); console.log('═'.repeat(70)); } main().catch(console.error);