@crstrskp/graph
Version:
High-performance TypeScript graph algorithms library optimized for trading bots and arbitrage detection
205 lines • 7.22 kB
JavaScript
"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