Files
wifi-densepose/vendor/ruvector/examples/neural-trader/exotic/hyperbolic-embeddings.js

625 lines
19 KiB
JavaScript

/**
* 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);