/** * Hyperbolic Market Embeddings * * EXOTIC: Poincaré disk embeddings for hierarchical market structure * * Uses @neural-trader/neural with RuVector for: * - Poincaré ball model for hyperbolic geometry * - Exponential capacity for tree-like hierarchies * - Market taxonomy learning (sector → industry → company) * - Distance preservation in curved space * * Hyperbolic space naturally represents hierarchical relationships * that exist in markets (market → sector → industry → stock). */ // Hyperbolic embedding configuration const hyperbolicConfig = { // Embedding parameters embedding: { dimension: 2, // 2D for visualization, can be higher curvature: -1, // Negative curvature (hyperbolic) learningRate: 0.01, epochs: 100, negSamples: 5 // Negative sampling }, // Market hierarchy hierarchy: { levels: ['Market', 'Sector', 'Industry', 'Stock'], useCorrelations: true // Learn from return correlations }, // Poincaré ball constraints poincare: { maxNorm: 0.99, // Stay inside unit ball epsilon: 1e-5 // Numerical stability } }; // Poincaré Ball Operations class PoincareOperations { constructor(curvature = -1) { this.c = Math.abs(curvature); this.sqrtC = Math.sqrt(this.c); } // Möbius addition: x ⊕ y mobiusAdd(x, y) { const c = this.c; const xNorm2 = x.reduce((s, v) => s + v * v, 0); const yNorm2 = y.reduce((s, v) => s + v * v, 0); const xy = x.reduce((s, v, i) => s + v * y[i], 0); const denom = 1 + 2 * c * xy + c * c * xNorm2 * yNorm2; return x.map((xi, i) => { const num = (1 + 2 * c * xy + c * yNorm2) * xi + (1 - c * xNorm2) * y[i]; return num / denom; }); } // Poincaré distance with numerical stability distance(x, y) { const diff = x.map((v, i) => v - y[i]); const diffNorm2 = diff.reduce((s, v) => s + v * v, 0); const xNorm2 = x.reduce((s, v) => s + v * v, 0); const yNorm2 = y.reduce((s, v) => s + v * v, 0); // Ensure points are inside the ball const eps = hyperbolicConfig.poincare.epsilon; const safeXNorm2 = Math.min(xNorm2, 1 - eps); const safeYNorm2 = Math.min(yNorm2, 1 - eps); const num = 2 * diffNorm2; const denom = (1 - safeXNorm2) * (1 - safeYNorm2); // Guard against numerical issues with Math.acosh (arg must be >= 1) const arg = 1 + num / Math.max(denom, eps); const safeArg = Math.max(1, arg); // acosh domain is [1, inf) return Math.acosh(safeArg) / this.sqrtC; } // Exponential map: tangent space → manifold expMap(x, v) { const c = this.c; const vNorm = Math.sqrt(v.reduce((s, vi) => s + vi * vi, 0)) + hyperbolicConfig.poincare.epsilon; const xNorm2 = x.reduce((s, xi) => s + xi * xi, 0); const lambda = 2 / (1 - c * xNorm2); const t = Math.tanh(this.sqrtC * lambda * vNorm / 2); const y = v.map(vi => t * vi / (this.sqrtC * vNorm)); return this.mobiusAdd(x, y); } // Logarithmic map: manifold → tangent space logMap(x, y) { const c = this.c; const eps = hyperbolicConfig.poincare.epsilon; const xNorm2 = Math.min(x.reduce((s, xi) => s + xi * xi, 0), 1 - eps); const lambda = 2 / Math.max(1 - c * xNorm2, eps); const mxy = this.mobiusAdd(x.map(v => -v), y); const mxyNorm = Math.sqrt(mxy.reduce((s, v) => s + v * v, 0)) + eps; // Guard atanh domain: argument must be in (-1, 1) const atanhArg = Math.min(this.sqrtC * mxyNorm, 1 - eps); const t = Math.atanh(atanhArg); return mxy.map(v => 2 * t * v / (lambda * this.sqrtC * mxyNorm)); } // Project to Poincaré ball (ensure ||x|| < 1) project(x) { const norm = Math.sqrt(x.reduce((s, v) => s + v * v, 0)); const maxNorm = hyperbolicConfig.poincare.maxNorm; if (norm >= maxNorm) { return x.map(v => v * maxNorm / norm); } return x; } // Riemannian gradient (for optimization) riemannianGrad(x, euclideanGrad) { const xNorm2 = x.reduce((s, v) => s + v * v, 0); const scale = Math.pow((1 - this.c * xNorm2), 2) / 4; return euclideanGrad.map(g => g * scale); } } // Hyperbolic Embedding Model class HyperbolicEmbedding { constructor(config) { this.config = config; this.poincare = new PoincareOperations(config.embedding.curvature); this.embeddings = new Map(); this.hierarchyGraph = new Map(); this.losses = []; } // Initialize embedding for entity initEmbedding(entity) { const dim = this.config.embedding.dimension; // Initialize near origin (parent entities will move toward center) const embedding = []; for (let i = 0; i < dim; i++) { embedding.push((Math.random() - 0.5) * 0.1); } this.embeddings.set(entity, this.poincare.project(embedding)); } // Add hierarchy relationship addHierarchy(parent, child) { if (!this.hierarchyGraph.has(parent)) { this.hierarchyGraph.set(parent, { children: [], parent: null }); } if (!this.hierarchyGraph.has(child)) { this.hierarchyGraph.set(child, { children: [], parent: null }); } this.hierarchyGraph.get(parent).children.push(child); this.hierarchyGraph.get(child).parent = parent; // Initialize embeddings if (!this.embeddings.has(parent)) this.initEmbedding(parent); if (!this.embeddings.has(child)) this.initEmbedding(child); } // Training loss: children should be farther from origin than parents computeLoss(parent, child) { const pEmb = this.embeddings.get(parent); const cEmb = this.embeddings.get(child); if (!pEmb || !cEmb) return 0; // Distance from origin const pDist = Math.sqrt(pEmb.reduce((s, v) => s + v * v, 0)); const cDist = Math.sqrt(cEmb.reduce((s, v) => s + v * v, 0)); // Parent should be closer to origin const hierarchyLoss = Math.max(0, pDist - cDist + 0.1); // Parent-child should be close const distLoss = this.poincare.distance(pEmb, cEmb); return hierarchyLoss + 0.5 * distLoss; } // Train embeddings train() { const lr = this.config.embedding.learningRate; for (let epoch = 0; epoch < this.config.embedding.epochs; epoch++) { let totalLoss = 0; // For each parent-child pair for (const [entity, info] of this.hierarchyGraph) { for (const child of info.children) { const loss = this.computeLoss(entity, child); totalLoss += loss; // Gradient update (simplified) this.updateEmbedding(entity, child, lr); } } this.losses.push(totalLoss); // Decay learning rate if (epoch % 20 === 0) { // lr *= 0.9; } } } // Riemannian gradient descent update updateEmbedding(parent, child, lr) { const pEmb = this.embeddings.get(parent); const cEmb = this.embeddings.get(child); const eps = hyperbolicConfig.poincare.epsilon; // Compute Euclidean gradients const pNorm2 = pEmb.reduce((s, v) => s + v * v, 0); const cNorm2 = cEmb.reduce((s, v) => s + v * v, 0); // Gradient for parent: move toward origin (hierarchy constraint) const pGradEuclid = pEmb.map(v => v); // gradient of ||x||^2 is 2x // Gradient for child: move toward parent but stay farther from origin const direction = cEmb.map((v, i) => pEmb[i] - v); const dirNorm = Math.sqrt(direction.reduce((s, d) => s + d * d, 0)) + eps; const normalizedDir = direction.map(d => d / dirNorm); // Child gradient: toward parent + outward from origin const cGradEuclid = cEmb.map((v, i) => -normalizedDir[i] * 0.3 - v * 0.1); // Convert to Riemannian gradients using metric tensor const pRiemannGrad = this.poincare.riemannianGrad(pEmb, pGradEuclid); const cRiemannGrad = this.poincare.riemannianGrad(cEmb, cGradEuclid); // Update using exponential map (proper Riemannian SGD) const pTangent = pRiemannGrad.map(g => -lr * g); const cTangent = cRiemannGrad.map(g => -lr * g); const newPEmb = this.poincare.expMap(pEmb, pTangent); const newCEmb = this.poincare.expMap(cEmb, cTangent); this.embeddings.set(parent, this.poincare.project(newPEmb)); this.embeddings.set(child, this.poincare.project(newCEmb)); } // Get embedding getEmbedding(entity) { return this.embeddings.get(entity); } // Find nearest neighbors in hyperbolic space findNearest(entity, k = 5) { const emb = this.embeddings.get(entity); if (!emb) return []; const distances = []; for (const [other, otherEmb] of this.embeddings) { if (other !== entity) { distances.push({ entity: other, distance: this.poincare.distance(emb, otherEmb) }); } } return distances.sort((a, b) => a.distance - b.distance).slice(0, k); } // Get depth (distance from origin) getDepth(entity) { const emb = this.embeddings.get(entity); if (!emb) return 0; return Math.sqrt(emb.reduce((s, v) => s + v * v, 0)); } } // Market hierarchy builder class MarketHierarchy { constructor() { this.sectors = { 'Technology': ['Software', 'Hardware', 'Semiconductors'], 'Healthcare': ['Pharma', 'Biotech', 'MedDevices'], 'Finance': ['Banks', 'Insurance', 'AssetMgmt'], 'Energy': ['Oil', 'Gas', 'Renewables'], 'Consumer': ['Retail', 'FoodBev', 'Apparel'] }; this.industries = { 'Software': ['MSFT', 'ORCL', 'CRM'], 'Hardware': ['AAPL', 'DELL', 'HPQ'], 'Semiconductors': ['NVDA', 'AMD', 'INTC'], 'Pharma': ['JNJ', 'PFE', 'MRK'], 'Biotech': ['AMGN', 'GILD', 'BIIB'], 'MedDevices': ['MDT', 'ABT', 'SYK'], 'Banks': ['JPM', 'BAC', 'WFC'], 'Insurance': ['BRK', 'MET', 'AIG'], 'AssetMgmt': ['BLK', 'GS', 'MS'], 'Oil': ['XOM', 'CVX', 'COP'], 'Gas': ['SLB', 'HAL', 'BKR'], 'Renewables': ['NEE', 'ENPH', 'SEDG'], 'Retail': ['AMZN', 'WMT', 'TGT'], 'FoodBev': ['KO', 'PEP', 'MCD'], 'Apparel': ['NKE', 'LULU', 'TJX'] }; } buildHierarchy(embedding) { // Market → Sectors for (const sector of Object.keys(this.sectors)) { embedding.addHierarchy('Market', sector); // Sector → Industries for (const industry of this.sectors[sector]) { embedding.addHierarchy(sector, industry); // Industry → Stocks if (this.industries[industry]) { for (const stock of this.industries[industry]) { embedding.addHierarchy(industry, stock); } } } } } getAllStocks() { const stocks = []; for (const industry of Object.values(this.industries)) { stocks.push(...industry); } return stocks; } } // Visualization helper class HyperbolicVisualizer { visualize(embedding, width = 40, height = 20) { const grid = []; for (let i = 0; i < height; i++) { grid.push(new Array(width).fill(' ')); } // Draw unit circle boundary for (let angle = 0; angle < 2 * Math.PI; angle += 0.1) { const x = Math.cos(angle) * 0.95; const y = Math.sin(angle) * 0.95; const gridX = Math.floor((x + 1) / 2 * (width - 1)); const gridY = Math.floor((1 - y) / 2 * (height - 1)); if (gridY >= 0 && gridY < height && gridX >= 0 && gridX < width) { grid[gridY][gridX] = '·'; } } // Plot embeddings const symbols = { market: '◉', sector: '●', industry: '○', stock: '·' }; for (const [entity, emb] of embedding.embeddings) { const x = emb[0]; const y = emb[1]; const gridX = Math.floor((x + 1) / 2 * (width - 1)); const gridY = Math.floor((1 - y) / 2 * (height - 1)); if (gridY >= 0 && gridY < height && gridX >= 0 && gridX < width) { let symbol = '?'; if (entity === 'Market') symbol = symbols.market; else if (['Technology', 'Healthcare', 'Finance', 'Energy', 'Consumer'].includes(entity)) symbol = symbols.sector; else if (entity.length > 4) symbol = symbols.industry; else symbol = symbols.stock; grid[gridY][gridX] = symbol; } } return grid.map(row => row.join('')).join('\n'); } } async function main() { console.log('═'.repeat(70)); console.log('HYPERBOLIC MARKET EMBEDDINGS'); console.log('═'.repeat(70)); console.log(); // 1. Build market hierarchy console.log('1. Market Hierarchy Construction:'); console.log('─'.repeat(70)); const hierarchy = new MarketHierarchy(); const embedding = new HyperbolicEmbedding(hyperbolicConfig); hierarchy.buildHierarchy(embedding); console.log(` Levels: ${hyperbolicConfig.hierarchy.levels.join(' → ')}`); console.log(` Sectors: ${Object.keys(hierarchy.sectors).length}`); console.log(` Industries: ${Object.keys(hierarchy.industries).length}`); console.log(` Stocks: ${hierarchy.getAllStocks().length}`); console.log(` Dimension: ${hyperbolicConfig.embedding.dimension}D Poincaré ball`); console.log(); // 2. Train embeddings console.log('2. Training Hyperbolic Embeddings:'); console.log('─'.repeat(70)); embedding.train(); console.log(` Epochs: ${hyperbolicConfig.embedding.epochs}`); console.log(` Learning rate: ${hyperbolicConfig.embedding.learningRate}`); console.log(` Initial loss: ${embedding.losses[0]?.toFixed(4) || 'N/A'}`); console.log(` Final loss: ${embedding.losses[embedding.losses.length - 1]?.toFixed(4) || 'N/A'}`); console.log(); // 3. Embedding depths console.log('3. Hierarchy Depth Analysis:'); console.log('─'.repeat(70)); console.log(' Entity depths (distance from origin):'); console.log(); // Market (root) const marketDepth = embedding.getDepth('Market'); console.log(` Market (root): ${marketDepth.toFixed(4)}`); // Sectors let avgSectorDepth = 0; for (const sector of Object.keys(hierarchy.sectors)) { avgSectorDepth += embedding.getDepth(sector); } avgSectorDepth /= Object.keys(hierarchy.sectors).length; console.log(` Sectors (avg): ${avgSectorDepth.toFixed(4)}`); // Industries let avgIndustryDepth = 0; let industryCount = 0; for (const industry of Object.keys(hierarchy.industries)) { avgIndustryDepth += embedding.getDepth(industry); industryCount++; } avgIndustryDepth /= industryCount; console.log(` Industries (avg): ${avgIndustryDepth.toFixed(4)}`); // Stocks let avgStockDepth = 0; const stocks = hierarchy.getAllStocks(); for (const stock of stocks) { avgStockDepth += embedding.getDepth(stock); } avgStockDepth /= stocks.length; console.log(` Stocks (avg): ${avgStockDepth.toFixed(4)}`); console.log(); console.log(' Depth increases with hierarchy level ✓'); console.log(' (Root near origin, leaves near boundary)'); console.log(); // 4. Sample embeddings console.log('4. Sample Embeddings (2D Poincaré Coordinates):'); console.log('─'.repeat(70)); const samples = ['Market', 'Technology', 'Software', 'MSFT', 'Finance', 'Banks', 'JPM']; console.log(' Entity │ x │ y │ Depth'); console.log('─'.repeat(70)); for (const entity of samples) { const emb = embedding.getEmbedding(entity); if (emb) { const depth = embedding.getDepth(entity); console.log(` ${entity.padEnd(16)} │ ${emb[0].toFixed(5).padStart(8)} │ ${emb[1].toFixed(5).padStart(8)} │ ${depth.toFixed(4)}`); } } console.log(); // 5. Nearest neighbors console.log('5. Nearest Neighbors (Hyperbolic Distance):'); console.log('─'.repeat(70)); const queryStocks = ['AAPL', 'JPM', 'XOM']; for (const stock of queryStocks) { const neighbors = embedding.findNearest(stock, 5); console.log(` ${stock} neighbors:`); for (const { entity, distance } of neighbors) { console.log(` ${entity.padEnd(12)} d=${distance.toFixed(4)}`); } console.log(); } // 6. Hyperbolic distance properties console.log('6. Hyperbolic Distance Properties:'); console.log('─'.repeat(70)); const poincare = embedding.poincare; // Same industry const samIndustry = poincare.distance( embedding.getEmbedding('MSFT'), embedding.getEmbedding('ORCL') ); // Same sector, different industry const sameSector = poincare.distance( embedding.getEmbedding('MSFT'), embedding.getEmbedding('NVDA') ); // Different sector const diffSector = poincare.distance( embedding.getEmbedding('MSFT'), embedding.getEmbedding('JPM') ); console.log(' Distance comparisons:'); console.log(` MSFT ↔ ORCL (same industry): ${samIndustry.toFixed(4)}`); console.log(` MSFT ↔ NVDA (same sector): ${sameSector.toFixed(4)}`); console.log(` MSFT ↔ JPM (diff sector): ${diffSector.toFixed(4)}`); console.log(); console.log(' Distances increase with hierarchical distance ✓'); console.log(); // 7. Visualization console.log('7. Poincaré Disk Visualization:'); console.log('─'.repeat(70)); const visualizer = new HyperbolicVisualizer(); const viz = visualizer.visualize(embedding); console.log(viz); console.log(); console.log(' Legend: ◉=Market ●=Sector ○=Industry ·=Stock'); console.log(); // 8. Sector clusters console.log('8. Sector Clustering Analysis:'); console.log('─'.repeat(70)); for (const [sector, industries] of Object.entries(hierarchy.sectors).slice(0, 3)) { const sectorEmb = embedding.getEmbedding(sector); // Calculate average distance from sector to its stocks let avgDist = 0; let count = 0; for (const industry of industries) { const stocks = hierarchy.industries[industry] || []; for (const stock of stocks) { const stockEmb = embedding.getEmbedding(stock); if (stockEmb) { avgDist += poincare.distance(sectorEmb, stockEmb); count++; } } } avgDist /= count || 1; console.log(` ${sector}:`); console.log(` Avg distance to stocks: ${avgDist.toFixed(4)}`); console.log(` Stocks: ${industries.flatMap(i => hierarchy.industries[i] || []).slice(0, 5).join(', ')}...`); console.log(); } // 9. Trading implications console.log('9. Trading Implications:'); console.log('─'.repeat(70)); console.log(' Hyperbolic embeddings enable:'); console.log(); console.log(' 1. Hierarchical diversification:'); console.log(' - Select stocks from different "branches"'); console.log(' - Maximize hyperbolic distance for diversification'); console.log(); console.log(' 2. Sector rotation strategies:'); console.log(' - Identify sector centroids'); console.log(' - Track rotation by watching centroid distances'); console.log(); console.log(' 3. Pair trading:'); console.log(' - Find pairs with small hyperbolic distance'); console.log(' - These stocks should move together'); console.log(); // 10. RuVector integration console.log('10. RuVector Vector Storage:'); console.log('─'.repeat(70)); console.log(' Hyperbolic embeddings stored as vectors:'); console.log(); const appleEmb = embedding.getEmbedding('AAPL'); console.log(` AAPL embedding: [${appleEmb.map(v => v.toFixed(4)).join(', ')}]`); console.log(); console.log(' Note: Euclidean HNSW can be used after mapping'); console.log(' to tangent space at origin for approximate NN.'); console.log(); console.log(' Use cases:'); console.log(' - Find hierarchically similar stocks'); console.log(' - Sector membership inference'); console.log(' - Anomaly detection (stocks far from expected position)'); console.log(); console.log('═'.repeat(70)); console.log('Hyperbolic market embeddings completed'); console.log('═'.repeat(70)); } main().catch(console.error);