Skip to content

Commit

Permalink
liquidator: use limit orders with auction params (#119)
Browse files Browse the repository at this point in the history
* liquidator: use limit orders with auction params

* liquidator: normalize slippage config to bps

* fix priority fee rounding
  • Loading branch information
wphan authored Jan 3, 2024
1 parent 8e7bb4d commit 0ead3a6
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 12 deletions.
12 changes: 10 additions & 2 deletions example.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,16 @@ botConfigs:
spotSubAccountConfig:
0: # subaccount 0 will watch all spot markets

# max slippage (from oracle price) to incur allow when derisking
maxSlippagePct: 0.005
# deprecated (bad naming): use maxSlippageBps
maxSlippagePct: 50

# Max slippage to incur allow when derisking (in bps).
# This is used to calculate the auction end price (worse price) when derisking
# and also passed into jupiter when doing swaps.
maxSlippageBps: 50

# duration of jit auction for derisk orders
deriskAuctionDurationSlots: 100

# what algo to use for derisking. Options are "market" or "twap"
deriskAlgo: "market"
Expand Down
71 changes: 62 additions & 9 deletions src/bots/liquidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
SerumFulfillmentConfigMap,
initialize,
DriftEnv,
getMarketOrderParams,
findDirectionToClose,
getSignedTokenAmount,
standardizeBaseAssetAmount,
Expand All @@ -38,6 +37,8 @@ import {
TxParams,
PriorityFeeSubscriber,
QuoteResponse,
getLimitOrderParams,
PERCENTAGE_PRECISION,
} from '@drift-labs/sdk';

import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
Expand Down Expand Up @@ -76,6 +77,7 @@ const errorCodesToSuppress = [
6010, // Error Number: 6010. Error Message: User Has No Position In Market.
];

const BPS_PRECISION = 10000;
const LIQUIDATE_THROTTLE_BACKOFF = 5000; // the time to wait before trying to liquidate a throttled user again
const MAX_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 20_000; // cap the computeUnitPrice to pay per fill tx

Expand Down Expand Up @@ -326,6 +328,10 @@ export class LiquidatorBot implements Bot {
SERUM_LOOKUP_TABLE: PublicKey
) {
this.liquidatorConfig = config;
if (this.liquidatorConfig.maxSlippageBps === undefined) {
this.liquidatorConfig.maxSlippageBps =
this.liquidatorConfig.maxSlippagePct!;
}

this.liquidatorConfig.deriskAlgoPerp =
config.deriskAlgoPerp ?? config.deriskAlgo;
Expand Down Expand Up @@ -534,7 +540,7 @@ export class LiquidatorBot implements Bot {
return {
computeUnits: 1_400_000,
computeUnitsPrice: Math.min(
this.priorityFeeSubscriber.maxPriorityFee * 1.1,
Math.floor(this.priorityFeeSubscriber.maxPriorityFee),
MAX_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS
),
};
Expand Down Expand Up @@ -653,16 +659,48 @@ export class LiquidatorBot implements Bot {
return healthy;
}

/**
* Calculates the worse price to execute at (used for auction end when derisking)
* @param oracle for asset we trading
* @param direction of trade
* @returns
*/
private calculateOrderLimitPrice(
oracle: OraclePriceData,
direction: PositionDirection
): BN {
const slippageBN = new BN(this.liquidatorConfig.maxSlippagePct! * 10000);
const slippageBN = new BN(
(this.liquidatorConfig.maxSlippageBps! / BPS_PRECISION) *
PERCENTAGE_PRECISION.toNumber()
);
if (isVariant(direction, 'long')) {
return oracle.price
.mul(PERCENTAGE_PRECISION.add(slippageBN))
.div(PERCENTAGE_PRECISION);
} else {
return oracle.price
.mul(PERCENTAGE_PRECISION.sub(slippageBN))
.div(PERCENTAGE_PRECISION);
}
}

/**
* Calcualtes the auctionStart price when derisking (the best price we want to execute at)
* @param oracle for asset we trading
* @param direction of trade
* @returns
*/
private calculateDeriskAuctionStartPrice(
oracle: OraclePriceData,
direction: PositionDirection
): BN {
let auctionStartPrice: BN;
if (isVariant(direction, 'long')) {
return oracle.price.mul(new BN(10000).add(slippageBN)).div(new BN(10000));
auctionStartPrice = oracle.price.sub(oracle.confidence);
} else {
return oracle.price.mul(new BN(10000).sub(slippageBN)).div(new BN(10000));
auctionStartPrice = oracle.price.add(oracle.confidence);
}
return auctionStartPrice;
}

private async driftSpotTrade(
Expand Down Expand Up @@ -696,13 +734,22 @@ export class LiquidatorBot implements Bot {
return;
}

const oracle = this.driftClient.getOracleDataForSpotMarket(marketIndex);
const auctionStartPrice = this.calculateDeriskAuctionStartPrice(
oracle,
orderDirection
);

const tx = await this.driftClient.placeSpotOrder(
getMarketOrderParams({
getLimitOrderParams({
marketIndex: marketIndex,
direction: orderDirection,
baseAssetAmount: standardizedTokenAmount,
reduceOnly: true,
price: limitPrice,
auctionDuration: this.liquidatorConfig.deriskAuctionDurationSlots!,
auctionStartPrice,
auctionEndPrice: limitPrice,
})
);
logger.info(
Expand Down Expand Up @@ -892,13 +939,20 @@ export class LiquidatorBot implements Bot {
);
const direction = findDirectionToClose(position);
const limitPrice = this.calculateOrderLimitPrice(oracle, direction);
const auctionStartPrice = this.calculateDeriskAuctionStartPrice(
oracle,
direction
);

return getMarketOrderParams({
return getLimitOrderParams({
direction,
baseAssetAmount,
reduceOnly: true,
marketIndex: position.marketIndex,
price: limitPrice,
auctionDuration: this.liquidatorConfig.deriskAuctionDurationSlots!,
auctionEndPrice: limitPrice,
auctionStartPrice,
});
}

Expand Down Expand Up @@ -1245,8 +1299,7 @@ export class LiquidatorBot implements Bot {
continue;
}

const slippageDenom = 10000;
const slippageBps = this.liquidatorConfig.maxSlippagePct! * slippageDenom;
const slippageBps = this.liquidatorConfig.maxSlippageBps! * BPS_PRECISION;
const jupQuote = await this.determineBestSpotSwapRoute(
position.marketIndex,
orderParams.direction,
Expand Down
10 changes: 9 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ export type LiquidatorConfig = BaseBotConfig & {
spotMarketIndicies?: Array<number>;
perpSubAccountConfig?: SubaccountConfig;
spotSubAccountConfig?: SubaccountConfig;

// deprecated: use maxSlippageBps (misnamed)
maxSlippagePct?: number;
maxSlippageBps?: number;

deriskAuctionDurationSlots?: number;
deriskAlgo?: OrderExecutionAlgoType;
deriskAlgoSpot?: OrderExecutionAlgoType;
deriskAlgoPerp?: OrderExecutionAlgoType;
Expand Down Expand Up @@ -251,7 +256,10 @@ export function loadConfigFromOpts(opts: any): Config {
perpMarketIndicies: loadCommaDelimitToArray(opts.perpMarketIndicies),
spotMarketIndicies: loadCommaDelimitToArray(opts.spotMarketIndicies),
runOnce: opts.runOnce ?? false,
maxSlippagePct: opts.maxSlippagePct ?? 0.05,
// deprecated: use maxSlippageBps
maxSlippagePct: opts.maxSlippagePct ?? 50,
maxSlippageBps: opts.maxSlippageBps ?? 50,
deriskAuctionDurationSlots: opts.deriskAuctionDurationSlots ?? 100,
deriskAlgo: opts.deriskAlgo ?? OrderExecutionAlgoType.Market,
twapDurationSec: parseInt(opts.twapDurationSec ?? '300'),
notifyOnLiquidation: opts.notifyOnLiquidation ?? false,
Expand Down

0 comments on commit 0ead3a6

Please sign in to comment.