Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

liquidator: use limit orders with auction params #119

Merged
merged 3 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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