625 lines
19 KiB
JavaScript
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);
|