Skip to content

Commit

Permalink
new selectMaker logic (#122)
Browse files Browse the repository at this point in the history
* liquidator: use dlob to estimate exit price for perps

* liqudator: use oracle orders

* new makerSelection

* add export for filler max num makers

* use map
  • Loading branch information
crispheaney authored Jan 9, 2024
1 parent a90ea90 commit 94c464c
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 11 deletions.
32 changes: 21 additions & 11 deletions src/bots/filler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ import {
getNodeToTriggerSignature,
sleepMs,
} from '../utils';
import { selectMakers } from '../makerSelection';

const MAX_TX_PACK_SIZE = 1230; //1232;
const CU_PER_FILL = 260_000; // CU cost for a successful fill
Expand All @@ -102,7 +103,7 @@ const FILL_ORDER_BACKOFF = 2000; // the time to wait before trying to a node in
const THROTTLED_NODE_SIZE_TO_PRUNE = 10; // Size of throttled nodes to get to before pruning the map
const TRIGGER_ORDER_COOLDOWN_MS = 1000; // the time to wait before trying to a node in the triggering map again
const MAX_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 20_000; // cap the computeUnitPrice to pay per fill tx
const MAX_MAKERS_PER_FILL = 6; // max number of unique makers to include per fill
export const MAX_MAKERS_PER_FILL = 6; // max number of unique makers to include per fill

const SETTLE_PNL_CHUNKS = 4;
const MAX_POSITIONS_PER_USER = 8;
Expand Down Expand Up @@ -186,6 +187,8 @@ function logMessageForNodeToFill(node: NodeToFill, prefix?: string): string {
return msg;
}

export type MakerNodeMap = Map<string, DLOBNode[]>;

export class FillerBot implements Bot {
public readonly name: string;
public readonly dryRun: boolean;
Expand Down Expand Up @@ -909,37 +912,44 @@ export class FillerBot implements Bot {
}> {
const makerInfos: Array<MakerInfo> = [];

// set to track whether maker account has already been included
const makersIncluded = new Set<string>();
if (nodeToFill.makerNodes.length > 0) {
let makerNodesMap: MakerNodeMap = new Map<string, DLOBNode[]>();
for (const makerNode of nodeToFill.makerNodes) {
if (this.isDLOBNodeThrottled(makerNode)) {
continue;
}

if (!makerNode.userAccount) {
continue;
}

const makerAccount = makerNode.userAccount;
if (makersIncluded.has(makerAccount)) {
continue;
}
if (makersIncluded.size >= MAX_MAKERS_PER_FILL) {
break;
if (makerNodesMap.has(makerNode.userAccount!)) {
makerNodesMap.get(makerNode.userAccount!)!.push(makerNode);
} else {
makerNodesMap.set(makerNode.userAccount!, [makerNode]);
}
}

if (makerNodesMap.size > MAX_MAKERS_PER_FILL) {
logger.info(`selecting from ${makerNodesMap.size} makers`);
makerNodesMap = selectMakers(makerNodesMap);
logger.info(`selected: ${Array.from(makerNodesMap.keys()).join(',')}`);
}

for (const [makerAccount, makerNodes] of makerNodesMap) {
const makerNode = makerNodes[0];

const makerUserAccount = await this.getUserAccountFromMap(makerAccount);
const makerAuthority = makerUserAccount.authority;
const makerUserStats = (
await this.userStatsMap!.mustGet(makerAuthority.toString())
).userStatsAccountPublicKey;
makerInfos.push({
maker: new PublicKey(makerNode.userAccount),
maker: new PublicKey(makerAccount),
makerUserAccount: makerUserAccount,
order: makerNode.order,
makerStats: makerUserStats,
});
makersIncluded.add(makerAccount);
}
}

Expand Down
69 changes: 69 additions & 0 deletions src/makerSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { BN, convertToNumber, divCeil, DLOBNode, ZERO } from '@drift-labs/sdk';
import { MakerNodeMap, MAX_MAKERS_PER_FILL } from './bots/filler';

const PROBABILITY_PRECISION = new BN(1000);

export function selectMakers(makerNodeMap: MakerNodeMap): MakerNodeMap {
const selectedMakers: MakerNodeMap = new Map();

while (selectedMakers.size < MAX_MAKERS_PER_FILL && makerNodeMap.size > 0) {
const maker = selectMaker(makerNodeMap);
if (maker === undefined) {
break;
}
const makerNodes = makerNodeMap.get(maker)!;
selectedMakers.set(maker, makerNodes);
makerNodeMap.delete(maker);
}

return selectedMakers;
}

function selectMaker(makerNodeMap: MakerNodeMap): string | undefined {
if (makerNodeMap.size === 0) {
return undefined;
}

let totalLiquidity = ZERO;
for (const [_, dlobNodes] of makerNodeMap) {
totalLiquidity = totalLiquidity.add(getMakerLiquidity(dlobNodes));
}

const probabilities = [];
for (const [_, dlobNodes] of makerNodeMap) {
probabilities.push(getProbability(dlobNodes, totalLiquidity));
}

let makerIndex = 0;
const random = Math.random();
let sum = 0;
for (let i = 0; i < probabilities.length; i++) {
sum += probabilities[i];
if (random < sum) {
makerIndex = i;
break;
}
}

return Array.from(makerNodeMap.keys())[makerIndex];
}

function getProbability(dlobNodes: DLOBNode[], totalLiquidity: BN): number {
const makerLiquidity = getMakerLiquidity(dlobNodes);
return convertToNumber(
divCeil(makerLiquidity.mul(PROBABILITY_PRECISION), totalLiquidity),
PROBABILITY_PRECISION
);
}

function getMakerLiquidity(dlobNodes: DLOBNode[]): BN {
return dlobNodes.reduce(
(acc, dlobNode) =>
acc.add(
dlobNode.order!.baseAssetAmount.sub(
dlobNode.order!.baseAssetAmountFilled
)
),
ZERO
);
}
63 changes: 63 additions & 0 deletions src/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { expect } from 'chai';
import { BN, isVariant } from '@drift-labs/sdk';
import { TwapExecutionProgress } from './types';
import { selectMakers } from './makerSelection';
import { MAX_MAKERS_PER_FILL } from './bots/filler';

describe('TwapExecutionProgress', () => {
const startTs = 1000;
Expand Down Expand Up @@ -265,3 +267,64 @@ describe('TwapExecutionProgress', () => {
expect(slice.toString()).to.be.equal(new BN(0).toString());
});
});

describe('selectMakers', () => {
let originalRandom: { (): number; (): number };

beforeEach(() => {
// Mock Math.random
let seed = 12345;
originalRandom = Math.random;
Math.random = () => {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
};
});

afterEach(() => {
// Restore original Math.random
Math.random = originalRandom;
});

it('more than 6', function () {
// Mock DLOBNode and Order
const mockOrder = (filledAmount: number, orderId: number) => ({
orderId,
baseAssetAmount: new BN(100),
baseAssetAmountFilled: new BN(filledAmount),
});

const mockDLOBNode = (filledAmount: number, orderId: number) => ({
order: mockOrder(filledAmount, orderId),
// Include other necessary properties of DLOBNode if needed
});

const makerNodeMap = new Map([
['0', [mockDLOBNode(10, 0)]],
['1', [mockDLOBNode(20, 1)]],
['2', [mockDLOBNode(30, 2)]],
['3', [mockDLOBNode(40, 3)]],
['4', [mockDLOBNode(50, 4)]],
['5', [mockDLOBNode(60, 5)]],
['6', [mockDLOBNode(70, 6)]],
['7', [mockDLOBNode(80, 7)]],
['8', [mockDLOBNode(90, 8)]],
]);

// @ts-ignore
const selectedMakers = selectMakers(makerNodeMap);

expect(selectedMakers).to.not.be.undefined;
expect(selectedMakers.size).to.be.equal(MAX_MAKERS_PER_FILL);

expect(selectedMakers.get('0')).to.not.be.undefined;
expect(selectedMakers.get('1')).to.not.be.undefined;
expect(selectedMakers.get('2')).to.not.be.undefined;
expect(selectedMakers.get('3')).to.not.be.undefined;
expect(selectedMakers.get('4')).to.be.undefined;
expect(selectedMakers.get('5')).to.not.be.undefined;
expect(selectedMakers.get('6')).to.not.be.undefined;
expect(selectedMakers.get('7')).to.be.undefined;
expect(selectedMakers.get('8')).to.be.undefined;
});
});

0 comments on commit 94c464c

Please sign in to comment.