Skip to content

Commit 428e957

Browse files
vincentwschaumergify[bot]
authored andcommitted
Improve vault start pnl query. (#2664)
(cherry picked from commit 81ec99c)
1 parent 80e34f1 commit 428e957

File tree

7 files changed

+130
-105
lines changed

7 files changed

+130
-105
lines changed

indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,27 @@ export async function getVaultsPnl(
6262

6363
return result.rows;
6464
}
65+
66+
export async function getLatestVaultPnl(): Promise<PnlTicksFromDatabase[]> {
67+
const result: {
68+
rows: PnlTicksFromDatabase[],
69+
} = await knexReadReplica.getConnection().raw(
70+
`
71+
SELECT DISTINCT ON ("subaccountId")
72+
"id",
73+
"subaccountId",
74+
"equity",
75+
"totalPnl",
76+
"netTransfers",
77+
"createdAt",
78+
"blockHeight",
79+
"blockTime"
80+
FROM ${VAULT_HOURLY_PNL_VIEW}
81+
ORDER BY "subaccountId", "blockTime" DESC;
82+
`,
83+
) as unknown as {
84+
rows: PnlTicksFromDatabase[],
85+
};
86+
87+
return result.rows;
88+
}

indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { getFixedRepresentation, sendRequest } from '../../../helpers/helpers';
2424
import { DateTime, Settings } from 'luxon';
2525
import Big from 'big.js';
2626
import config from '../../../../src/config';
27+
import { clearVaultStartPnl, startVaultStartPnlCache } from '../../../../src/caches/vault-start-pnl';
2728

2829
describe('vault-controller#V4', () => {
2930
const latestBlockHeight: string = '25';
@@ -131,6 +132,7 @@ describe('vault-controller#V4', () => {
131132
await dbHelpers.clearData();
132133
await VaultPnlTicksView.refreshDailyView();
133134
await VaultPnlTicksView.refreshHourlyView();
135+
clearVaultStartPnl();
134136
config.VAULT_PNL_HISTORY_HOURS = vaultPnlHistoryHoursPrev;
135137
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS = vaultPnlLastPnlWindowPrev;
136138
config.VAULT_PNL_START_DATE = vaultPnlStartDatePrev;
@@ -653,6 +655,7 @@ describe('vault-controller#V4', () => {
653655
}
654656
await VaultPnlTicksView.refreshDailyView();
655657
await VaultPnlTicksView.refreshHourlyView();
658+
await startVaultStartPnlCache();
656659

657660
return createdTicks;
658661
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { NodeEnv } from '@dydxprotocol-indexer/base';
2+
import {
3+
PnlTicksFromDatabase,
4+
PnlTicksTable,
5+
} from '@dydxprotocol-indexer/postgres';
6+
import _ from 'lodash';
7+
8+
import { getVaultMapping, getVaultPnlStartDate } from '../lib/helpers';
9+
import { VaultMapping } from '../types';
10+
11+
let vaultStartPnl: PnlTicksFromDatabase[] = [];
12+
13+
export async function startVaultStartPnlCache(): Promise<void> {
14+
const vaultMapping: VaultMapping = await getVaultMapping();
15+
vaultStartPnl = await PnlTicksTable.getLatestPnlTick(
16+
_.keys(vaultMapping),
17+
// Add a buffer of 10 minutes to get the first PnL tick for PnL data as PnL ticks aren't
18+
// created exactly on the hour.
19+
getVaultPnlStartDate().plus({ minutes: 10 }),
20+
);
21+
}
22+
23+
export function getVaultStartPnl(): PnlTicksFromDatabase[] {
24+
return vaultStartPnl;
25+
}
26+
27+
export function clearVaultStartPnl(): void {
28+
if (process.env.NODE_ENV !== NodeEnv.TEST) {
29+
throw Error('cannot clear vault start pnl cache outside of test environment');
30+
}
31+
32+
vaultStartPnl = [];
33+
}

indexer/services/comlink/src/controllers/api/v4/vault-controller.ts

Lines changed: 21 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { logger, stats } from '@dydxprotocol-indexer/base';
1+
import { stats } from '@dydxprotocol-indexer/base';
22
import {
33
PnlTicksFromDatabase,
4-
PnlTicksTable,
54
perpetualMarketRefresher,
65
PerpetualMarketFromDatabase,
76
USDC_ASSET_ID,
@@ -22,7 +21,6 @@ import {
2221
BlockFromDatabase,
2322
FundingIndexUpdatesTable,
2423
PnlTickInterval,
25-
VaultTable,
2624
VaultFromDatabase,
2725
MEGAVAULT_SUBACCOUNT_ID,
2826
TransferFromDatabase,
@@ -42,10 +40,13 @@ import {
4240
} from 'tsoa';
4341

4442
import { getReqRateLimiter } from '../../../caches/rate-limiters';
43+
import { getVaultStartPnl } from '../../../caches/vault-start-pnl';
4544
import config from '../../../config';
4645
import {
4746
aggregateHourlyPnlTicks,
4847
getSubaccountResponse,
48+
getVaultMapping,
49+
getVaultPnlStartDate,
4950
handleControllerError,
5051
} from '../../../lib/helpers';
5152
import { rateLimiterMiddleware } from '../../../lib/rate-limit';
@@ -108,7 +109,7 @@ class VaultController extends Controller {
108109
getVaultPositions(vaultSubaccounts),
109110
BlockTable.getLatest(),
110111
getMainSubaccountEquity(),
111-
getLatestPnlTick(vaultSubaccountIdsWithMainSubaccount, _.values(vaultSubaccounts)),
112+
getLatestPnlTick(_.values(vaultSubaccounts)),
112113
getFirstMainVaultTransferDateTime(),
113114
]);
114115
stats.timing(
@@ -162,7 +163,7 @@ class VaultController extends Controller {
162163
getVaultSubaccountPnlTicks(_.keys(vaultSubaccounts), getResolution(resolution)),
163164
getVaultPositions(vaultSubaccounts),
164165
BlockTable.getLatest(),
165-
getLatestPnlTicks(_.keys(vaultSubaccounts)),
166+
getLatestPnlTicks(),
166167
]);
167168
const latestTicksBySubaccountId: Dictionary<PnlTicksFromDatabase> = _.keyBy(
168169
latestTicks,
@@ -348,27 +349,13 @@ async function getVaultSubaccountPnlTicks(
348349
windowSeconds = config.VAULT_PNL_HISTORY_HOURS * 60 * 60; // hours to seconds
349350
}
350351

351-
const [
352-
pnlTicks,
353-
adjustByPnlTicks,
354-
] : [
355-
PnlTicksFromDatabase[],
356-
PnlTicksFromDatabase[],
357-
] = await Promise.all([
358-
VaultPnlTicksView.getVaultsPnl(
359-
resolution,
360-
windowSeconds,
361-
getVaultPnlStartDate(),
362-
),
363-
PnlTicksTable.getLatestPnlTick(
364-
vaultSubaccountIds,
365-
// Add a buffer of 10 minutes to get the first PnL tick for PnL data as PnL ticks aren't
366-
// created exactly on the hour.
367-
getVaultPnlStartDate().plus({ minutes: 10 }),
368-
),
369-
]);
352+
const pnlTicks: PnlTicksFromDatabase[] = await VaultPnlTicksView.getVaultsPnl(
353+
resolution,
354+
windowSeconds,
355+
getVaultPnlStartDate(),
356+
);
370357

371-
return adjustVaultPnlTicks(pnlTicks, adjustByPnlTicks);
358+
return adjustVaultPnlTicks(pnlTicks, getVaultStartPnl());
372359
}
373360

374361
async function getVaultPositions(
@@ -559,60 +546,26 @@ function getPnlTicksWithCurrentTick(
559546
return pnlTicks.concat([currentTick]);
560547
}
561548

562-
export async function getLatestPnlTicks(
563-
vaultSubaccountIds: string[],
564-
): Promise<PnlTicksFromDatabase[]> {
565-
const [
566-
latestPnlTicks,
567-
adjustByPnlTicks,
568-
] : [
569-
PnlTicksFromDatabase[],
570-
PnlTicksFromDatabase[],
571-
] = await Promise.all([
572-
PnlTicksTable.getLatestPnlTick(
573-
vaultSubaccountIds,
574-
DateTime.now().toUTC(),
575-
),
576-
PnlTicksTable.getLatestPnlTick(
577-
vaultSubaccountIds,
578-
// Add a buffer of 10 minutes to get the first PnL tick for PnL data as PnL ticks aren't
579-
// created exactly on the hour.
580-
getVaultPnlStartDate().plus({ minutes: 10 }),
581-
),
582-
]);
549+
export async function getLatestPnlTicks(): Promise<PnlTicksFromDatabase[]> {
550+
const latestPnlTicks: PnlTicksFromDatabase[] = await VaultPnlTicksView.getLatestVaultPnl();
583551
const adjustedPnlTicks: PnlTicksFromDatabase[] = adjustVaultPnlTicks(
584552
latestPnlTicks,
585-
adjustByPnlTicks,
553+
getVaultStartPnl(),
586554
);
587555
return adjustedPnlTicks;
588556
}
589557

590558
export async function getLatestPnlTick(
591-
vaultSubaccountIds: string[],
592559
vaults: VaultFromDatabase[],
593560
): Promise<PnlTicksFromDatabase | undefined> {
594-
const [
595-
pnlTicks,
596-
adjustByPnlTicks,
597-
] : [
598-
PnlTicksFromDatabase[],
599-
PnlTicksFromDatabase[],
600-
] = await Promise.all([
601-
VaultPnlTicksView.getVaultsPnl(
602-
PnlTickInterval.hour,
603-
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS * 60 * 60,
604-
getVaultPnlStartDate(),
605-
),
606-
PnlTicksTable.getLatestPnlTick(
607-
vaultSubaccountIds,
608-
// Add a buffer of 10 minutes to get the first PnL tick for PnL data as PnL ticks aren't
609-
// created exactly on the hour.
610-
getVaultPnlStartDate().plus({ minutes: 10 }),
611-
),
612-
]);
561+
const pnlTicks: PnlTicksFromDatabase[] = await VaultPnlTicksView.getVaultsPnl(
562+
PnlTickInterval.hour,
563+
config.VAULT_LATEST_PNL_TICK_WINDOW_HOURS * 60 * 60,
564+
getVaultPnlStartDate(),
565+
);
613566
const adjustedPnlTicks: PnlTicksFromDatabase[] = adjustVaultPnlTicks(
614567
pnlTicks,
615-
adjustByPnlTicks,
568+
getVaultStartPnl(),
616569
);
617570
// Aggregate and get pnl tick closest to the hour
618571
const aggregatedTicks: PnlTicksFromDatabase[] = aggregateVaultPnlTicks(
@@ -802,41 +755,4 @@ function adjustVaultPnlTicks(
802755
});
803756
}
804757

805-
async function getVaultMapping(): Promise<VaultMapping> {
806-
const vaults: VaultFromDatabase[] = await VaultTable.findAll(
807-
{},
808-
[],
809-
{},
810-
);
811-
const vaultMapping: VaultMapping = _.zipObject(
812-
vaults.map((vault: VaultFromDatabase): string => {
813-
return SubaccountTable.uuid(vault.address, 0);
814-
}),
815-
vaults,
816-
);
817-
const validVaultMapping: VaultMapping = {};
818-
for (const subaccountId of _.keys(vaultMapping)) {
819-
const perpetual: PerpetualMarketFromDatabase | undefined = perpetualMarketRefresher
820-
.getPerpetualMarketFromClobPairId(
821-
vaultMapping[subaccountId].clobPairId,
822-
);
823-
if (perpetual === undefined) {
824-
logger.warning({
825-
at: 'VaultController#getVaultPositions',
826-
message: `Vault clob pair id ${vaultMapping[subaccountId]} does not correspond to a ` +
827-
'perpetual market.',
828-
subaccountId,
829-
});
830-
continue;
831-
}
832-
validVaultMapping[subaccountId] = vaultMapping[subaccountId];
833-
}
834-
return validVaultMapping;
835-
}
836-
837-
function getVaultPnlStartDate(): DateTime {
838-
const startDate: DateTime = DateTime.fromISO(config.VAULT_PNL_START_DATE).toUTC();
839-
return startDate;
840-
}
841-
842758
export default router;

indexer/services/comlink/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
} from '@dydxprotocol-indexer/base';
66
import { perpetualMarketRefresher, liquidityTierRefresher } from '@dydxprotocol-indexer/postgres';
77

8+
import { startVaultStartPnlCache } from './caches/vault-start-pnl';
89
import config from './config';
910
import IndexV4 from './controllers/api/index-v4';
1011
import { connect as connectToRedis } from './helpers/redis/redis-controller';
@@ -42,6 +43,8 @@ async function start() {
4243
]);
4344
wrapBackgroundTask(perpetualMarketRefresher.start(), true, 'startUpdatePerpetualMarkets');
4445
wrapBackgroundTask(liquidityTierRefresher.start(), true, 'startUpdateLiquidityTiers');
46+
// Initialize cache for vault start PnL
47+
await startVaultStartPnlCache();
4548

4649
await connectToRedis();
4750
logger.info({

indexer/services/comlink/src/lib/helpers.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
AssetFromDatabase,
2525
AssetColumns,
2626
MarketColumns,
27+
VaultFromDatabase, VaultTable, perpetualMarketRefresher,
2728
} from '@dydxprotocol-indexer/postgres';
2829
import Big from 'big.js';
2930
import express from 'express';
@@ -47,6 +48,7 @@ import {
4748
PerpetualPositionWithFunding,
4849
Risk,
4950
SubaccountResponseObject,
51+
VaultMapping,
5052
} from '../types';
5153
import { ZERO, ZERO_USDC_POSITION } from './constants';
5254
import { InvalidParamError, NotFoundError } from './errors';
@@ -720,3 +722,42 @@ export function aggregateHourlyPnlTicks(
720722
};
721723
});
722724
}
725+
726+
/* ------- VAULT HELPERS ------- */
727+
728+
export async function getVaultMapping(): Promise<VaultMapping> {
729+
const vaults: VaultFromDatabase[] = await VaultTable.findAll(
730+
{},
731+
[],
732+
{},
733+
);
734+
const vaultMapping: VaultMapping = _.zipObject(
735+
vaults.map((vault: VaultFromDatabase): string => {
736+
return SubaccountTable.uuid(vault.address, 0);
737+
}),
738+
vaults,
739+
);
740+
const validVaultMapping: VaultMapping = {};
741+
for (const subaccountId of _.keys(vaultMapping)) {
742+
const perpetual: PerpetualMarketFromDatabase | undefined = perpetualMarketRefresher
743+
.getPerpetualMarketFromClobPairId(
744+
vaultMapping[subaccountId].clobPairId,
745+
);
746+
if (perpetual === undefined) {
747+
logger.warning({
748+
at: 'get-vault-mapping',
749+
message: `Vault clob pair id ${vaultMapping[subaccountId]} does not correspond to a ` +
750+
'perpetual market.',
751+
subaccountId,
752+
});
753+
continue;
754+
}
755+
validVaultMapping[subaccountId] = vaultMapping[subaccountId];
756+
}
757+
return validVaultMapping;
758+
}
759+
760+
export function getVaultPnlStartDate(): DateTime {
761+
const startDate: DateTime = DateTime.fromISO(config.VAULT_PNL_START_DATE).toUTC();
762+
return startDate;
763+
}

indexer/services/comlink/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
TradeType,
2525
TradingRewardAggregationPeriod,
2626
TransferType,
27+
VaultFromDatabase,
2728
} from '@dydxprotocol-indexer/postgres';
2829
import { RedisOrder } from '@dydxprotocol-indexer/v4-protos';
2930
import Big from 'big.js';
@@ -691,6 +692,10 @@ export interface MegavaultHistoricalPnlRequest {
691692

692693
export interface VaultsHistoricalPnlRequest extends MegavaultHistoricalPnlRequest {}
693694

695+
export interface VaultMapping {
696+
[subaccountId: string]: VaultFromDatabase,
697+
}
698+
694699
/* ------- Affiliates Types ------- */
695700
export interface AffiliateMetadataRequest{
696701
address: string,

0 commit comments

Comments
 (0)