dlob add fetch indicative quotes (#372)

* dlob add fetch indicative quotes

* indicative ws stream

* prettify

* allow for oracle orders
This commit is contained in:
moosecat
2025-04-16 12:06:21 -07:00
committed by GitHub
parent 6d1a11f805
commit b3b655dc65
6 changed files with 405 additions and 260 deletions

View File

@@ -1,11 +1,17 @@
import {
BN,
DLOBSubscriber,
DLOBSubscriptionConfig,
DriftEnv,
L2OrderBookGenerator,
MarketType,
Order,
OrderStatus,
OrderTriggerCondition,
OrderType,
PerpOperation,
PositionDirection,
ZERO,
isOperationPaused,
isVariant,
} from '@drift-labs/sdk';
@@ -37,6 +43,8 @@ const PERP_MAKRET_STALENESS_THRESHOLD = 30 * 60 * 1000;
const SPOT_MAKRET_STALENESS_THRESHOLD = 60 * 60 * 1000;
const STALE_ORACLE_REMOVE_VAMM_THRESHOLD = 160;
const INDICATIVE_QUOTES_PUBKEY = 'inDNdu3ML4vG5LNExqcwuCQtLcCU8KfK5YM2qYV3JJz';
const PERP_MARKETS_TO_SKIP_SLOT_CHECK =
process.env.PERP_MARKETS_TO_SKIP_SLOT_CHECK !== undefined
? parsePositiveIntArray(process.env.PERP_MARKETS_TO_SKIP_SLOT_CHECK)
@@ -55,6 +63,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
public marketArgs: wsMarketArgs[] = [];
public lastSeenL2Formatted: Map<MarketType, Map<number, any>>;
redisClient: RedisClient;
indicativeQuotesRedisClient?: RedisClient;
public killSwitchSlotDiffThreshold: number;
public lastMarketSlotMap: Map<
MarketType,
@@ -65,6 +74,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
config: DLOBSubscriptionConfig & {
env: DriftEnv;
redisClient: RedisClient;
indicativeQuotesRedisClient?: RedisClient;
perpMarketInfos: wsMarketInfo[];
spotMarketInfos: wsMarketInfo[];
spotMarketSubscribers: SubscriberLookup;
@@ -73,6 +83,8 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
) {
super(config);
this.redisClient = config.redisClient;
this.indicativeQuotesRedisClient = config.indicativeQuotesRedisClient;
this.killSwitchSlotDiffThreshold =
config.killSwitchSlotDiffThreshold || 200;
@@ -124,6 +136,105 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
await super.updateDLOB();
for (const marketArgs of this.marketArgs) {
try {
if (this.indicativeQuotesRedisClient) {
const marketType = isVariant(marketArgs.marketType, 'perp')
? 'perp'
: 'spot';
const mms = await this.indicativeQuotesRedisClient.smembers(
`market_mms_${marketType}_${marketArgs.marketIndex}`
);
const mmQuotes = await Promise.all(
mms.map((mm) => {
return this.indicativeQuotesRedisClient.get(
`mm_quotes_${marketType}_${marketArgs.marketIndex}_${mm}`
);
})
);
const nowMinus1000Ms = Date.now() - 1000;
mmQuotes.forEach((quote) => {
if (Number(quote['ts']) > nowMinus1000Ms) {
const indicativeBaseOrder: Order = {
status: OrderStatus.OPEN,
orderType: OrderType.LIMIT,
orderId: 0,
slot: new BN(this.slotSource.getSlot()),
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
baseAssetAmount: ZERO,
immediateOrCancel: false,
direction: PositionDirection.LONG,
oraclePriceOffset: 0,
maxTs: new BN(quote['ts'] + 1000),
reduceOnly: false,
triggerCondition: OrderTriggerCondition.ABOVE,
price: ZERO,
userOrderId: 0,
postOnly: true,
auctionDuration: 0,
auctionStartPrice: ZERO,
auctionEndPrice: ZERO,
// Rest are not necessary and set for type conforming
existingPositionDirection: PositionDirection.LONG,
triggerPrice: ZERO,
baseAssetAmountFilled: ZERO,
quoteAssetAmountFilled: ZERO,
quoteAssetAmount: ZERO,
bitFlags: 0,
postedSlotTail: 0,
};
if (quote['bid_size'] && quote['bid_price']) {
// Sanity check bid price and size
const indicativeBid: Order = Object.assign(
{},
indicativeBaseOrder,
{
oraclePriceOffset: quote['is_oracle_offset']
? quote['bid_price']
: 0,
price: quote['is_oracle_offset']
? 0
: new BN(quote['bid_price']),
baseAssetAmount: new BN(quote['bid_size']),
direction: PositionDirection.LONG,
}
);
this.dlob.insertOrder(
indicativeBid,
INDICATIVE_QUOTES_PUBKEY,
this.slotSource.getSlot(),
false
);
}
if (quote['ask_size'] && quote['ask_price']) {
const indicativeAsk: Order = Object.assign(
{},
indicativeBaseOrder,
{
oraclePriceOffset: quote['is_oracle_offset']
? quote['ask_price']
: 0,
price: quote['is_oracle_offset']
? 0
: new BN(quote['ask_price']),
baseAssetAmount: new BN(quote['ask_size']),
direction: PositionDirection.SHORT,
}
);
this.dlob.insertOrder(
indicativeAsk,
INDICATIVE_QUOTES_PUBKEY,
this.slotSource.getSlot(),
false
);
}
}
});
}
this.getL2AndSendMsg(marketArgs);
this.getL3AndSendMsg(marketArgs);
} catch (error) {
@@ -162,6 +273,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
logger.info('Oracle is stale, removing vamm orders');
includeVamm = false;
}
const l2 = this.getL2({ ...l2FuncArgs, includeVamm });
const slot = l2.slot;
const lastMarketSlotAndTime = this.lastMarketSlotMap
@@ -264,44 +376,43 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
this.redisClient.publish(
`${clientPrefix}orderbook_${marketType}_${marketArgs.marketIndex}${
this.protectedMakerView ? '_pmm' : ''
this.indicativeQuotesRedisClient ? '_indicative' : ''
}`,
l2Formatted
);
this.redisClient.set(
`last_update_orderbook_${marketType}_${marketArgs.marketIndex}${
this.protectedMakerView ? '_pmm' : ''
this.indicativeQuotesRedisClient ? '_indicative' : ''
}`,
l2Formatted_depth100
);
const oraclePriceData =
marketType === 'spot'
? this.driftClient.getOracleDataForSpotMarket(marketArgs.marketIndex)
: this.driftClient.getOracleDataForPerpMarket(marketArgs.marketIndex);
const bids = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.LONG,
slot: slot,
oraclePriceData,
numMakers: 4,
})
.map((x) => x.toString());
const asks = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.SHORT,
slot,
oraclePriceData,
numMakers: 4,
})
.map((x) => x.toString());
this.redisClient.set(
`last_update_orderbook_best_makers_${marketType}_${marketArgs.marketIndex}`,
{ bids, asks, slot }
);
if (!this.indicativeQuotesRedisClient) {
const bids = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.LONG,
slot: slot,
oraclePriceData: oracleData,
numMakers: 4,
})
.map((x) => x.toString());
const asks = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.SHORT,
slot,
oraclePriceData: oracleData,
numMakers: 4,
})
.map((x) => x.toString());
this.redisClient.set(
`last_update_orderbook_best_makers_${marketType}_${marketArgs.marketIndex}`,
{ bids, asks, slot }
);
}
}
getL3AndSendMsg(marketArgs: wsMarketArgs): void {
@@ -355,7 +466,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
this.redisClient.set(
`last_update_orderbook_l3_${marketType}_${marketArgs.marketIndex}${
this.protectedMakerView ? '_pmm' : ''
this.indicativeQuotesRedisClient ? '_indicative' : ''
}`,
l3
);

View File

@@ -1,9 +1,18 @@
import { DLOB, OrderSubscriber, UserAccount, UserMap } from '@drift-labs/sdk';
import {
DLOB,
OrderSubscriber,
ProtectMakerParamsMap,
UserAccount,
UserMap,
} from '@drift-labs/sdk';
import { PublicKey } from '@solana/web3.js';
export type DLOBProvider = {
subscribe(): Promise<void>;
getDLOB(slot: number, protectedMakerView: boolean): Promise<DLOB>;
getDLOB(
slot: number,
protectedMakerParamsMap?: ProtectMakerParamsMap
): Promise<DLOB>;
getUniqueAuthorities(): PublicKey[];
getUserAccounts(): Generator<{
userAccount: UserAccount;
@@ -20,8 +29,11 @@ export function getDLOBProviderFromUserMap(userMap: UserMap): DLOBProvider {
subscribe: async () => {
await userMap.subscribe();
},
getDLOB: async (slot: number, protectedMakerView: boolean) => {
return await userMap.getDLOB(slot, protectedMakerView);
getDLOB: async (
slot: number,
protectedMakerParamsMap?: ProtectMakerParamsMap
) => {
return await userMap.getDLOB(slot, protectedMakerParamsMap);
},
getUniqueAuthorities: () => {
return userMap.getUniqueAuthorities();
@@ -56,8 +68,11 @@ export function getDLOBProviderFromOrderSubscriber(
subscribe: async () => {
await orderSubscriber.subscribe();
},
getDLOB: async (slot: number, protectedMakerView: boolean) => {
return await orderSubscriber.getDLOB(slot, protectedMakerView);
getDLOB: async (
slot: number,
protectedMakerParamsMap?: ProtectMakerParamsMap
) => {
return await orderSubscriber.getDLOB(slot, protectedMakerParamsMap);
},
getUniqueAuthorities: () => {
const authorities = new Set<string>();

View File

@@ -313,6 +313,9 @@ const main = async () => {
});
await redisClient.connect();
const indicativeRedisClient = new RedisClient({});
await indicativeRedisClient.connect();
const connection = new Connection(endpoint, {
wsEndpoint: wsEndpoint,
commitment: stateCommitment,
@@ -506,7 +509,7 @@ const main = async () => {
});
await dlobSubscriber.subscribe();
const dlobSubscriberPmm = new DLOBSubscriberIO({
const dlobSubscriberIndicative = new DLOBSubscriberIO({
driftClient,
env: driftEnv,
dlobSource: dlobProvider,
@@ -518,8 +521,9 @@ const main = async () => {
spotMarketInfos,
killSwitchSlotDiffThreshold: KILLSWITCH_SLOT_DIFF_THRESHOLD,
protectedMakerView: true,
indicativeQuotesRedisClient: indicativeRedisClient,
});
await dlobSubscriberPmm.subscribe();
await dlobSubscriberIndicative.subscribe();
if (useWebsocket && !FEATURE_FLAGS.DISABLE_GPA_REFRESH) {
const recursiveFetch = (delay = WS_FALLBACK_FETCH_INTERVAL) => {
@@ -595,7 +599,6 @@ const main = async () => {
const handleDebug = async (req: Request, res: Response) => {
const marketIndex = +req.query.marketIndex;
const protectedMakerView = req.query.includePmm === 'true';
let marketType: MarketType = MarketType.PERP;
let oraclePriceData: OraclePriceData;
if (req.query.marketType === 'spot') {
@@ -606,7 +609,7 @@ const main = async () => {
}
try {
const slot = slotSource.getSlot();
const dlob = await dlobProvider.getDLOB(slot, protectedMakerView);
const dlob = await dlobProvider.getDLOB(slot);
const l2 = dlob.getL2({
marketIndex,
marketType,
@@ -629,7 +632,6 @@ const main = async () => {
},
l2: l2WithBNToStrings(l2),
l3,
includePmm: protectedMakerView,
};
res.json(state);

View File

@@ -95,6 +95,8 @@ const getRedisChannelFromMessage = (message: any): string => {
return `trades_${marketType}_${marketIndex}`;
case 'orderbook':
return `orderbook_${marketType}_${marketIndex}`;
case 'orderbook_indicative':
return `orderbook_${marketType}_${marketIndex}_indicative`;
case 'priorityfees':
return `priorityFees_${marketType}_${marketIndex}`;
case undefined: