diff --git a/src/dlob-subscriber/DLOBSubscriberIO.ts b/src/dlob-subscriber/DLOBSubscriberIO.ts index 0287d78..455da7c 100644 --- a/src/dlob-subscriber/DLOBSubscriberIO.ts +++ b/src/dlob-subscriber/DLOBSubscriberIO.ts @@ -24,13 +24,13 @@ import { SubscriberLookup, addMarketSlotToResponse, addOracletoResponse, - aggregatePrices, l2WithBNToStrings, parsePositiveIntArray, + publishGroupings, } from '../utils/utils'; import { setHealthStatus, HEALTH_STATUS } from '../core/metrics'; -type wsMarketArgs = { +export type wsMarketArgs = { marketIndex: number; marketType: MarketType; marketName: string; @@ -39,12 +39,11 @@ type wsMarketArgs = { numVammOrders?: number; fallbackL2Generators?: L2OrderBookGenerator[]; updateOnChange?: boolean; - tickSize?: BN + tickSize?: BN; }; require('dotenv').config(); -export const GROUPING_OPTIONS = [1, 10, 100, 500, 1000]; const PERP_MAKRET_STALENESS_THRESHOLD = 30 * 60 * 1000; const SPOT_MAKRET_STALENESS_THRESHOLD = 60 * 60 * 1000; const STALE_ORACLE_REMOVE_VAMM_THRESHOLD = 160; @@ -120,7 +119,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber { includeVamm, updateOnChange: false, fallbackL2Generators: [], - tickSize: perpMarket?.amm?.orderTickSize ?? ONE + tickSize: perpMarket?.amm?.orderTickSize ?? ONE, }); } for (const market of config.spotMarketInfos) { @@ -135,7 +134,8 @@ export class DLOBSubscriberIO extends DLOBSubscriber { config.spotMarketSubscribers[market.marketIndex].phoenix, config.spotMarketSubscribers[market.marketIndex].openbook, ].filter((a) => !!a), - tickSize: config.spotMarketSubscribers[market.marketIndex].tickSize ?? ONE + tickSize: + config.spotMarketSubscribers[market.marketIndex].tickSize ?? ONE, }); } } @@ -399,30 +399,15 @@ export class DLOBSubscriberIO extends DLOBSubscriber { }`, l2Formatted_depth100 ); - - GROUPING_OPTIONS.forEach(group => { - const pricePrecision = BigNum.from(group).mul(marketArgs.tickSize).toNum() - const aggregatedBids = aggregatePrices(l2Formatted.bids, 'bid', pricePrecision) - .sort((a, b) => b[0] - a[0]) - .slice(0, 20) - - const aggregatedAsks = aggregatePrices(l2Formatted.asks, 'ask', pricePrecision) - .sort((a, b) => a[0] - b[0]) - .slice(0, 20) - - const l2Formatted_grouped20 = Object.assign({}, l2Formatted, { - bids: aggregatedBids, - asks: aggregatedAsks, - }); - - this.redisClient.publish( - `${clientPrefix}orderbook_${marketType}_${marketArgs.marketIndex}_grouped_${group}${ - this.indicativeQuotesRedisClient ? '_indicative' : '' - }`, - l2Formatted_grouped20 - ); - }) + publishGroupings( + l2Formatted, + marketArgs, + this.redisClient, + clientPrefix, + marketType, + this.indicativeQuotesRedisClient + ); if (!this.indicativeQuotesRedisClient) { const bids = this.dlob diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b80257b..1257e95 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,8 +1,8 @@ import { BN, + BigNum, DriftClient, DriftEnv, - L2Level, L2OrderBook, L3OrderBook, MarketType, @@ -21,6 +21,16 @@ import { logger } from './logger'; import { NextFunction, Request, Response } from 'express'; import FEATURE_FLAGS from './featureFlags'; import { Connection } from '@solana/web3.js'; +import { wsMarketArgs } from 'src/dlob-subscriber/DLOBSubscriberIO'; + +export const GROUPING_OPTIONS = [1, 10, 100, 500, 1000]; +export const GROUPING_DEPENDENCIES = { + 1: null, + 10: 1, + 100: 10, + 500: 100, + 1000: 100, +}; export const l2WithBNToStrings = (l2: L2OrderBook): any => { for (const key of Object.keys(l2)) { @@ -181,7 +191,7 @@ export function aggregatePrices(entries, side, pricePrecision) { const price = parseFloat(entry.price); const data = { size: parseFloat(entry.size), - sources: entry.sources || {} + sources: entry.sources || {}, }; let bucketPrice, displayPrice; @@ -207,19 +217,85 @@ export function aggregatePrices(entries, side, pricePrecision) { bucketData.size += data.size; if (data.sources) { - Object.entries(data.sources).forEach(([sourceKey, sourceSize]: [string, string]) => { - if (!bucketData.sources[sourceKey]) { - bucketData.sources[sourceKey] = 0; + Object.entries(data.sources).forEach( + ([sourceKey, sourceSize]: [string, string]) => { + if (!bucketData.sources[sourceKey]) { + bucketData.sources[sourceKey] = 0; + } + bucketData.sources[sourceKey] += parseFloat(sourceSize); } - bucketData.sources[sourceKey] += parseFloat(sourceSize); - }); + ); } - }); return Array.from(result.values()); } +export function publishGroupings( + l2Formatted, + marketArgs: wsMarketArgs, + redisClient: RedisClient, + clientPrefix: string, + marketType: string, + indicativeQuotesRedisClient: RedisClient +) { + const groupingResults = new Map(); + + GROUPING_OPTIONS.forEach((group) => { + const pricePrecision = BigNum.from(group).mul(marketArgs.tickSize).toNum(); + const dependency = GROUPING_DEPENDENCIES[group]; + + let fullAggregatedBids, fullAggregatedAsks; + + if (dependency && groupingResults.has(dependency)) { + const previousResults = groupingResults.get(dependency); + + fullAggregatedBids = aggregatePrices( + previousResults.bids, + 'bid', + pricePrecision + ).sort((a, b) => b[0] - a[0]); + + fullAggregatedAsks = aggregatePrices( + previousResults.asks, + 'ask', + pricePrecision + ).sort((a, b) => a[0] - b[0]); + } else { + fullAggregatedBids = aggregatePrices( + l2Formatted.bids, + 'bid', + pricePrecision + ).sort((a, b) => b[0] - a[0]); + + fullAggregatedAsks = aggregatePrices( + l2Formatted.asks, + 'ask', + pricePrecision + ).sort((a, b) => a[0] - b[0]); + } + + groupingResults.set(group, { + bids: fullAggregatedBids, + asks: fullAggregatedAsks, + }); + + const aggregatedBids = fullAggregatedBids.slice(0, 20); + const aggregatedAsks = fullAggregatedAsks.slice(0, 20); + const l2Formatted_grouped20 = Object.assign({}, l2Formatted, { + bids: aggregatedBids, + asks: aggregatedAsks, + }); + + redisClient.publish( + `${clientPrefix}orderbook_${marketType}_${ + marketArgs.marketIndex + }_grouped_${group}${indicativeQuotesRedisClient ? '_indicative' : ''}`, + l2Formatted_grouped20 + ); + }); +} + /** * Takes in a req.query like: `{ * marketName: 'SOL-PERP,BTC-PERP,ETH-PERP', diff --git a/src/wsConnectionManager.ts b/src/wsConnectionManager.ts index 9fcb521..f37b7a9 100644 --- a/src/wsConnectionManager.ts +++ b/src/wsConnectionManager.ts @@ -3,11 +3,10 @@ import express from 'express'; import * as http from 'http'; import compression from 'compression'; import { WebSocket, WebSocketServer } from 'ws'; -import { sleep, selectMostRecentBySlot } from './utils/utils'; +import { sleep, selectMostRecentBySlot, GROUPING_OPTIONS } from './utils/utils'; import { register, Gauge, Counter } from 'prom-client'; import { DriftEnv, PerpMarkets, SpotMarkets } from '@drift-labs/sdk'; import { RedisClient, RedisClientPrefix } from '@drift/common/clients'; -import { GROUPING_OPTIONS } from './dlob-subscriber/DLOBSubscriberIO'; // Set up env constants require('dotenv').config();