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

Filter out to single tick per interval. #2403

Merged
merged 5 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -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,
vincentwschau marked this conversation as resolved.
Show resolved Hide resolved
});
}).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
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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe define a function that converts from resolution to seconds? thinking about the possibility of adding more resolution values in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This doesn't work with the underlying SQL query.

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')!),
vincentwschau marked this conversation as resolved.
Show resolved Hide resolved
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) {
return resolution;
}
vincentwschau marked this conversation as resolved.
Show resolved Hide resolved

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