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

Allow configuring day vault PnL starts. #2570

Merged
merged 2 commits into from
Nov 14, 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
Original file line number Diff line number Diff line change
Expand Up @@ -460,22 +460,19 @@ describe('PnlTicks store', () => {
interval,
7 * 24 * 60 * 60, // 1 week
[defaultSubaccountId, defaultSubaccountIdWithAlternateAddress],
DateTime.fromISO(createdTicks[8].blockTime).plus({ seconds: 1 }),
);
// 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],
];

Expand All @@ -486,6 +483,33 @@ describe('PnlTicks store', () => {
}
});

it('Gets latest pnl ticks for subaccounts before or at given date', async () => {
const createdTicks: PnlTicksFromDatabase[] = await setupIntervalPnlTicks();
const latestTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getLatestPnlTick(
[defaultSubaccountId, defaultSubaccountIdWithAlternateAddress],
DateTime.fromISO(createdTicks[8].blockTime).plus({ seconds: 1 }),
);
expect(latestTicks).toEqual([createdTicks[8], createdTicks[3]]);
});

it('Gets empty pnl ticks for subaccounts before or at date earlier than all pnl data', async () => {
const createdTicks: PnlTicksFromDatabase[] = await setupIntervalPnlTicks();
const latestTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getLatestPnlTick(
[defaultSubaccountId, defaultSubaccountIdWithAlternateAddress],
DateTime.fromISO(createdTicks[0].blockTime).minus({ years: 1 }),
);
expect(latestTicks).toEqual([]);
});

it('Gets empty pnl ticks for subaccounts before or at date if no subaccounts given', async () => {
const createdTicks: PnlTicksFromDatabase[] = await setupIntervalPnlTicks();
const latestTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getLatestPnlTick(
[],
DateTime.fromISO(createdTicks[0].blockTime).plus({ years: 1 }),
);
expect(latestTicks).toEqual([]);
});

});

