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

[SERVICES-2541] Timescaledb query for token mini price charts #1441

Open
wants to merge 15 commits into
base: feat/xexchange-v3
Choose a base branch
from
Open
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
22 changes: 22 additions & 0 deletions src/helpers/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Address } from '@multiversx/sdk-core';
import { BigNumber } from 'bignumber.js';
import { BinaryUtils } from '@multiversx/sdk-nestjs-common';
import moment from 'moment';

export const SECONDS_TIMESTAMP_LENGTH = 10;
export const MILLISECONDS_TIMESTAMP_LENGTH = 13;

export function encodeTransactionData(data: string): string {
const delimiter = '@';
Expand Down Expand Up @@ -60,3 +64,21 @@ export function awsOneYear(): string {
export function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

export function isValidUnixTimestamp(value: string): boolean {
if (/^\d+$/.test(value) === false) {
return false;
}

const timestamp = Number(value);

if (value.length === SECONDS_TIMESTAMP_LENGTH) {
return moment.unix(timestamp).isValid();
}

if (value.length === MILLISECONDS_TIMESTAMP_LENGTH) {
return moment(timestamp).isValid();
}

return false;
}
3 changes: 3 additions & 0 deletions src/modules/analytics/analytics.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { RemoteConfigModule } from '../remote-config/remote-config.module';
import { AnalyticsModule as AnalyticsServicesModule } from 'src/services/analytics/analytics.module';
import { WeeklyRewardsSplittingModule } from 'src/submodules/weekly-rewards-splitting/weekly-rewards-splitting.module';
import { AnalyticsSetterService } from './services/analytics.setter.service';
import { AnalyticsTokenService } from './services/analytics.token.service';

@Module({
imports: [
Expand Down Expand Up @@ -48,12 +49,14 @@ import { AnalyticsSetterService } from './services/analytics.setter.service';
AnalyticsSetterService,
AnalyticsPairService,
PairDayDataResolver,
AnalyticsTokenService,
],
exports: [
AnalyticsAWSGetterService,
AnalyticsAWSSetterService,
AnalyticsComputeService,
AnalyticsSetterService,
AnalyticsTokenService,
],
})
export class AnalyticsModule {}
30 changes: 28 additions & 2 deletions src/modules/analytics/analytics.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import { Args, Resolver } from '@nestjs/graphql';
import {
CandleDataModel,
HistoricDataModel,
TokenCandlesModel,
} from 'src/modules/analytics/models/analytics.model';
import { AnalyticsQueryArgs, PriceCandlesQueryArgs } from './models/query.args';
import {
AnalyticsQueryArgs,
PriceCandlesQueryArgs,
TokenPriceCandlesQueryArgs,
} from './models/query.args';
import { AnalyticsAWSGetterService } from './services/analytics.aws.getter.service';
import { AnalyticsComputeService } from './services/analytics.compute.service';
import { PairComputeService } from '../pair/services/pair.compute.service';
Expand Down Expand Up @@ -177,7 +182,11 @@ export class AnalyticsResolver {
return [];
}

@Query(() => [CandleDataModel])
@Query(() => [CandleDataModel], {
deprecationReason:
'New optimized query is now available (tokensLast7dPrice).' +
'It allows fetching price data for multiple tokens in a single request',
})
@UsePipes(
new ValidationPipe({
skipNullProperties: true,
Expand All @@ -197,4 +206,21 @@ export class AnalyticsResolver {
args.resolution,
);
}

@Query(() => [TokenCandlesModel])
@UsePipes(
new ValidationPipe({
skipNullProperties: true,
skipMissingProperties: true,
skipUndefinedProperties: true,
}),
)
async tokensLast7dPrice(
@Args({ type: () => TokenPriceCandlesQueryArgs })
args: TokenPriceCandlesQueryArgs,
): Promise<TokenCandlesModel[]> {
return await this.analyticsAWSGetter.getTokensLast7dPrices(
args.identifiers,
);
}
}
13 changes: 13 additions & 0 deletions src/modules/analytics/models/analytics.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,16 @@ export class OhlcvDataModel {
Object.assign(this, init);
}
}

@ObjectType()
export class TokenCandlesModel {
@Field()
identifier: string;

@Field(() => [OhlcvDataModel])
candles: OhlcvDataModel[];

constructor(init?: Partial<TokenCandlesModel>) {
Object.assign(this, init);
}
}
18 changes: 17 additions & 1 deletion src/modules/analytics/models/query.args.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { ArgsType, Field, registerEnumType } from '@nestjs/graphql';
import { IsNotEmpty, Matches } from 'class-validator';
import {
ArrayMaxSize,
ArrayMinSize,
IsArray,
IsNotEmpty,
Matches,
Min,
} from 'class-validator';
import { IsValidMetric } from 'src/helpers/validators/metric.validator';
import { IsValidSeries } from 'src/helpers/validators/series.validator';
import { IsValidUnixTime } from 'src/helpers/validators/unix.time.validator';
Expand Down Expand Up @@ -53,3 +60,12 @@ export class PriceCandlesQueryArgs {
@Field(() => PriceCandlesResolutions)
resolution: PriceCandlesResolutions;
}

@ArgsType()
export class TokenPriceCandlesQueryArgs {
@Field(() => [String])
@IsArray()
@ArrayMinSize(1, { message: 'At least 1 token ID is required' })
@ArrayMaxSize(10, { message: 'At most 10 token IDs can be provided' })
identifiers: string[];
}
29 changes: 28 additions & 1 deletion src/modules/analytics/services/analytics.aws.getter.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Injectable } from '@nestjs/common';
import { generateCacheKeyFromParams } from '../../../utils/generate-cache-key';
import { CacheService } from '@multiversx/sdk-nestjs-cache';
import { HistoricDataModel } from '../models/analytics.model';
import {
HistoricDataModel,
OhlcvDataModel,
TokenCandlesModel,
} from '../models/analytics.model';
import moment from 'moment';
import { ErrorLoggerAsync } from '@multiversx/sdk-nestjs-common';
import { getMany } from 'src/utils/get.many.utils';

@Injectable()
export class AnalyticsAWSGetterService {
Expand Down Expand Up @@ -108,6 +113,28 @@ export class AnalyticsAWSGetterService {
return data !== undefined ? data.slice(1) : [];
}

@ErrorLoggerAsync()
async getTokensLast7dPrices(
identifiers: string[],
): Promise<TokenCandlesModel[]> {
const cacheKeys = identifiers.map((tokenID) =>
this.getAnalyticsCacheKey('tokenLast7dPrices', tokenID),
);

const candles = await getMany<OhlcvDataModel[]>(
this.cachingService,
cacheKeys,
);

return candles.map(
(tokenCandles, index) =>
new TokenCandlesModel({
identifier: identifiers[index],
candles: tokenCandles ?? [],
}),
);
}

private getAnalyticsCacheKey(...args: any) {
return generateCacheKeyFromParams('analytics', ...args);
}
Expand Down
13 changes: 13 additions & 0 deletions src/modules/analytics/services/analytics.setter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Constants } from '@multiversx/sdk-nestjs-common';
import { CacheService } from '@multiversx/sdk-nestjs-cache';
import { GenericSetterService } from 'src/services/generics/generic.setter.service';
import { Logger } from 'winston';
import { OhlcvDataModel } from '../models/analytics.model';

export class AnalyticsSetterService extends GenericSetterService {
constructor(
Expand Down Expand Up @@ -84,4 +85,16 @@ export class AnalyticsSetterService extends GenericSetterService {
Constants.oneMinute() * 5,
);
}

async setTokenLast7dPrices(
tokenID: string,
values: OhlcvDataModel[],
): Promise<string> {
return await this.setData(
this.getCacheKey('tokenLast7dPrices', tokenID),
values,
Constants.oneMinute() * 10,
Constants.oneMinute() * 6,
);
}
}
Loading
Loading