From d132b082760e278862f85ebaa4edbf59d384f6bf Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:18:27 -0400 Subject: [PATCH 1/2] [TRA-571] Add query to get intervaled PnL ticks. --- .../__tests__/stores/pnl-ticks-table.test.ts | 162 ++++++++++++++++++ .../postgres/src/stores/pnl-ticks-table.ts | 57 ++++++ .../postgres/src/types/pnl-ticks-types.ts | 5 + 3 files changed, 224 insertions(+) diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index cf382cdd47..41cb143267 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -2,8 +2,10 @@ import { IsoString, LeaderboardPnlCreateObject, Ordering, + PnlTickInterval, PnlTicksColumns, PnlTicksCreateObject, + PnlTicksFromDatabase, } from '../../src/types'; import * as PnlTicksTable from '../../src/stores/pnl-ticks-table'; import * as BlockTable from '../../src/stores/block-table'; @@ -439,6 +441,51 @@ describe('PnlTicks store', () => { expect(leaderboardRankedData.length).toEqual(2); }); + it.each([ + { + description: 'Get hourly pnl ticks', + interval: PnlTickInterval.hour, + }, + { + description: 'Get daily pnl ticks', + interval: PnlTickInterval.day, + }, + ])('$description', async ({ + interval, + }: { + interval: PnlTickInterval, + }) => { + const createdTicks: PnlTicksFromDatabase[] = await setupIntervalPnlTicks(); + const pnlTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getPnlTicksAtIntervals( + interval, + 604800, + [defaultSubaccountId, defaultSubaccountIdWithAlternateAddress], + ); + // See setup function for created ticks. + // Should exclude tick that is within the same hour except the first. + const expectedHourlyTicks: PnlTicksFromDatabase[] = [ + createdTicks[8], + createdTicks[7], + createdTicks[5], + createdTicks[3], + createdTicks[2], + createdTicks[0], + ]; + // Should exclude ticks that is within the same day except for the first. + const expectedDailyTicks: PnlTicksFromDatabase[] = [ + createdTicks[8], + createdTicks[7], + createdTicks[3], + createdTicks[2], + ]; + + if (interval === PnlTickInterval.day) { + expect(pnlTicks).toEqual(expectedDailyTicks); + } else if (interval === PnlTickInterval.hour) { + expect(pnlTicks).toEqual(expectedHourlyTicks); + } + }); + }); async function setupRankedPnlTicksData() { @@ -544,3 +591,118 @@ async function setupRankedPnlTicksData() { }, ]); } + +async function setupIntervalPnlTicks(): Promise { + const currentTime: DateTime = DateTime.utc().startOf('day'); + const tenMinAgo: string = currentTime.minus({ minute: 10 }).toISO(); + const almostTenMinAgo: string = currentTime.minus({ second: 603 }).toISO(); + const twoHoursAgo: string = currentTime.minus({ hour: 2 }).toISO(); + const twoDaysAgo: string = currentTime.minus({ day: 2 }).toISO(); + const monthAgo: string = currentTime.minus({ day: 30 }).toISO(); + await Promise.all([ + BlockTable.create({ + blockHeight: '3', + time: monthAgo, + }), + BlockTable.create({ + blockHeight: '4', + time: twoDaysAgo, + }), + BlockTable.create({ + blockHeight: '6', + time: twoHoursAgo, + }), + BlockTable.create({ + blockHeight: '8', + time: almostTenMinAgo, + }), + BlockTable.create({ + blockHeight: '10', + time: tenMinAgo, + }), + ]); + const createdTicks: PnlTicksFromDatabase[] = await PnlTicksTable.createMany([ + { + subaccountId: defaultSubaccountId, + equity: '1100', + createdAt: almostTenMinAgo, + totalPnl: '1200', + netTransfers: '50', + blockHeight: '10', + blockTime: almostTenMinAgo, + }, + { + subaccountId: defaultSubaccountId, + equity: '1090', + createdAt: tenMinAgo, + totalPnl: '1190', + netTransfers: '50', + blockHeight: '8', + blockTime: tenMinAgo, + }, + { + subaccountId: defaultSubaccountId, + equity: '1080', + createdAt: twoHoursAgo, + totalPnl: '1180', + netTransfers: '50', + blockHeight: '6', + blockTime: twoHoursAgo, + }, + { + subaccountId: defaultSubaccountId, + equity: '1070', + createdAt: twoDaysAgo, + totalPnl: '1170', + netTransfers: '50', + blockHeight: '4', + blockTime: twoDaysAgo, + }, + { + subaccountId: defaultSubaccountId, + equity: '1200', + createdAt: monthAgo, + totalPnl: '1170', + netTransfers: '50', + blockHeight: '3', + blockTime: monthAgo, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '200', + createdAt: almostTenMinAgo, + totalPnl: '300', + netTransfers: '50', + blockHeight: '10', + blockTime: almostTenMinAgo, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '210', + createdAt: tenMinAgo, + totalPnl: '310', + netTransfers: '50', + blockHeight: '8', + blockTime: tenMinAgo, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '220', + createdAt: twoHoursAgo, + totalPnl: '320', + netTransfers: '50', + blockHeight: '6', + blockTime: twoHoursAgo, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '230', + createdAt: twoDaysAgo, + totalPnl: '330', + netTransfers: '50', + blockHeight: '4', + blockTime: twoDaysAgo, + }, + ]); + return createdTicks; +} diff --git a/indexer/packages/postgres/src/stores/pnl-ticks-table.ts b/indexer/packages/postgres/src/stores/pnl-ticks-table.ts index f3cc9ffc1c..64ef22587a 100644 --- a/indexer/packages/postgres/src/stores/pnl-ticks-table.ts +++ b/indexer/packages/postgres/src/stores/pnl-ticks-table.ts @@ -22,6 +22,7 @@ import { PaginationFromDatabase, LeaderboardPnlCreateObject, LeaderboardPnlTimeSpan, + PnlTickInterval, } from '../types'; export function uuid( @@ -448,3 +449,59 @@ async function getAllTimeRankedPnlTicks(): Promise return result.rows; } + +/** + * Constructs a query to get pnl ticks at a specific interval for a set of subaccounts + * within a time range. + * Uses a windowing function in the raw query to get the first row of each window of the specific + * interval time. + * Currently only supports hourly / daily as the interval. + * @param interval 'day' or 'hour'. + * @param timeWindowSeconds Window of time to get pnl ticks for at the specified interval. + * @param subaccountIds Set of subaccounts to get pnl ticks for. + * @returns + */ +export async function getPnlTicksAtIntervals( + interval: PnlTickInterval, + timeWindowSeconds: number, + subaccountIds: string[], +): Promise { + const result: { + rows: PnlTicksFromDatabase[], + } = await knexReadReplica.getConnection().raw( + ` + SELECT + "id", + "subaccountId", + "equity", + "totalPnl", + "netTransfers", + "createdAt", + "blockHeight", + "blockTime" + FROM ( + SELECT + pnl_ticks.*, + ROW_NUMBER() OVER ( + PARTITION BY + "subaccountId", + DATE_TRUNC( + '${interval}', + "blockTime" + ) ORDER BY "blockTime" + ) AS r + FROM pnl_ticks + WHERE + "subaccountId" IN (${subaccountIds.map((id: string) => { return `'${id}'`; }).join(',')}) AND + "blockTime" > NOW() - INTERVAL '${timeWindowSeconds} second' + ) AS pnl_intervals + WHERE + r = 1 + ORDER BY "subaccountId"; + `, + ) as unknown as { + rows: PnlTicksFromDatabase[], + }; + + return result.rows; +} diff --git a/indexer/packages/postgres/src/types/pnl-ticks-types.ts b/indexer/packages/postgres/src/types/pnl-ticks-types.ts index 2e73eddedd..87c206b890 100644 --- a/indexer/packages/postgres/src/types/pnl-ticks-types.ts +++ b/indexer/packages/postgres/src/types/pnl-ticks-types.ts @@ -22,3 +22,8 @@ export enum PnlTicksColumns { blockHeight = 'blockHeight', blockTime = 'blockTime', } + +export enum PnlTickInterval { + hour = 'hour', + day = 'day', +} From 1fdc1734486f546e318bb1d132c88969f93bce31 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:24:05 -0400 Subject: [PATCH 2/2] Change comment. --- .../packages/postgres/__tests__/stores/pnl-ticks-table.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index 41cb143267..2fb699acbf 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -458,7 +458,7 @@ describe('PnlTicks store', () => { const createdTicks: PnlTicksFromDatabase[] = await setupIntervalPnlTicks(); const pnlTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getPnlTicksAtIntervals( interval, - 604800, + 7 * 24 * 60 * 60, // 1 week [defaultSubaccountId, defaultSubaccountIdWithAlternateAddress], ); // See setup function for created ticks.