Skip to content

Commit

Permalink
[IND-546] Prevent IOC/FOK orders from changing order book levels / se…
Browse files Browse the repository at this point in the history
…nding updates. (#898)

* [IND-546] Prevent IOC/FOK orders from changing order book levels / sending updates.

* Address comments.
  • Loading branch information
vincentwschau committed Dec 22, 2023
1 parent 781153c commit 538fe2b
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 43 deletions.
18 changes: 17 additions & 1 deletion indexer/packages/redis/__tests__/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const defaultOrder: IndexerOrder = {
subticks: Long.fromValue(2_000_000, true),
goodTilBlock: 1150,
goodTilBlockTime: undefined,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_POST_ONLY,
reduceOnly: false,
clientMetadata: 0,
conditionType: IndexerOrder_ConditionType.CONDITION_TYPE_UNSPECIFIED,
Expand All @@ -91,6 +91,14 @@ export const defaultConditionalOrder: IndexerOrder = {
conditionType: IndexerOrder_ConditionType.CONDITION_TYPE_STOP_LOSS,
conditionalOrderTriggerSubticks: Long.fromValue(190_000_000, true),
};
export const defaultOrderFok: IndexerOrder = {
...defaultOrder,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL,
};
export const defaultOrderIoc: IndexerOrder = {
...defaultOrder,
timeInForce: IndexerOrder_TimeInForce.TIME_IN_FORCE_IOC,
};

export const defaultOrderUuid: string = OrderTable.orderIdToUuid(defaultOrderId);
export const defaultOrderUuidGoodTilBlockTime: string = OrderTable.orderIdToUuid(
Expand Down Expand Up @@ -126,6 +134,14 @@ export const defaultRedisOrderConditional: RedisOrder = {
id: defaultOrderUuidConditional,
order: defaultConditionalOrder,
};
export const defaultRedisOrderFok: RedisOrder = {
...defaultRedisOrder,
order: defaultOrderFok,
};
export const defaultRedisOrderIoc: RedisOrder = {
...defaultRedisOrder,
order: defaultOrderIoc,
};

export const orderPlace: OffChainUpdateOrderPlaceUpdateMessage = {
orderUpdate: undefined,
Expand Down
9 changes: 8 additions & 1 deletion indexer/packages/v4-proto-parser/src/order-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createHash } from 'crypto';

import { IndexerOrderId } from '@dydxprotocol-indexer/v4-protos';
import { IndexerOrderId, IndexerOrder_TimeInForce } from '@dydxprotocol-indexer/v4-protos';

import { ORDER_FLAG_CONDITIONAL, ORDER_FLAG_LONG_TERM } from './constants';

Expand All @@ -23,3 +23,10 @@ export function isStatefulOrder(orderFlag: number | String): boolean {
}
return numberOrderFlag === ORDER_FLAG_CONDITIONAL || numberOrderFlag === ORDER_FLAG_LONG_TERM;
}

export function requiresImmediateExecution(tif: IndexerOrder_TimeInForce): boolean {
return (
tif === IndexerOrder_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL ||
tif === IndexerOrder_TimeInForce.TIME_IN_FORCE_IOC
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import {
StatefulOrderUpdatesCache,
CanceledOrderStatus,
} from '@dydxprotocol-indexer/redis';

import {
OffChainUpdateV1,
IndexerOrder,
Expand Down Expand Up @@ -104,6 +103,20 @@ describe('order-place-handler', () => {
goodTilBlock: undefined,
goodTilBlockTime: 1_300_000_000,
};
const replacementOrderFok: IndexerOrder = {
...redisTestConstants.defaultOrderFok,
goodTilBlock: 1160,
goodTilBlockTime: undefined,
quantums: Long.fromValue(500_000, true),
subticks: Long.fromValue(1_000_000, true),
};
const replacementOrderIoc: IndexerOrder = {
...redisTestConstants.defaultOrderIoc,
goodTilBlock: 1160,
goodTilBlockTime: undefined,
quantums: Long.fromValue(500_000, true),
subticks: Long.fromValue(1_000_000, true),
};
const replacedOrder: RedisOrder = convertToRedisOrder(
replacementOrder,
testConstants.defaultPerpetualMarket,
Expand All @@ -116,6 +129,14 @@ describe('order-place-handler', () => {
replacementOrderConditional,
testConstants.defaultPerpetualMarket,
);
const replacedOrderFok: RedisOrder = convertToRedisOrder(
replacementOrderFok,
testConstants.defaultPerpetualMarket,
);
const replacedOrderIoc: RedisOrder = convertToRedisOrder(
replacementOrderIoc,
testConstants.defaultPerpetualMarket,
);
const replacementUpdate: OffChainUpdateV1 = {
orderPlace: {
order: replacementOrder,
Expand All @@ -137,6 +158,20 @@ describe('order-place-handler', () => {
OrderPlaceV1_OrderPlacementStatus.ORDER_PLACEMENT_STATUS_BEST_EFFORT_OPENED,
},
};
const replacementUpdateFok: OffChainUpdateV1 = {
orderPlace: {
order: replacementOrderFok,
placementStatus:
OrderPlaceV1_OrderPlacementStatus.ORDER_PLACEMENT_STATUS_BEST_EFFORT_OPENED,
},
};
const replacementUpdateIoc: OffChainUpdateV1 = {
orderPlace: {
order: replacementOrderIoc,
placementStatus:
OrderPlaceV1_OrderPlacementStatus.ORDER_PLACEMENT_STATUS_BEST_EFFORT_OPENED,
},
};
const replacementMessage: KafkaMessage = createKafkaMessage(
Buffer.from(Uint8Array.from(OffChainUpdateV1.encode(replacementUpdate).finish())),
);
Expand All @@ -150,6 +185,12 @@ describe('order-place-handler', () => {
OffChainUpdateV1.encode(replacementUpdateConditional).finish(),
)),
);
const replacementMessageFok: KafkaMessage = createKafkaMessage(
Buffer.from(Uint8Array.from(OffChainUpdateV1.encode(replacementUpdateFok).finish())),
);
const replacementMessageIoc: KafkaMessage = createKafkaMessage(
Buffer.from(Uint8Array.from(OffChainUpdateV1.encode(replacementUpdateIoc).finish())),
);
const dbDefaultOrder: OrderFromDatabase = {
...testConstants.defaultOrder,
id: testConstants.defaultOrderId,
Expand All @@ -164,6 +205,16 @@ describe('order-place-handler', () => {
id: testConstants.defaultConditionalOrderId,
createdAtHeight: '3',
};
const dbDefaultOrderFok: OrderFromDatabase = {
...testConstants.defaultOrder,
id: testConstants.defaultOrderId,
timeInForce: TimeInForce.FOK,
};
const dbDefaultOrderIoc: OrderFromDatabase = {
...testConstants.defaultOrder,
id: testConstants.defaultOrderId,
timeInForce: TimeInForce.IOC,
};

beforeAll(async () => {
await dbHelpers.migrate();
Expand Down Expand Up @@ -410,6 +461,7 @@ describe('order-place-handler', () => {
redisTestConstants.defaultOrderUuid,
replacedOrder,
true,
true,
],
[
'goodTilBlockTime',
Expand All @@ -420,6 +472,7 @@ describe('order-place-handler', () => {
redisTestConstants.defaultOrderUuidGoodTilBlockTime,
replacedOrderGoodTilBlockTime,
false,
true,
],
[
'conditional',
Expand All @@ -430,6 +483,29 @@ describe('order-place-handler', () => {
redisTestConstants.defaultOrderUuidConditional,
replacedOrderConditional,
false,
true,
],
[
'Fill-or-Kill',
redisTestConstants.defaultOrderFok,
replacementMessageFok,
redisTestConstants.defaultRedisOrderFok,
dbDefaultOrderFok,
redisTestConstants.defaultOrderUuid,
replacedOrderFok,
true,
false,
],
[
'Immediate-or-Cancel',
redisTestConstants.defaultOrderIoc,
replacementMessageIoc,
redisTestConstants.defaultRedisOrderIoc,
dbDefaultOrderIoc,
redisTestConstants.defaultOrderUuid,
replacedOrderIoc,
true,
false,
],
])('handles order place for replacing order (with %s), resting on book', async (
_name: string,
Expand All @@ -440,6 +516,7 @@ describe('order-place-handler', () => {
expectedOrderUuid: string,
expectedReplacedOrder: RedisOrder,
expectSubaccountMessage: boolean,
expectOrderBookUpdate: boolean,
) => {
const oldOrderTotalFilled: number = 10;
const oldPriceLevelInitialQuantums: number = Number(initialOrderToPlace.quantums) * 2;
Expand Down Expand Up @@ -521,9 +598,11 @@ describe('order-place-handler', () => {
);

// Check the order book levels were updated
expect(orderbook.bids).toHaveLength(1);
expect(orderbook.asks).toHaveLength(0);
expect(orderbook.bids).toContainEqual(expectedPriceLevel);
if (expectOrderBookUpdate) {
expect(orderbook.bids).toHaveLength(1);
expect(orderbook.asks).toHaveLength(0);
expect(orderbook.bids).toContainEqual(expectedPriceLevel);
}

// Check the order was removed from the open orders cache
await expectOpenOrderIds(testConstants.defaultPerpetualMarket.clobPairId, []);
Expand All @@ -542,11 +621,12 @@ describe('order-place-handler', () => {
testConstants.defaultPerpetualMarket,
APIOrderStatusEnum.BEST_EFFORT_OPENED,
expectSubaccountMessage,
OrderbookMessage.fromPartial({
contents: JSON.stringify(orderbookContents),
clobPairId: testConstants.defaultPerpetualMarket.clobPairId,
version: ORDERBOOKS_WEBSOCKET_MESSAGE_VERSION,
}),
expectOrderBookUpdate
? OrderbookMessage.fromPartial({
contents: JSON.stringify(orderbookContents),
clobPairId: testConstants.defaultPerpetualMarket.clobPairId,
version: ORDERBOOKS_WEBSOCKET_MESSAGE_VERSION,
}) : undefined,
);
expectStats(true);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
testConstants,
testMocks,
apiTranslations,
TimeInForce,
} from '@dydxprotocol-indexer/postgres';
import {
OpenOrdersCache,
Expand Down Expand Up @@ -123,6 +124,15 @@ describe('OrderRemoveHandler', () => {
testConstants.defaultPerpetualMarket.atomicResolution,
);

const dbOrderFok: OrderCreateObject = {
...testConstants.defaultOrder,
timeInForce: TimeInForce.FOK,
};
const dbOrderIoc: OrderCreateObject = {
...testConstants.defaultOrder,
timeInForce: TimeInForce.IOC,
};

it.each([
[
{
Expand Down Expand Up @@ -222,6 +232,7 @@ describe('OrderRemoveHandler', () => {
testConstants.defaultOrder,
redisTestConstants.defaultRedisOrder,
redisTestConstants.defaultOrderUuid,
true,
undefined,
],
[
Expand All @@ -230,6 +241,7 @@ describe('OrderRemoveHandler', () => {
testConstants.defaultOrderGoodTilBlockTime,
redisTestConstants.defaultRedisOrderGoodTilBlockTime,
redisTestConstants.defaultOrderUuidGoodTilBlockTime,
true,
undefined,
],
[
Expand All @@ -238,14 +250,34 @@ describe('OrderRemoveHandler', () => {
testConstants.defaultConditionalOrder,
redisTestConstants.defaultRedisOrderConditional,
redisTestConstants.defaultOrderUuidConditional,
true,
testConstants.defaultConditionalOrder.triggerPrice,
],
[
'Fill-or-Kill',
redisTestConstants.defaultOrderId,
dbOrderFok,
redisTestConstants.defaultRedisOrderFok,
redisTestConstants.defaultOrderUuid,
false,
undefined,
],
[
'Immediate-or-Cancel',
redisTestConstants.defaultOrderId,
dbOrderIoc,
redisTestConstants.defaultRedisOrderIoc,
redisTestConstants.defaultOrderUuid,
false,
undefined,
],
])('successfully removes order (with %s)', async (
_name: string,
removedOrderId: IndexerOrderId,
removedOrder: OrderCreateObject,
removedRedisOrder: RedisOrder,
expectedOrderUuid: string,
expectOrderbookUpdate: boolean,
triggerPrice?: string,
) => {
const offChainUpdate: OffChainUpdateV1 = orderRemoveToOffChainUpdate({
Expand Down Expand Up @@ -302,7 +334,7 @@ describe('OrderRemoveHandler', () => {
removedRedisOrder.ticker,
OrderSide.BUY,
defaultPrice,
remainingOrderbookLevel,
expectOrderbookUpdate ? remainingOrderbookLevel : orderbookLevel,
),
expectOrdersCacheEmpty(expectedOrderUuid),
expectOrdersDataCacheEmpty(removedOrderId),
Expand Down Expand Up @@ -365,11 +397,12 @@ describe('OrderRemoveHandler', () => {
subaccountId: redisTestConstants.defaultSubaccountId,
version: SUBACCOUNTS_WEBSOCKET_MESSAGE_VERSION,
}),
OrderbookMessage.fromPartial({
contents: JSON.stringify(orderbookContents),
clobPairId: testConstants.defaultPerpetualMarket.clobPairId,
version: ORDERBOOKS_WEBSOCKET_MESSAGE_VERSION,
}),
expectOrderbookUpdate
? OrderbookMessage.fromPartial({
contents: JSON.stringify(orderbookContents),
clobPairId: testConstants.defaultPerpetualMarket.clobPairId,
version: ORDERBOOKS_WEBSOCKET_MESSAGE_VERSION,
}) : undefined,
);
expect(logger.error).not.toHaveBeenCalled();
expectTimingStats(true, true);
Expand Down
Loading

0 comments on commit 538fe2b

Please sign in to comment.