Skip to content

Commit

Permalink
Filter out to single tick per interval. (#2403)
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentwschau authored Oct 1, 2024
1 parent 233c147 commit ce94992
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import Big from 'big.js';

describe('vault-controller#V4', () => {
const latestBlockHeight: string = '25';
const currentBlockHeight: string = '7';
const twoHourBlockHeight: string = '5';
const currentBlockHeight: string = '9';
const twoHourBlockHeight: string = '7';
const almostTwoDayBlockHeight: string = '5';
const twoDayBlockHeight: string = '3';
const currentTime: DateTime = DateTime.utc().startOf('day').minus({ hour: 5 });
const latestTime: DateTime = currentTime.plus({ second: 5 });
const twoHoursAgo: DateTime = currentTime.minus({ hour: 2 });
const twoDaysAgo: DateTime = currentTime.minus({ day: 2 });
const almostTwoDaysAgo: DateTime = currentTime.minus({ hour: 47 });
const initialFundingIndex: string = '10000';
const vault1Equity: number = 159500;
const vault2Equity: number = 10000;
Expand Down Expand Up @@ -70,6 +72,11 @@ describe('vault-controller#V4', () => {
time: latestTime.toISO(),
blockHeight: latestBlockHeight,
}),
BlockTable.create({
...testConstants.defaultBlock,
time: almostTwoDaysAgo.toISO(),
blockHeight: almostTwoDayBlockHeight,
}),
]);
await SubaccountTable.create(testConstants.vaultSubaccount);
await SubaccountTable.create({
Expand Down Expand Up @@ -152,14 +159,27 @@ describe('vault-controller#V4', () => {
});

it.each([
['no resolution', '', [1, 2]],
['daily resolution', '?resolution=day', [1, 2]],
['hourly resolution', '?resolution=hour', [1, 2, 3]],
['no resolution', '', [1, 2], [undefined, 6], [9, 10]],
['daily resolution', '?resolution=day', [1, 2], [undefined, 6], [9, 10]],
[
'hourly resolution',
'?resolution=hour',
[1, undefined, 2, 3],
[undefined, 5, 6, 7],
[9, undefined, 10, 11],
],
])('Get /megavault/historicalPnl with 2 vault subaccounts and main subaccount (%s)', async (
_name: string,
queryParam: string,
expectedTicksIndex: number[],
expectedTicksIndex1: (number | undefined)[],
expectedTicksIndex2: (number | undefined)[],
expectedTicksIndexMain: (number | undefined)[],
) => {
const expectedTicksArray: (number | undefined)[][] = [
expectedTicksIndex1,
expectedTicksIndex2,
expectedTicksIndexMain,
];
await Promise.all([
VaultTable.create({
...testConstants.defaultVault,
Expand Down Expand Up @@ -198,15 +218,33 @@ describe('vault-controller#V4', () => {
createdAt: latestTime.toISO(),
};

expect(response.body.megavaultPnl).toHaveLength(expectedTicksIndex.length + 1);
expect(response.body.megavaultPnl).toHaveLength(expectedTicksIndex1.length + 1);
expect(response.body.megavaultPnl).toEqual(
expect.arrayContaining(
expectedTicksIndex.map((index: number) => {
expectedTicksIndex1.map((_: number | undefined, pos: number) => {
const pnlTickBase: any = {
equity: '0',
totalPnl: '0',
netTransfers: '0',
};
let expectedTick: PnlTicksFromDatabase;
for (const expectedTicks of expectedTicksArray) {
if (expectedTicks[pos] !== undefined) {
expectedTick = createdPnlTicks[expectedTicks[pos]!];
pnlTickBase.equity = Big(pnlTickBase.equity).add(expectedTick.equity).toFixed();
pnlTickBase.totalPnl = Big(pnlTickBase.totalPnl)
.add(expectedTick.totalPnl)
.toFixed();
pnlTickBase.netTransfers = Big(pnlTickBase.netTransfers)
.add(expectedTick.netTransfers)
.toFixed();
}
}
return expect.objectContaining({
...expectedPnlTickBase,
createdAt: createdPnlTicks[index].createdAt,
blockHeight: createdPnlTicks[index].blockHeight,
blockTime: createdPnlTicks[index].blockTime,
...pnlTickBase,
createdAt: expectedTick!.createdAt,
blockHeight: expectedTick!.blockHeight,
blockTime: expectedTick!.blockTime,
});
}).concat([expect.objectContaining(finalTick)]),
),
Expand Down Expand Up @@ -494,9 +532,9 @@ describe('vault-controller#V4', () => {
PnlTicksTable.create({
...testConstants.defaultPnlTick,
subaccountId: testConstants.vaultSubaccountId,
blockTime: twoDaysAgo.toISO(),
createdAt: twoDaysAgo.toISO(),
blockHeight: twoDayBlockHeight,
blockTime: almostTwoDaysAgo.toISO(),
createdAt: almostTwoDaysAgo.toISO(),
blockHeight: almostTwoDayBlockHeight,
}),
PnlTicksTable.create({
...testConstants.defaultPnlTick,
Expand Down
1 change: 1 addition & 0 deletions indexer/services/comlink/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const configSchema = {

// Vaults config
VAULT_PNL_HISTORY_DAYS: parseInteger({ default: 90 }),
VAULT_PNL_HISTORY_HOURS: parseInteger({ default: 72 }),
};

////////////////////////////////////////////////////////////////////////////////
Expand Down
75 changes: 64 additions & 11 deletions indexer/services/comlink/src/controllers/api/v4/vault-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Big from 'big.js';
import express from 'express';
import { checkSchema, matchedData } from 'express-validator';
import _ from 'lodash';
import { DateTime } from 'luxon';
import {
Controller, Get, Query, Route,
} from 'tsoa';
Expand Down Expand Up @@ -85,7 +86,7 @@ class VaultController extends Controller {
BlockFromDatabase,
string,
] = await Promise.all([
getVaultSubaccountPnlTicks(vaultSubaccountIdsWithMainSubaccount, resolution),
getVaultSubaccountPnlTicks(vaultSubaccountIdsWithMainSubaccount, getResolution(resolution)),
getVaultPositions(vaultSubaccounts),
BlockTable.getLatest(),
getMainSubaccountEquity(),
Expand All @@ -102,7 +103,7 @@ class VaultController extends Controller {
}, mainSubaccountEquity);
const pnlTicksWithCurrentTick: PnlTicksFromDatabase[] = getPnlTicksWithCurrentTick(
currentEquity,
Array.from(aggregatedPnlTicks.values()),
filterOutIntervalTicks(aggregatedPnlTicks, getResolution(resolution)),
latestBlock,
);

Expand All @@ -128,7 +129,7 @@ class VaultController extends Controller {
Map<string, VaultPosition>,
BlockFromDatabase,
] = await Promise.all([
getVaultSubaccountPnlTicks(_.keys(vaultSubaccounts), resolution),
getVaultSubaccountPnlTicks(_.keys(vaultSubaccounts), getResolution(resolution)),
getVaultPositions(vaultSubaccounts),
BlockTable.getLatest(),
]);
Expand Down Expand Up @@ -294,21 +295,22 @@ router.get(

async function getVaultSubaccountPnlTicks(
vaultSubaccountIds: string[],
resolution?: PnlTickInterval,
resolution: PnlTickInterval,
): Promise<PnlTicksFromDatabase[]> {
if (vaultSubaccountIds.length === 0) {
return [];
}
let pnlTickInterval: PnlTickInterval;
if (resolution === undefined) {
pnlTickInterval = PnlTickInterval.day;

let windowSeconds: number;
if (resolution === PnlTickInterval.day) {
windowSeconds = config.VAULT_PNL_HISTORY_DAYS * 24 * 60 * 60; // days to seconds
} else {
pnlTickInterval = resolution;
windowSeconds = config.VAULT_PNL_HISTORY_HOURS * 60 * 60; // hours to seconds
}

const pnlTicks: PnlTicksFromDatabase[] = await PnlTicksTable.getPnlTicksAtIntervals(
pnlTickInterval,
config.VAULT_PNL_HISTORY_DAYS * 24 * 60 * 60,
resolution,
windowSeconds,
vaultSubaccountIds,
);

Expand Down Expand Up @@ -461,7 +463,7 @@ function getPnlTicksWithCurrentTick(
return [];
}
const currentTick: PnlTicksFromDatabase = {
...pnlTicks[pnlTicks.length - 1],
...(_.maxBy(pnlTicks, 'blockTime')!),
equity,
blockHeight: latestBlock.blockHeight,
blockTime: latestBlock.time,
Expand All @@ -470,6 +472,57 @@ function getPnlTicksWithCurrentTick(
return pnlTicks.concat([currentTick]);
}

/**
* Takes in a map of block heights to PnlTicks and filters out the closest pnl tick per interval.
* @param pnlTicksByBlock Map of block number to pnl tick.
* @param resolution Resolution of interval.
* @returns Array of PnlTicksFromDatabase, one per interval.
*/
function filterOutIntervalTicks(
pnlTicksByBlock: Map<number, PnlTicksFromDatabase>,
resolution: PnlTickInterval,
): PnlTicksFromDatabase[] {
// Track block to block time.
const blockToBlockTime: Map<number, DateTime> = new Map();
// Track start of days to closest block by block time.
const blocksPerInterval: Map<string, number> = new Map();
// Track start of days to closest Pnl tick.
const ticksPerInterval: Map<string, PnlTicksFromDatabase> = new Map();
pnlTicksByBlock.forEach((pnlTick: PnlTicksFromDatabase, block: number): void => {
const blockTime: DateTime = DateTime.fromISO(pnlTick.blockTime).toUTC();
blockToBlockTime.set(block, blockTime);

const startOfInterval: DateTime = blockTime.toUTC().startOf(resolution);
const startOfIntervalStr: string = startOfInterval.toISO();
const startOfIntervalBlock: number | undefined = blocksPerInterval.get(startOfIntervalStr);
// No block for the start of interval, set this block as the block for the interval.
if (startOfIntervalBlock === undefined) {
blocksPerInterval.set(startOfIntervalStr, block);
ticksPerInterval.set(startOfIntervalStr, pnlTick);
return;
}

const startOfDayBlockTime: DateTime | undefined = blockToBlockTime.get(startOfIntervalBlock);
// Invalid block set as start of day block, set this block as the block for the day.
if (startOfDayBlockTime === undefined) {
blocksPerInterval.set(startOfIntervalStr, block);
ticksPerInterval.set(startOfIntervalStr, pnlTick);
return;
}

// This block is closer to the start of the day, set it as the block for the day.
if (blockTime.diff(startOfInterval) < startOfDayBlockTime.diff(startOfInterval)) {
blocksPerInterval.set(startOfIntervalStr, block);
ticksPerInterval.set(startOfIntervalStr, pnlTick);
}
});
return Array.from(ticksPerInterval.values());
}

function getResolution(resolution: PnlTickInterval = PnlTickInterval.day): PnlTickInterval {
return resolution;
}

async function getVaultMapping(): Promise<VaultMapping> {
const vaults: VaultFromDatabase[] = await VaultTable.findAll(
{},
Expand Down

0 comments on commit ce94992

Please sign in to comment.