From 4d9f2d0fa9d90fe7f09da0121f47163fcb4ddfc8 Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Tue, 26 Nov 2024 18:07:49 -0500 Subject: [PATCH] [TRA-890] Return nearest hour PnL as final data point for vaults pnl. (#2616) --- .../api/v4/vault-controller.test.ts | 25 ++++++---- .../controllers/api/v4/vault-controller.ts | 48 ++++++++++++++++--- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts index 5d8a1ef709..4035552057 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts @@ -21,7 +21,7 @@ import { import { RequestMethod, VaultHistoricalPnl } from '../../../../src/types'; import request from 'supertest'; import { getFixedRepresentation, sendRequest } from '../../../helpers/helpers'; -import { DateTime } from 'luxon'; +import { DateTime, Settings } from 'luxon'; import Big from 'big.js'; import config from '../../../../src/config'; @@ -124,6 +124,7 @@ describe('vault-controller#V4', () => { effectiveAtHeight: twoDayBlockHeight, }), ]); + Settings.now = () => latestTime.valueOf(); }); afterEach(async () => { @@ -133,6 +134,7 @@ describe('vault-controller#V4', () => { config.VAULT_PNL_HISTORY_HOURS = vaultPnlHistoryHoursPrev; config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS = vaultPnlLastPnlWindowPrev; config.VAULT_PNL_START_DATE = vaultPnlStartDatePrev; + Settings.now = () => new Date().valueOf(); }); it.each([ @@ -298,13 +300,14 @@ describe('vault-controller#V4', () => { }); it.each([ - ['no resolution', '', [1, 2]], - ['daily resolution', '?resolution=day', [1, 2]], - ['hourly resolution', '?resolution=hour', [1, 2, 3, 4]], + ['no resolution', '', [1, 2], 4], + ['daily resolution', '?resolution=day', [1, 2], 4], + ['hourly resolution', '?resolution=hour', [1, 2, 3, 4], 4], ])('Get /vaults/historicalPnl with single vault subaccount (%s)', async ( _name: string, queryParam: string, expectedTicksIndex: number[], + currentTickIndex: number, ) => { await VaultTable.create({ ...testConstants.defaultVault, @@ -313,7 +316,7 @@ describe('vault-controller#V4', () => { }); const createdPnlTicks: PnlTicksFromDatabase[] = await createPnlTicks(); const finalTick: PnlTicksFromDatabase = { - ...createdPnlTicks[expectedTicksIndex[expectedTicksIndex.length - 1]], + ...createdPnlTicks[currentTickIndex], equity: Big(vault1Equity).toFixed(), blockHeight: latestBlockHeight, blockTime: latestTime.toISO(), @@ -338,14 +341,16 @@ describe('vault-controller#V4', () => { }); it.each([ - ['no resolution', '', [1, 2], [6, 7]], - ['daily resolution', '?resolution=day', [1, 2], [6, 7]], - ['hourly resolution', '?resolution=hour', [1, 2, 3, 4], [6, 7, 8, 9]], + ['no resolution', '', [1, 2], [6, 7], 4, 9], + ['daily resolution', '?resolution=day', [1, 2], [6, 7], 4, 9], + ['hourly resolution', '?resolution=hour', [1, 2, 3, 4], [6, 7, 8, 9], 4, 9], ])('Get /vaults/historicalPnl with 2 vault subaccounts (%s)', async ( _name: string, queryParam: string, expectedTicksIndex1: number[], expectedTicksIndex2: number[], + currentTickIndex1: number, + currentTickIndex2: number, ) => { await Promise.all([ VaultTable.create({ @@ -361,14 +366,14 @@ describe('vault-controller#V4', () => { ]); const createdPnlTicks: PnlTicksFromDatabase[] = await createPnlTicks(); const finalTick1: PnlTicksFromDatabase = { - ...createdPnlTicks[expectedTicksIndex1[expectedTicksIndex1.length - 1]], + ...createdPnlTicks[currentTickIndex1], equity: Big(vault1Equity).toFixed(), blockHeight: latestBlockHeight, blockTime: latestTime.toISO(), createdAt: latestTime.toISO(), }; const finalTick2: PnlTicksFromDatabase = { - ...createdPnlTicks[expectedTicksIndex2[expectedTicksIndex2.length - 1]], + ...createdPnlTicks[currentTickIndex2], equity: Big(vault2Equity).toFixed(), blockHeight: latestBlockHeight, blockTime: latestTime.toISO(), diff --git a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts index b4e75197e7..4df7f35fe4 100644 --- a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts @@ -35,7 +35,7 @@ import Big from 'big.js'; import bounds from 'binary-searching'; import express from 'express'; import { checkSchema, matchedData } from 'express-validator'; -import _ from 'lodash'; +import _, { Dictionary } from 'lodash'; import { DateTime } from 'luxon'; import { Controller, Get, Query, Route, @@ -152,15 +152,22 @@ class VaultController extends Controller { vaultPnlTicks, vaultPositions, latestBlock, + latestTicks, ] : [ PnlTicksFromDatabase[], Map, BlockFromDatabase, + PnlTicksFromDatabase[], ] = await Promise.all([ getVaultSubaccountPnlTicks(_.keys(vaultSubaccounts), getResolution(resolution)), getVaultPositions(vaultSubaccounts), BlockTable.getLatest(), + getLatestPnlTicks(_.keys(vaultSubaccounts)), ]); + const latestTicksBySubaccountId: Dictionary = _.keyBy( + latestTicks, + 'subaccountId', + ); const groupedVaultPnlTicks: VaultHistoricalPnl[] = _(vaultPnlTicks) .filter((pnlTickFromDatabsae: PnlTicksFromDatabase): boolean => { @@ -185,6 +192,7 @@ class VaultController extends Controller { currentEquity, pnlTicks, latestBlock, + latestTicksBySubaccountId[subaccountId], ); return { @@ -350,13 +358,13 @@ async function getVaultSubaccountPnlTicks( VaultPnlTicksView.getVaultsPnl( resolution, windowSeconds, - getVautlPnlStartDate(), + getVaultPnlStartDate(), ), 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 }), + getVaultPnlStartDate().plus({ minutes: 10 }), ), ]); @@ -551,6 +559,34 @@ function getPnlTicksWithCurrentTick( return pnlTicks.concat([currentTick]); } +export async function getLatestPnlTicks( + vaultSubaccountIds: string[], +): Promise { + const [ + latestPnlTicks, + adjustByPnlTicks, + ] : [ + PnlTicksFromDatabase[], + PnlTicksFromDatabase[], + ] = await Promise.all([ + PnlTicksTable.getLatestPnlTick( + vaultSubaccountIds, + DateTime.now().toUTC(), + ), + 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. + getVaultPnlStartDate().plus({ minutes: 10 }), + ), + ]); + const adjustedPnlTicks: PnlTicksFromDatabase[] = adjustVaultPnlTicks( + latestPnlTicks, + adjustByPnlTicks, + ); + return adjustedPnlTicks; +} + export async function getLatestPnlTick( vaultSubaccountIds: string[], vaults: VaultFromDatabase[], @@ -565,13 +601,13 @@ export async function getLatestPnlTick( VaultPnlTicksView.getVaultsPnl( PnlTickInterval.hour, config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS * 60 * 60, - getVautlPnlStartDate(), + getVaultPnlStartDate(), ), 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 }), + getVaultPnlStartDate().plus({ minutes: 10 }), ), ]); const adjustedPnlTicks: PnlTicksFromDatabase[] = adjustVaultPnlTicks( @@ -798,7 +834,7 @@ async function getVaultMapping(): Promise { return validVaultMapping; } -function getVautlPnlStartDate(): DateTime { +function getVaultPnlStartDate(): DateTime { const startDate: DateTime = DateTime.fromISO(config.VAULT_PNL_START_DATE).toUTC(); return startDate; }