UNPKG

@crstrskp/graph

Version:

High-performance TypeScript graph algorithms library optimized for trading bots and arbitrage detection

205 lines 7.22 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RealTimeGraphImpl = void 0; const Edge_1 = require("./Edge"); const Vertex_1 = require("./Vertex"); const Path_1 = require("./Path"); const ThreadSafeGraphImpl_1 = require("./ThreadSafeGraphImpl"); class RealTimeGraphImpl extends ThreadSafeGraphImpl_1.ThreadSafeGraphImpl { constructor(batchSize = 100, batchTimeoutMs = 10) { super(); this.batchTimeout = null; this.isProcessingBatch = false; this.edgeMetadata = new Map(); this.edgeIndex = new Map(); this.updateQueue = []; this.batchSize = batchSize; this.batchTimeoutMs = batchTimeoutMs; } async addMarketUpdate(update) { this.updateQueue.push(update); if (this.updateQueue.length >= this.batchSize) { await this.processBatch(); } else if (!this.batchTimeout) { this.batchTimeout = setTimeout(() => this.processBatch(), this.batchTimeoutMs); } } async addMarketUpdates(updates) { this.updateQueue.push(...updates); if (this.updateQueue.length >= this.batchSize) { await this.processBatch(); } else if (!this.batchTimeout) { this.batchTimeout = setTimeout(() => this.processBatch(), this.batchTimeoutMs); } } async processBatch() { if (this.isProcessingBatch || this.updateQueue.length === 0) { return; } this.isProcessingBatch = true; if (this.batchTimeout) { clearTimeout(this.batchTimeout); this.batchTimeout = null; } const batch = this.updateQueue.splice(0, this.batchSize); try { await this.applyUpdates(batch); } catch (error) { console.error('Error processing market updates:', error); } finally { this.isProcessingBatch = false; } // Process remaining updates if any if (this.updateQueue.length > 0) { setTimeout(() => this.processBatch(), 0); } } async applyUpdates(updates) { return this.readWriteLock.withWriteLock(async () => { for (const update of updates) { await this.applyUpdateInternal(update); } }); } async applyUpdateInternal(update) { const edgeKey = `${update.fromAsset}->${update.toAsset}`; let edge = this.edgeIndex.get(edgeKey); if (!edge) { // Create vertices if they don't exist let fromVertex = await this.getVertexByLabel(update.fromAsset); if (!fromVertex) { fromVertex = await this.insertVertex(update.fromAsset); } let toVertex = await this.getVertexByLabel(update.toAsset); if (!toVertex) { toVertex = await this.insertVertex(update.toAsset); } // Create new edge edge = await this.insertEdge(fromVertex, toVertex, update.price); this.edgeIndex.set(edgeKey, edge); } else { // Update existing edge edge.setCost(update.price); } // Update metadata this.edgeMetadata.set(edge, { lastUpdated: update.timestamp, maxAge: 30000, maxTradeSize: update.maxTradeSize, source: 'market' }); } async getArbitrageOpportunities(maxAge = 30000) { return this.readWriteLock.withReadLock(async () => { this.cleanStaleEdges(maxAge); return this.findNegativeCycles(); }); } cleanStaleEdges(maxAge) { const now = Date.now(); const staleEdges = []; this.edgeMetadata.forEach((metadata, edge) => { if (now - metadata.lastUpdated > maxAge) { staleEdges.push(edge); } }); staleEdges.forEach(edge => { this.removeEdgeInternal(edge); this.edgeMetadata.delete(edge); // Remove from index const edgeKey = `${edge.start.label}->${edge.end.label}`; this.edgeIndex.delete(edgeKey); }); } findNegativeCycles() { const cycles = []; if (this.getAllVertices().length === 0) { return cycles; } const distances = this.bellmanFordInternal(this.getAllVertices()[0]); // Check for negative cycles for (const edge of this.getAllEdges()) { const startDist = distances.get(edge.start); const endDist = distances.get(edge.end); if (startDist && endDist) { const newDist = startDist + edge.getCost(); if (newDist < endDist) { // Found negative cycle const cycle = this.buildCycleFrom(edge); if (cycle && cycle.steps.length > 0) { cycles.push(cycle); } } } } return cycles; } buildCycleFrom(edge) { const path = new Path_1.Path(); const visited = new Set(); let current = edge.start; while (current && !visited.has(this.getNodeKey(current))) { visited.add(this.getNodeKey(current)); path.addStep(current); current = current.getPrev(); } return path.steps.length > 0 ? path : null; } getNodeKey(node) { if (node instanceof Vertex_1.Vertex) { return `v:${node.label}`; } else { return `e:${node.start.label}->${node.end.label}`; } } async getOptimalPath(from, to, maxAge = 30000) { return this.readWriteLock.withReadLock(async () => { this.cleanStaleEdges(maxAge); const fromVertex = await this.getVertexByLabel(from); const toVertex = await this.getVertexByLabel(to); if (!fromVertex || !toVertex) { return null; } return this.dijkstraInternal(fromVertex, toVertex); }); } async getEdgeMetadata(edge) { return this.edgeMetadata.get(edge); } async validateTradeSize(path, tradeSize) { for (const step of path.steps) { if (step instanceof Edge_1.Edge) { const metadata = this.edgeMetadata.get(step); if (metadata?.maxTradeSize && tradeSize > metadata.maxTradeSize) { return false; } } } return true; } getStatistics() { return { totalVertices: this.getAllVertices().length, totalEdges: this.getAllEdges().length, queueSize: this.updateQueue.length, isProcessing: this.isProcessingBatch }; } async destroy() { if (this.batchTimeout) { clearTimeout(this.batchTimeout); this.batchTimeout = null; } this.updateQueue = []; this.edgeMetadata.clear(); this.edgeIndex.clear(); } } exports.RealTimeGraphImpl = RealTimeGraphImpl; //# sourceMappingURL=RealTimeGraphImpl.js.map