Skip to content

Commit

Permalink
[IND-412]: Correctly set order status for all order types (#577)
Browse files Browse the repository at this point in the history
* [IND-412]: Correctly handle case when clientIds are used

* nits
  • Loading branch information
Christopher-Li authored Oct 12, 2023
1 parent 51aac9d commit 37f665c
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 28 deletions.
2 changes: 2 additions & 0 deletions indexer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* [#143](https://github.com/dydxprotocol/v4-chain/pull/143) Add websocket events for perpetual markets.

### Improvements
* [#577](https://github.com/dydxprotocol/v4-chain/pull/577) Correctly set order status for all order types

* [#552](https://github.com/dydxprotocol/v4-chain/pull/552) Updated Elliptic compliance client block addresses with risk scores equal to the threshold in addition to risk score greater than the threshold.

* [#469](https://github.com/dydxprotocol/v4-chain/pull/469) Added a reason field to `/screen` endpoint to display a reason for blocking an address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ describe('LiquidationHandler', () => {
goodTilOneof,
clobPairId: defaultClobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_IOC,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
reduceOnly: true,
clientMetadata: 0,
});
Expand Down Expand Up @@ -300,7 +300,7 @@ describe('LiquidationHandler', () => {
clobPairId: defaultClobPairId,
side: makerOrderProto.side === IndexerOrder_Side.SIDE_BUY ? OrderSide.BUY : OrderSide.SELL,
orderFlags: makerOrderProto.orderId!.orderFlags.toString(),
timeInForce: TimeInForce.IOC,
timeInForce: TimeInForce.GTT,
reduceOnly: true,
goodTilBlock: protocolTranslations.getGoodTilBlock(makerOrderProto)?.toString(),
goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(makerOrderProto),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
FillTable,
FillType,
Liquidity,
OrderFromDatabase,
OrderSide,
OrderStatus,
OrderTable,
Expand Down Expand Up @@ -280,7 +281,7 @@ describe('OrderHandler', () => {
goodTilOneof: takerGoodTilOneof,
clobPairId: defaultClobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_IOC,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
reduceOnly: true,
clientMetadata: 0,
});
Expand Down Expand Up @@ -354,7 +355,7 @@ describe('OrderHandler', () => {
clobPairId: defaultClobPairId,
side: protocolTranslations.protocolOrderSideToOrderSide(takerOrderProto.side),
orderFlags: takerOrderProto.orderId!.orderFlags.toString(),
timeInForce: TimeInForce.IOC,
timeInForce: TimeInForce.GTT,
reduceOnly: true,
goodTilBlock: protocolTranslations.getGoodTilBlock(takerOrderProto)?.toString(),
goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(takerOrderProto),
Expand Down Expand Up @@ -869,7 +870,7 @@ describe('OrderHandler', () => {
},
clobPairId: defaultClobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
reduceOnly: false,
clientMetadata: 0,
});
Expand Down Expand Up @@ -954,7 +955,7 @@ describe('OrderHandler', () => {
clobPairId: defaultClobPairId,
side: protocolTranslations.protocolOrderSideToOrderSide(makerOrderProto.side),
orderFlags: makerOrderProto.orderId!.orderFlags.toString(),
timeInForce: TimeInForce.FOK,
timeInForce: TimeInForce.GTT,
reduceOnly: false,
goodTilBlock: protocolTranslations.getGoodTilBlock(makerOrderProto)?.toString(),
goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(makerOrderProto),
Expand Down Expand Up @@ -1074,7 +1075,7 @@ describe('OrderHandler', () => {
},
clobPairId: testConstants.defaultPerpetualMarket3.clobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
reduceOnly: false,
clientMetadata: 0,
});
Expand Down Expand Up @@ -1165,7 +1166,7 @@ describe('OrderHandler', () => {
clobPairId: testConstants.defaultPerpetualMarket3.clobPairId,
side: protocolTranslations.protocolOrderSideToOrderSide(makerOrderProto.side),
orderFlags: makerOrderProto.orderId!.orderFlags.toString(),
timeInForce: TimeInForce.FOK,
timeInForce: TimeInForce.GTT,
reduceOnly: false,
goodTilBlock: protocolTranslations.getGoodTilBlock(makerOrderProto)?.toString(),
goodTilBlockTime: protocolTranslations.getGoodTilBlockTime(makerOrderProto),
Expand Down Expand Up @@ -1359,6 +1360,238 @@ describe('OrderHandler', () => {
await expectNoCandles();
});

it.each([
[
'via knex',
false,
],
[
'via SQL function',
true,
],
])('correctly sets status for short term IOC orders (%s)', async (
_name: string,
useSqlFunction: boolean,
) => {
config.USE_ORDER_HANDLER_SQL_FUNCTION = useSqlFunction;
const transactionIndex: number = 0;
const eventIndex: number = 0;
const makerQuantums: number = 100;
const makerSubticks: number = 1_000_000;

const makerOrderProto: IndexerOrder = createOrder({
subaccountId: defaultSubaccountId,
clientId: 0,
side: IndexerOrder_Side.SIDE_BUY,
quantums: makerQuantums,
subticks: makerSubticks,
goodTilOneof: {
goodTilBlock: 10,
},
clobPairId: testConstants.defaultPerpetualMarket3.clobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_IOC,
reduceOnly: false,
clientMetadata: 0,
});

const takerSubticks: number = 150_000;
const takerQuantums: number = 10;
const takerOrderProto: IndexerOrder = createOrder({
subaccountId: defaultSubaccountId2,
clientId: 0,
side: IndexerOrder_Side.SIDE_SELL,
quantums: takerQuantums,
subticks: takerSubticks,
goodTilOneof: {
goodTilBlock: 10,
},
clobPairId: testConstants.defaultPerpetualMarket3.clobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_IOC,
reduceOnly: true,
clientMetadata: 0,
});

const fillAmount: number = takerQuantums;
const orderFillEvent: OrderFillEventV1 = createOrderFillEvent(
makerOrderProto,
takerOrderProto,
fillAmount,
fillAmount,
fillAmount,
);
const kafkaMessage: KafkaMessage = createKafkaMessageFromOrderFillEvent({
orderFillEvent,
transactionIndex,
eventIndex,
height: parseInt(defaultHeight, 10),
time: defaultTime,
txHash: defaultTxHash,
});

await Promise.all([
// initial position for subaccount 1
PerpetualPositionTable.create({
...defaultPerpetualPosition,
perpetualId: testConstants.defaultPerpetualMarket3.id,
}),
// initial position for subaccount 2
PerpetualPositionTable.create({
...defaultPerpetualPosition,
subaccountId: testConstants.defaultSubaccountId2,
perpetualId: testConstants.defaultPerpetualMarket3.id,
}),
]);

await onMessage(kafkaMessage);

const makerOrderId: string = OrderTable.orderIdToUuid(makerOrderProto.orderId!);
const takerOrderId: string = OrderTable.orderIdToUuid(takerOrderProto.orderId!);

const [makerOrder, takerOrder]: [
OrderFromDatabase | undefined,
OrderFromDatabase | undefined
] = await Promise.all([
OrderTable.findById(makerOrderId),
OrderTable.findById(takerOrderId),
]);

expect(makerOrder).toBeDefined();
expect(takerOrder).toBeDefined();

// maker order is partially filled
expect(makerOrder!.status).toEqual(OrderStatus.CANCELED);
// taker order is fully filled
expect(takerOrder!.status).toEqual(OrderStatus.FILLED);
});

it.each([
[
'limit',
'via knex',
false,
IndexerOrder_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
],
[
'limit',
'via SQL function',
true,
IndexerOrder_TimeInForce.TIME_IN_FORCE_UNSPECIFIED,
],
[
'post-only',
'via knex',
false,
IndexerOrder_TimeInForce.TIME_IN_FORCE_POST_ONLY,
],
[
'post-only',
'via SQL function',
true,
IndexerOrder_TimeInForce.TIME_IN_FORCE_POST_ONLY,
],
])('correctly sets status for short term %s orders (%s)', async (
_orderType: string,
_name: string,
useSqlFunction: boolean,
timeInForce: IndexerOrder_TimeInForce,
) => {
config.USE_ORDER_HANDLER_SQL_FUNCTION = useSqlFunction;
const transactionIndex: number = 0;
const eventIndex: number = 0;
const makerQuantums: number = 100;
const makerSubticks: number = 1_000_000;

const makerOrderProto: IndexerOrder = createOrder({
subaccountId: defaultSubaccountId,
clientId: 0,
side: IndexerOrder_Side.SIDE_BUY,
quantums: makerQuantums,
subticks: makerSubticks,
goodTilOneof: {
goodTilBlock: 10,
},
clobPairId: testConstants.defaultPerpetualMarket3.clobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce,
reduceOnly: false,
clientMetadata: 0,
});

const takerSubticks: number = 150_000;
const takerQuantums: number = 100;
const takerOrderProto: IndexerOrder = createOrder({
subaccountId: defaultSubaccountId2,
clientId: 0,
side: IndexerOrder_Side.SIDE_SELL,
quantums: takerQuantums,
subticks: takerSubticks,
goodTilOneof: {
goodTilBlock: 10,
},
clobPairId: testConstants.defaultPerpetualMarket3.clobPairId,
orderFlags: ORDER_FLAG_SHORT_TERM.toString(),
timeInForce,
reduceOnly: true,
clientMetadata: 0,
});

const makerOrderId: string = OrderTable.orderIdToUuid(makerOrderProto.orderId!);
await CanceledOrdersCache.addCanceledOrderId(makerOrderId, Date.now(), redisClient);

const fillAmount: number = 10;
const orderFillEvent: OrderFillEventV1 = createOrderFillEvent(
makerOrderProto,
takerOrderProto,
fillAmount,
fillAmount,
fillAmount,
);
const kafkaMessage: KafkaMessage = createKafkaMessageFromOrderFillEvent({
orderFillEvent,
transactionIndex,
eventIndex,
height: parseInt(defaultHeight, 10),
time: defaultTime,
txHash: defaultTxHash,
});

await Promise.all([
// initial position for subaccount 1
PerpetualPositionTable.create({
...defaultPerpetualPosition,
perpetualId: testConstants.defaultPerpetualMarket3.id,
}),
// initial position for subaccount 2
PerpetualPositionTable.create({
...defaultPerpetualPosition,
subaccountId: testConstants.defaultSubaccountId2,
perpetualId: testConstants.defaultPerpetualMarket3.id,
}),
]);

await onMessage(kafkaMessage);

const takerOrderId: string = OrderTable.orderIdToUuid(takerOrderProto.orderId!);

const [makerOrder, takerOrder]: [
OrderFromDatabase | undefined,
OrderFromDatabase | undefined
] = await Promise.all([
OrderTable.findById(makerOrderId),
OrderTable.findById(takerOrderId),
]);

expect(makerOrder).toBeDefined();
expect(takerOrder).toBeDefined();

// maker order is partially filled, and in CanceledOrdersCache
expect(makerOrder!.status).toEqual(OrderStatus.BEST_EFFORT_CANCELED);
// taker order is partially filled, and not in CanceledOrdersCache
expect(takerOrder!.status).toEqual(OrderStatus.OPEN);
});

async function expectDefaultOrderAndFillSubaccountKafkaMessages(
producerSendMock: jest.SpyInstance,
eventId: Buffer,
Expand Down
Loading

0 comments on commit 37f665c

Please sign in to comment.