async function setupRankedPnlTicksData() {
Expand Down
43 changes: 43 additions & 0 deletions indexer/packages/postgres/src/stores/pnl-ticks-table.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _ from 'lodash';
import { DateTime } from 'luxon';
import { QueryBuilder } from 'objection';

import {
Expand Down Expand Up @@ -465,7 +466,11 @@ export async function getPnlTicksAtIntervals(
interval: PnlTickInterval,
timeWindowSeconds: number,
subaccountIds: string[],
earliestDate: DateTime,
): Promise <PnlTicksFromDatabase[]> {
if (subaccountIds.length === 0) {
return [];
}
const result: {
rows: PnlTicksFromDatabase[],
} = await knexReadReplica.getConnection().raw(
Expand Down Expand Up @@ -493,6 +498,7 @@ export async function getPnlTicksAtIntervals(
FROM pnl_ticks
WHERE
"subaccountId" IN (${subaccountIds.map((id: string) => { return `'${id}'`; }).join(',')}) AND
"blockTime" >= '${earliestDate.toUTC().toISO()}'::timestamp AND
"blockTime" > NOW() - INTERVAL '${timeWindowSeconds} second'
) AS pnl_intervals
WHERE
Expand All @@ -505,3 +511,40 @@ export async function getPnlTicksAtIntervals(

return result.rows;
}

export async function getLatestPnlTick(
subaccountIds: string[],
beforeOrAt: DateTime,
): Promise <PnlTicksFromDatabase[]> {
if (subaccountIds.length === 0) {
return [];
}
const result: {
rows: PnlTicksFromDatabase[],
} = await knexReadReplica.getConnection().raw(
`
SELECT
DISTINCT ON ("subaccountId")
"id",
"subaccountId",
"equity",
"totalPnl",
"netTransfers",
"createdAt",
"blockHeight",
"blockTime"
FROM
pnl_ticks
WHERE
"subaccountId" in (${subaccountIds.map((id: string) => { return `'${id}'`; }).join(',')}) AND
"blockTime" <= '${beforeOrAt.toUTC().toISO()}'::timestamp
ORDER BY
"subaccountId",
"blockTime" DESC
`,
) as unknown as {
rows: PnlTicksFromDatabase[],
};

return result.rows;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('vault-controller#V4', () => {
const mainVaultEquity: number = 10000;
const vaultPnlHistoryHoursPrev: number = config.VAULT_PNL_HISTORY_HOURS;
const vaultPnlLastPnlWindowPrev: number = config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS;
const vaultPnlStartDatePrev: string = config.VAULT_PNL_START_DATE;

beforeAll(async () => {
await dbHelpers.migrate();
Expand All @@ -58,6 +59,8 @@ describe('vault-controller#V4', () => {
config.VAULT_PNL_HISTORY_HOURS = 168;
// Use last 48 hours to get latest pnl tick for tests.
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS = 48;
// Use a time before all pnl ticks as the pnl start date.
config.VAULT_PNL_START_DATE = '2020-01-01T00:00:00Z';
await testMocks.seedData();
await perpetualMarketRefresher.updatePerpetualMarkets();
await liquidityTierRefresher.updateLiquidityTiers();
Expand Down Expand Up @@ -126,6 +129,7 @@ describe('vault-controller#V4', () => {
await dbHelpers.clearData();
config.VAULT_PNL_HISTORY_HOURS = vaultPnlHistoryHoursPrev;
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS = vaultPnlLastPnlWindowPrev;
config.VAULT_PNL_START_DATE = vaultPnlStartDatePrev;
});

it('Get /megavault/historicalPnl with no vault subaccounts', async () => {
Expand All @@ -138,21 +142,32 @@ describe('vault-controller#V4', () => {
});

it.each([
['no resolution', '', [1, 2], 4],
['daily resolution', '?resolution=day', [1, 2], 4],
['hourly resolution', '?resolution=hour', [1, 2, 3, 4], 4],
['no resolution', '', [1, 2], 4, undefined],
['daily resolution', '?resolution=day', [1, 2], 4, undefined],
['hourly resolution', '?resolution=hour', [1, 2, 3, 4], 4, undefined],
['start date adjust PnL', '?resolution=hour', [1, 2, 3, 4], 4, twoDaysAgo.toISO()],
])('Get /megavault/historicalPnl with single vault subaccount (%s)', async (
_name: string,
queryParam: string,
expectedTicksIndex: number[],
finalTickIndex: number,
startDate: string | undefined,
) => {
if (startDate !== undefined) {
config.VAULT_PNL_START_DATE = startDate;
}
await VaultTable.create({
...testConstants.defaultVault,
address: testConstants.defaultSubaccount.address,
clobPairId: testConstants.defaultPerpetualMarket.clobPairId,
});
const createdPnlTicks: PnlTicksFromDatabase[] = await createPnlTicks();
// Adjust PnL by total pnl of start date
if (startDate !== undefined) {
for (const createdPnlTick of createdPnlTicks) {
createdPnlTick.totalPnl = Big(createdPnlTick.totalPnl).sub('10000').toFixed();
}
}
const finalTick: PnlTicksFromDatabase = {
...createdPnlTicks[finalTickIndex],
equity: Big(vault1Equity).toFixed(),
Expand Down
2 changes: 2 additions & 0 deletions indexer/services/comlink/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ export const configSchema = {
// Vaults config
VAULT_PNL_HISTORY_DAYS: parseInteger({ default: 90 }),
VAULT_PNL_HISTORY_HOURS: parseInteger({ default: 72 }),
VAULT_PNL_START_DATE: parseString({ default: '2024-01-01T00:00:00Z' }),
VAULT_LATEST_PNL_TICK_WINDOW_HOURS: parseInteger({ default: 1 }),
VAULT_FETCH_FUNDING_INDEX_BLOCK_WINDOWS: parseInteger({ default: 250_000 }),

};

////////////////////////////////////////////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class VaultController extends Controller {
BlockFromDatabase,
string,
PnlTicksFromDatabase | undefined,
DateTime | undefined
DateTime | undefined,
] = await Promise.all([
getVaultSubaccountPnlTicks(vaultSubaccountIdsWithMainSubaccount, getResolution(resolution)),
getVaultPositions(vaultSubaccounts),
Expand All @@ -114,7 +114,6 @@ class VaultController extends Controller {
`${config.SERVICE_NAME}.${controllerName}.fetch_ticks_positions_equity.timing`,
Date.now() - startTicksPositions,
);

// aggregate pnlTicks for all vault subaccounts grouped by blockHeight
const aggregatedPnlTicks: PnlTicksFromDatabase[] = aggregateVaultPnlTicks(
vaultPnlTicks,
Expand Down Expand Up @@ -337,13 +336,28 @@ async function getVaultSubaccountPnlTicks(
windowSeconds = config.VAULT_PNL_HISTORY_HOURS * 60 * 60; // hours to seconds
}

const pnlTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getPnlTicksAtIntervals(
resolution,
windowSeconds,
vaultSubaccountIds,
);
const [
pnlTicks,
adjustByPnlTicks,
] : [
PnlTicksFromDatabase[],
PnlTicksFromDatabase[],
] = await Promise.all([
PnlTicksTable.getPnlTicksAtIntervals(
resolution,
windowSeconds,
vaultSubaccountIds,
getVautlPnlStartDate(),
),
Comment on lines +350 to +351
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in function name 'getVautlPnlStartDate'

The function name getVautlPnlStartDate is misspelled. It should be getVaultPnlStartDate. Please update the function name and all its usages accordingly.

Apply this diff to correct the function name and its usages:

-function getVautlPnlStartDate(): DateTime {
+function getVaultPnlStartDate(): DateTime {
  const startDate: DateTime = DateTime.fromISO(config.VAULT_PNL_START_DATE).toUTC();
  return startDate;
}

...

-        getVautlPnlStartDate(),
+        getVaultPnlStartDate(),

...

-          getVautlPnlStartDate().plus({ minutes: 10 }),
+          getVaultPnlStartDate().plus({ minutes: 10 }),

...

-          getVautlPnlStartDate(),
+          getVaultPnlStartDate(),

...

-          getVautlPnlStartDate().plus({ minutes: 10 }),
+          getVaultPnlStartDate().plus({ minutes: 10 }),

Also applies to: 356-357, 566-567, 572-573, 799-802

PnlTicksTable.getLatestPnlTick(
vaultSubaccountIds,
// Add a buffer of 10 minutes to get the first PnL tick for PnL data as PnL ticks aren't
// created exactly on the hour.
getVautlPnlStartDate().plus({ minutes: 10 }),
),
]);

return pnlTicks;
return adjustVaultPnlTicks(pnlTicks, adjustByPnlTicks);
}

async function getVaultPositions(
Expand Down Expand Up @@ -538,14 +552,33 @@ export async function getLatestPnlTick(
vaultSubaccountIds: string[],
vaults: VaultFromDatabase[],
): Promise<PnlTicksFromDatabase | undefined> {
const pnlTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getPnlTicksAtIntervals(
PnlTickInterval.hour,
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS * 60 * 60,
vaultSubaccountIds,
const [
pnlTicks,
adjustByPnlTicks,
] : [
PnlTicksFromDatabase[],
PnlTicksFromDatabase[],
] = await Promise.all([
PnlTicksTable.getPnlTicksAtIntervals(
PnlTickInterval.hour,
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS * 60 * 60,
vaultSubaccountIds,
getVautlPnlStartDate(),
),
PnlTicksTable.getLatestPnlTick(
vaultSubaccountIds,
// Add a buffer of 10 minutes to get the first PnL tick for PnL data as PnL ticks aren't
// created exactly on the hour.
getVautlPnlStartDate().plus({ minutes: 10 }),
),
]);
const adjustedPnlTicks: PnlTicksFromDatabase[] = adjustVaultPnlTicks(
pnlTicks,
adjustByPnlTicks,
);
// Aggregate and get pnl tick closest to the hour
const aggregatedTicks: PnlTicksFromDatabase[] = aggregateVaultPnlTicks(
pnlTicks,
adjustedPnlTicks,
vaults,
);
const filteredTicks: PnlTicksFromDatabase[] = filterOutIntervalTicks(
Expand Down Expand Up @@ -708,6 +741,29 @@ function aggregateVaultPnlTicks(
}).map((aggregatedPnlTick: AggregatedPnlTick) => { return aggregatedPnlTick.pnlTick; });
}

function adjustVaultPnlTicks(
pnlTicks: PnlTicksFromDatabase[],
pnlTicksToAdjustBy: PnlTicksFromDatabase[],
): PnlTicksFromDatabase[] {
const subaccountToPnlTick: {[subaccountId: string]: PnlTicksFromDatabase} = {};
for (const pnlTickToAdjustBy of pnlTicksToAdjustBy) {
subaccountToPnlTick[pnlTickToAdjustBy.subaccountId] = pnlTickToAdjustBy;
}

return pnlTicks.map((pnlTick: PnlTicksFromDatabase): PnlTicksFromDatabase => {
const adjustByPnlTick: PnlTicksFromDatabase | undefined = subaccountToPnlTick[
pnlTick.subaccountId
];
if (adjustByPnlTick === undefined) {
return pnlTick;
}
return {
...pnlTick,
totalPnl: Big(pnlTick.totalPnl).sub(Big(adjustByPnlTick.totalPnl)).toFixed(),
};
});
}

async function getVaultMapping(): Promise<VaultMapping> {
const vaults: VaultFromDatabase[] = await VaultTable.findAll(
{},
Expand Down Expand Up @@ -740,4 +796,9 @@ async function getVaultMapping(): Promise<VaultMapping> {
return validVaultMapping;
}

function getVautlPnlStartDate(): DateTime {
const startDate: DateTime = DateTime.fromISO(config.VAULT_PNL_START_DATE).toUTC();
return startDate;
}

export default router;
Loading