Feature/set up portfolio snapshot queue (#3725)

* Set up portfolio snapshot queue

* Update changelog
This commit is contained in:
Thomas Kaul 2024-09-10 20:32:08 +02:00 committed by GitHub
parent 383a02519a
commit 403ee2741d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 621 additions and 119 deletions

View File

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Migrated the portfolio snapshot calculation to the queue design pattern
- Optimized the asynchronous operations using `Promise.all()` in the info service - Optimized the asynchronous operations using `Promise.all()` in the info service
- Optimized the asynchronous operations using `Promise.all()` in the admin control panel endpoint - Optimized the asynchronous operations using `Promise.all()` in the admin control panel endpoint
- Extracted the users from the admin control panel endpoint to a dedicated endpoint - Extracted the users from the admin control panel endpoint to a dedicated endpoint

View File

@ -2,10 +2,10 @@ import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorat
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service'; import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PropertyDto } from '@ghostfolio/api/services/property/property.dto'; import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_HIGH,
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM, DATA_GATHERING_QUEUE_PRIORITY_MEDIUM,

View File

@ -4,12 +4,12 @@ import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscriptio
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';

View File

@ -1,4 +1,5 @@
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
@ -7,7 +8,7 @@ import { QueueService } from './queue.service';
@Module({ @Module({
controllers: [QueueController], controllers: [QueueController],
imports: [DataGatheringModule], imports: [DataGatheringModule, PortfolioSnapshotQueueModule],
providers: [QueueService] providers: [QueueService]
}) })
export class QueueModule {} export class QueueModule {}

View File

@ -1,5 +1,6 @@
import { import {
DATA_GATHERING_QUEUE, DATA_GATHERING_QUEUE,
PORTFOLIO_SNAPSHOT_QUEUE,
QUEUE_JOB_STATUS_LIST QUEUE_JOB_STATUS_LIST
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { AdminJobs } from '@ghostfolio/common/interfaces'; import { AdminJobs } from '@ghostfolio/common/interfaces';
@ -12,11 +13,19 @@ import { JobStatus, Queue } from 'bull';
export class QueueService { export class QueueService {
public constructor( public constructor(
@InjectQueue(DATA_GATHERING_QUEUE) @InjectQueue(DATA_GATHERING_QUEUE)
private readonly dataGatheringQueue: Queue private readonly dataGatheringQueue: Queue,
@InjectQueue(PORTFOLIO_SNAPSHOT_QUEUE)
private readonly portfolioSnapshotQueue: Queue
) {} ) {}
public async deleteJob(aId: string) { public async deleteJob(aId: string) {
return (await this.dataGatheringQueue.getJob(aId))?.remove(); let job = await this.dataGatheringQueue.getJob(aId);
if (!job) {
job = await this.portfolioSnapshotQueue.getJob(aId);
}
return job?.remove();
} }
public async deleteJobs({ public async deleteJobs({
@ -25,15 +34,21 @@ export class QueueService {
status?: JobStatus[]; status?: JobStatus[];
}) { }) {
for (const statusItem of status) { for (const statusItem of status) {
await this.dataGatheringQueue.clean( const queueStatus = statusItem === 'waiting' ? 'wait' : statusItem;
300,
statusItem === 'waiting' ? 'wait' : statusItem await this.dataGatheringQueue.clean(300, queueStatus);
); await this.portfolioSnapshotQueue.clean(300, queueStatus);
} }
} }
public async executeJob(aId: string) { public async executeJob(aId: string) {
return (await this.dataGatheringQueue.getJob(aId))?.promote(); let job = await this.dataGatheringQueue.getJob(aId);
if (!job) {
job = await this.portfolioSnapshotQueue.getJob(aId);
}
return job?.promote();
} }
public async getJobs({ public async getJobs({
@ -43,10 +58,13 @@ export class QueueService {
limit?: number; limit?: number;
status?: JobStatus[]; status?: JobStatus[];
}): Promise<AdminJobs> { }): Promise<AdminJobs> {
const jobs = await this.dataGatheringQueue.getJobs(status); const [dataGatheringJobs, portfolioSnapshotJobs] = await Promise.all([
this.dataGatheringQueue.getJobs(status),
this.portfolioSnapshotQueue.getJobs(status)
]);
const jobsWithState = await Promise.all( const jobsWithState = await Promise.all(
jobs [...dataGatheringJobs, ...portfolioSnapshotJobs]
.filter((job) => { .filter((job) => {
return job; return job;
}) })

View File

@ -1,11 +1,12 @@
import { EventsModule } from '@ghostfolio/api/events/events.module'; import { EventsModule } from '@ghostfolio/api/events/events.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { CronService } from '@ghostfolio/api/services/cron.service'; import { CronService } from '@ghostfolio/api/services/cron.service';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module'; import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module';
import { import {
DEFAULT_LANGUAGE_CODE, DEFAULT_LANGUAGE_CODE,
@ -81,6 +82,7 @@ import { UserModule } from './user/user.module';
OrderModule, OrderModule,
PlatformModule, PlatformModule,
PortfolioModule, PortfolioModule,
PortfolioSnapshotQueueModule,
PrismaModule, PrismaModule,
PropertyModule, PropertyModule,
RedisCacheModule, RedisCacheModule,

View File

@ -7,10 +7,10 @@ import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.mo
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';

View File

@ -9,9 +9,9 @@ import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service'; import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config'; import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config';
import { import {

View File

@ -4,10 +4,10 @@ import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.mo
import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module'; import { TagModule } from '@ghostfolio/api/services/tag/tag.module';

View File

@ -4,8 +4,8 @@ import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_HIGH,

View File

@ -6,11 +6,11 @@ import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redac
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';

View File

@ -1,9 +1,9 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor'; import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_HIGH,

View File

@ -3,6 +3,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { Filter, HistoricalDataItem } from '@ghostfolio/common/interfaces'; import { Filter, HistoricalDataItem } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -22,6 +23,7 @@ export class PortfolioCalculatorFactory {
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly currentRateService: CurrentRateService, private readonly currentRateService: CurrentRateService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly portfolioSnapshotService: PortfolioSnapshotService,
private readonly redisCacheService: RedisCacheService private readonly redisCacheService: RedisCacheService
) {} ) {}
@ -51,6 +53,7 @@ export class PortfolioCalculatorFactory {
configurationService: this.configurationService, configurationService: this.configurationService,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
portfolioSnapshotService: this.portfolioSnapshotService,
redisCacheService: this.redisCacheService redisCacheService: this.redisCacheService
}); });
case PerformanceCalculationType.TWR: case PerformanceCalculationType.TWR:
@ -63,6 +66,7 @@ export class PortfolioCalculatorFactory {
userId, userId,
configurationService: this.configurationService, configurationService: this.configurationService,
exchangeRateDataService: this.exchangeRateDataService, exchangeRateDataService: this.exchangeRateDataService,
portfolioSnapshotService: this.portfolioSnapshotService,
redisCacheService: this.redisCacheService redisCacheService: this.redisCacheService
}); });
default: default:

View File

@ -10,8 +10,14 @@ import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { CACHE_TTL_INFINITE } from '@ghostfolio/common/config'; import {
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS,
PORTFOLIO_SNAPSHOT_QUEUE_PRIORITY_HIGH,
PORTFOLIO_SNAPSHOT_QUEUE_PRIORITY_LOW
} from '@ghostfolio/common/config';
import { import {
DATE_FORMAT, DATE_FORMAT,
getSum, getSum,
@ -34,7 +40,6 @@ import { Logger } from '@nestjs/common';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import { import {
addMilliseconds,
differenceInDays, differenceInDays,
eachDayOfInterval, eachDayOfInterval,
endOfDay, endOfDay,
@ -59,6 +64,7 @@ export abstract class PortfolioCalculator {
private endDate: Date; private endDate: Date;
private exchangeRateDataService: ExchangeRateDataService; private exchangeRateDataService: ExchangeRateDataService;
private filters: Filter[]; private filters: Filter[];
private portfolioSnapshotService: PortfolioSnapshotService;
private redisCacheService: RedisCacheService; private redisCacheService: RedisCacheService;
private snapshot: PortfolioSnapshot; private snapshot: PortfolioSnapshot;
private snapshotPromise: Promise<void>; private snapshotPromise: Promise<void>;
@ -74,6 +80,7 @@ export abstract class PortfolioCalculator {
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
filters, filters,
portfolioSnapshotService,
redisCacheService, redisCacheService,
userId userId
}: { }: {
@ -84,6 +91,7 @@ export abstract class PortfolioCalculator {
currentRateService: CurrentRateService; currentRateService: CurrentRateService;
exchangeRateDataService: ExchangeRateDataService; exchangeRateDataService: ExchangeRateDataService;
filters: Filter[]; filters: Filter[];
portfolioSnapshotService: PortfolioSnapshotService;
redisCacheService: RedisCacheService; redisCacheService: RedisCacheService;
userId: string; userId: string;
}) { }) {
@ -132,6 +140,7 @@ export abstract class PortfolioCalculator {
return a.date?.localeCompare(b.date); return a.date?.localeCompare(b.date);
}); });
this.portfolioSnapshotService = portfolioSnapshotService;
this.redisCacheService = redisCacheService; this.redisCacheService = redisCacheService;
this.userId = userId; this.userId = userId;
@ -153,7 +162,7 @@ export abstract class PortfolioCalculator {
): PortfolioSnapshot; ): PortfolioSnapshot;
@LogPerformance @LogPerformance
private async computeSnapshot(): Promise<PortfolioSnapshot> { public async computeSnapshot(): Promise<PortfolioSnapshot> {
const lastTransactionPoint = last(this.transactionPoints); const lastTransactionPoint = last(this.transactionPoints);
const transactionPoints = this.transactionPoints?.filter(({ date }) => { const transactionPoints = this.transactionPoints?.filter(({ date }) => {
@ -866,29 +875,6 @@ export abstract class PortfolioCalculator {
return chartDateMap; return chartDateMap;
} }
private async computeAndCacheSnapshot() {
const snapshot = await this.computeSnapshot();
const expiration = addMilliseconds(
new Date(),
this.configurationService.get('CACHE_QUOTES_TTL')
);
this.redisCacheService.set(
this.redisCacheService.getPortfolioSnapshotKey({
filters: this.filters,
userId: this.userId
}),
JSON.stringify(<PortfolioSnapshotValue>(<unknown>{
expiration: expiration.getTime(),
portfolioSnapshot: snapshot
})),
CACHE_TTL_INFINITE
);
return snapshot;
}
@LogPerformance @LogPerformance
private computeTransactionPoints() { private computeTransactionPoints() {
this.transactionPoints = []; this.transactionPoints = [];
@ -1034,6 +1020,7 @@ export abstract class PortfolioCalculator {
let cachedPortfolioSnapshot: PortfolioSnapshot; let cachedPortfolioSnapshot: PortfolioSnapshot;
let isCachedPortfolioSnapshotExpired = false; let isCachedPortfolioSnapshotExpired = false;
const jobId = this.userId;
try { try {
const cachedPortfolioSnapshotValue = await this.redisCacheService.get( const cachedPortfolioSnapshotValue = await this.redisCacheService.get(
@ -1069,11 +1056,43 @@ export abstract class PortfolioCalculator {
if (isCachedPortfolioSnapshotExpired) { if (isCachedPortfolioSnapshotExpired) {
// Compute in the background // Compute in the background
this.computeAndCacheSnapshot(); this.portfolioSnapshotService.addJobToQueue({
data: {
filters: this.filters,
userCurrency: this.currency,
userId: this.userId
},
name: PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
opts: {
...PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS,
jobId,
priority: PORTFOLIO_SNAPSHOT_QUEUE_PRIORITY_LOW
}
});
} }
} else { } else {
// Wait for computation // Wait for computation
this.snapshot = await this.computeAndCacheSnapshot(); await this.portfolioSnapshotService.addJobToQueue({
data: {
filters: this.filters,
userCurrency: this.currency,
userId: this.userId
},
name: PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
opts: {
...PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS,
jobId,
priority: PORTFOLIO_SNAPSHOT_QUEUE_PRIORITY_HIGH
}
});
const job = await this.portfolioSnapshotService.getJob(jobId);
if (job) {
await job.finished();
}
await this.initialize();
} }
} }
} }

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -118,14 +136,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -103,14 +121,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -88,14 +106,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -15,6 +15,8 @@ import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cac
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -29,6 +31,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -54,7 +68,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -69,12 +84,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -117,14 +135,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -88,14 +106,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),

View File

@ -15,6 +15,8 @@ import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cac
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -29,6 +31,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -54,7 +68,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -69,12 +84,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -101,14 +119,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -88,14 +106,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -27,6 +29,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -40,7 +54,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -55,12 +70,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -87,17 +105,18 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
userId: userDummyData.id userId: userDummyData.id
}); });
const liabilitiesInBaseCurrency = const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
await portfolioCalculator.getLiabilitiesInBaseCurrency();
expect(liabilitiesInBaseCurrency).toEqual(new Big(3000)); expect(portfolioSnapshot.totalLiabilitiesWithCurrencyEffect).toEqual(
new Big(3000)
);
}); });
}); });
}); });

View File

@ -15,6 +15,8 @@ import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cac
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock'; import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -29,6 +31,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -54,7 +68,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -69,12 +84,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -116,14 +134,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'USD', currency: 'USD',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
errors: [], errors: [],

View File

@ -9,11 +9,11 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { subDays } from 'date-fns';
import { last } from 'lodash';
jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
return { return {
@ -24,6 +24,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -37,7 +49,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -52,12 +65,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -66,14 +82,14 @@ describe('PortfolioCalculator', () => {
it('with no orders', async () => { it('with no orders', async () => {
jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime()); jest.useFakeTimers().setSystemTime(parseDate('2021-12-18').getTime());
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities: [], activities: [],
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -103,14 +121,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -14,6 +14,8 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock'; import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper'; import { parseDate } from '@ghostfolio/common/helper';
import { Big } from 'big.js'; import { Big } from 'big.js';
@ -28,6 +30,18 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
}; };
}); });
jest.mock(
'@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service',
() => {
return {
// eslint-disable-next-line @typescript-eslint/naming-convention
PortfolioSnapshotService: jest.fn().mockImplementation(() => {
return PortfolioSnapshotServiceMock;
})
};
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => { jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
return { return {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -41,7 +55,8 @@ describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -56,12 +71,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });
@ -103,14 +121,14 @@ describe('PortfolioCalculator', () => {
} }
]; ];
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = portfolioCalculatorFactory.createCalculator({
activities, activities,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF', currency: 'CHF',
userId: userDummyData.id userId: userDummyData.id
}); });
const portfolioSnapshot = await portfolioCalculator.getSnapshot(); const portfolioSnapshot = await portfolioCalculator.computeSnapshot();
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();

View File

@ -3,12 +3,14 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
describe('PortfolioCalculator', () => { describe('PortfolioCalculator', () => {
let configurationService: ConfigurationService; let configurationService: ConfigurationService;
let currentRateService: CurrentRateService; let currentRateService: CurrentRateService;
let exchangeRateDataService: ExchangeRateDataService; let exchangeRateDataService: ExchangeRateDataService;
let factory: PortfolioCalculatorFactory; let portfolioCalculatorFactory: PortfolioCalculatorFactory;
let portfolioSnapshotService: PortfolioSnapshotService;
let redisCacheService: RedisCacheService; let redisCacheService: RedisCacheService;
beforeEach(() => { beforeEach(() => {
@ -23,12 +25,15 @@ describe('PortfolioCalculator', () => {
null null
); );
portfolioSnapshotService = new PortfolioSnapshotService(null);
redisCacheService = new RedisCacheService(null, null); redisCacheService = new RedisCacheService(null, null);
factory = new PortfolioCalculatorFactory( portfolioCalculatorFactory = new PortfolioCalculatorFactory(
configurationService, configurationService,
currentRateService, currentRateService,
exchangeRateDataService, exchangeRateDataService,
portfolioSnapshotService,
redisCacheService redisCacheService
); );
}); });

View File

@ -10,12 +10,13 @@ import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module'; import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
@ -40,6 +41,7 @@ import { RulesService } from './rules.service';
MarketDataModule, MarketDataModule,
OrderModule, OrderModule,
PerformanceLoggingModule, PerformanceLoggingModule,
PortfolioSnapshotQueueModule,
PrismaModule, PrismaModule,
RedactValuesInResponseModule, RedactValuesInResponseModule,
RedisCacheModule, RedisCacheModule,

View File

@ -1,13 +1,28 @@
import { Filter } from '@ghostfolio/common/interfaces';
import { Milliseconds } from 'cache-manager'; import { Milliseconds } from 'cache-manager';
export const RedisCacheServiceMock = { export const RedisCacheServiceMock = {
cache: new Map<string, string>(),
get: (key: string): Promise<string> => { get: (key: string): Promise<string> => {
return Promise.resolve(null); const value = RedisCacheServiceMock.cache.get(key) || null;
return Promise.resolve(value);
}, },
getPortfolioSnapshotKey: (userId: string): string => { getPortfolioSnapshotKey: ({
return `portfolio-snapshot-${userId}`; filters,
userId
}: {
filters?: Filter[];
userId: string;
}): string => {
const filtersHash = filters?.length;
return `portfolio-snapshot-${userId}${filtersHash > 0 ? `-${filtersHash}` : ''}`;
}, },
set: (key: string, value: string, ttl?: Milliseconds): Promise<string> => { set: (key: string, value: string, ttl?: Milliseconds): Promise<string> => {
RedisCacheServiceMock.cache.set(key, value);
return Promise.resolve(value); return Promise.resolve(value);
} }
}; };

View File

@ -9,9 +9,9 @@ import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule'; import { Cron, CronExpression } from '@nestjs/schedule';
import { DataGatheringService } from './data-gathering/data-gathering.service';
import { ExchangeRateDataService } from './exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from './exchange-rate-data/exchange-rate-data.service';
import { PropertyService } from './property/property.service'; import { PropertyService } from './property/property.service';
import { DataGatheringService } from './queues/data-gathering/data-gathering.service';
import { TwitterBotService } from './twitter-bot/twitter-bot.service'; import { TwitterBotService } from './twitter-bot/twitter-bot.service';
@Injectable() @Injectable()

View File

@ -1,11 +1,11 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { DataEnhancerModule } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.module'; import { DataEnhancerModule } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { DATA_GATHERING_QUEUE } from '@ghostfolio/common/config'; import { DATA_GATHERING_QUEUE } from '@ghostfolio/common/config';

View File

@ -4,7 +4,7 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data/market-d
import { import {
DATA_GATHERING_QUEUE, DATA_GATHERING_QUEUE,
GATHER_ASSET_PROFILE_PROCESS, GATHER_ASSET_PROFILE_PROCESS,
GATHER_HISTORICAL_MARKET_DATA_PROCESS GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper'; import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
@ -58,7 +58,10 @@ export class DataGatheringProcessor {
} }
} }
@Process({ concurrency: 1, name: GATHER_HISTORICAL_MARKET_DATA_PROCESS }) @Process({
concurrency: 1,
name: GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME
})
public async gatherHistoricalMarketData(job: Job<IDataGatheringItem>) { public async gatherHistoricalMarketData(job: Job<IDataGatheringItem>) {
try { try {
const { dataSource, date, symbol } = job.data; const { dataSource, date, symbol } = job.data;
@ -69,7 +72,7 @@ export class DataGatheringProcessor {
currentDate, currentDate,
DATE_FORMAT DATE_FORMAT
)}`, )}`,
`DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS})` `DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME})`
); );
const historicalData = await this.dataProviderService.getHistoricalRaw({ const historicalData = await this.dataProviderService.getHistoricalRaw({
@ -123,12 +126,12 @@ export class DataGatheringProcessor {
currentDate, currentDate,
DATE_FORMAT DATE_FORMAT
)}`, )}`,
`DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS})` `DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME})`
); );
} catch (error) { } catch (error) {
Logger.error( Logger.error(
error, error,
`DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS})` `DataGatheringProcessor (${GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME})`
); );
throw new Error(error); throw new Error(error);

View File

@ -11,8 +11,8 @@ import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_HIGH,
DATA_GATHERING_QUEUE_PRIORITY_LOW, DATA_GATHERING_QUEUE_PRIORITY_LOW,
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM, DATA_GATHERING_QUEUE_PRIORITY_MEDIUM,
GATHER_HISTORICAL_MARKET_DATA_PROCESS, GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME,
GATHER_HISTORICAL_MARKET_DATA_PROCESS_OPTIONS, GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_OPTIONS,
PROPERTY_BENCHMARKS PROPERTY_BENCHMARKS
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { import {
@ -279,9 +279,9 @@ export class DataGatheringService {
date, date,
symbol symbol
}, },
name: GATHER_HISTORICAL_MARKET_DATA_PROCESS, name: GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME,
opts: { opts: {
...GATHER_HISTORICAL_MARKET_DATA_PROCESS_OPTIONS, ...GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_OPTIONS,
priority, priority,
jobId: `${getAssetProfileIdentifier({ jobId: `${getAssetProfileIdentifier({
dataSource, dataSource,

View File

@ -0,0 +1,7 @@
import { Filter } from '@ghostfolio/common/interfaces';
export interface IPortfolioSnapshotQueueJob {
filters: Filter[];
userCurrency: string;
userId: string;
}

View File

@ -0,0 +1,37 @@
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PORTFOLIO_SNAPSHOT_QUEUE } from '@ghostfolio/common/config';
import { BullModule } from '@nestjs/bull';
import { Module } from '@nestjs/common';
import { PortfolioSnapshotProcessor } from './portfolio-snapshot.processor';
@Module({
exports: [BullModule, PortfolioSnapshotService],
imports: [
BullModule.registerQueue({
name: PORTFOLIO_SNAPSHOT_QUEUE
}),
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
MarketDataModule,
OrderModule,
RedisCacheModule
],
providers: [
CurrentRateService,
PortfolioCalculatorFactory,
PortfolioSnapshotProcessor,
PortfolioSnapshotService
]
})
export class PortfolioSnapshotQueueModule {}

View File

@ -0,0 +1,96 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import {
PerformanceCalculationType,
PortfolioCalculatorFactory
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import {
CACHE_TTL_INFINITE,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,
PORTFOLIO_SNAPSHOT_QUEUE
} from '@ghostfolio/common/config';
import { Process, Processor } from '@nestjs/bull';
import { Injectable, Logger } from '@nestjs/common';
import { Job } from 'bull';
import { addMilliseconds } from 'date-fns';
import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface';
@Injectable()
@Processor(PORTFOLIO_SNAPSHOT_QUEUE)
export class PortfolioSnapshotProcessor {
public constructor(
private readonly calculatorFactory: PortfolioCalculatorFactory,
private readonly configurationService: ConfigurationService,
private readonly orderService: OrderService,
private readonly redisCacheService: RedisCacheService
) {}
@Process({ concurrency: 1, name: PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME })
public async calculatePortfolioSnapshot(
job: Job<IPortfolioSnapshotQueueJob>
) {
try {
const startTime = performance.now();
Logger.log(
`Portfolio snapshot calculation of user '${job.data.userId}' has been started`,
`PortfolioSnapshotProcessor (${PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME})`
);
const { activities } =
await this.orderService.getOrdersForPortfolioCalculator({
filters: job.data.filters,
userCurrency: job.data.userCurrency,
userId: job.data.userId
});
const portfolioCalculator = this.calculatorFactory.createCalculator({
activities,
calculationType: PerformanceCalculationType.TWR,
currency: job.data.userCurrency,
filters: job.data.filters,
userId: job.data.userId
});
const snapshot = await portfolioCalculator.computeSnapshot();
Logger.log(
`Portfolio snapshot calculation of user '${job.data.userId}' has been completed in ${(
(performance.now() - startTime) /
1000
).toFixed(3)} seconds`,
`PortfolioSnapshotProcessor (${PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME})`
);
const expiration = addMilliseconds(
new Date(),
this.configurationService.get('CACHE_QUOTES_TTL')
);
this.redisCacheService.set(
this.redisCacheService.getPortfolioSnapshotKey({
filters: job.data.filters,
userId: job.data.userId
}),
JSON.stringify(<PortfolioSnapshotValue>(<unknown>{
expiration: expiration.getTime(),
portfolioSnapshot: snapshot
})),
CACHE_TTL_INFINITE
);
return snapshot;
} catch (error) {
Logger.error(
error,
`PortfolioSnapshotProcessor (${PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME})`
);
throw new Error(error);
}
}
}

View File

@ -0,0 +1,34 @@
import { Job, JobOptions } from 'bull';
import { setTimeout } from 'timers/promises';
import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface';
export const PortfolioSnapshotServiceMock = {
addJobToQueue({
data,
name,
opts
}: {
data: IPortfolioSnapshotQueueJob;
name: string;
opts?: JobOptions;
}): Promise<Job<any>> {
const mockJob: Partial<Job<any>> = {
finished: async () => {
await setTimeout(100);
return Promise.resolve();
}
};
this.jobsStore.set(opts?.jobId, mockJob);
return Promise.resolve(mockJob as Job<any>);
},
getJob(jobId: string): Promise<Job<any>> {
const job = this.jobsStore.get(jobId);
return Promise.resolve(job as Job<any>);
},
jobsStore: new Map<string, Partial<Job<any>>>()
};

View File

@ -0,0 +1,31 @@
import { PORTFOLIO_SNAPSHOT_QUEUE } from '@ghostfolio/common/config';
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { JobOptions, Queue } from 'bull';
import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queue-job.interface';
@Injectable()
export class PortfolioSnapshotService {
public constructor(
@InjectQueue(PORTFOLIO_SNAPSHOT_QUEUE)
private readonly portfolioSnapshotQueue: Queue
) {}
public async addJobToQueue({
data,
name,
opts
}: {
data: IPortfolioSnapshotQueueJob;
name: string;
opts?: JobOptions;
}) {
return this.portfolioSnapshotQueue.add(name, data, opts);
}
public async getJob(jobId: string) {
return this.portfolioSnapshotQueue.getJob(jobId);
}
}

View File

@ -35,6 +35,8 @@
<ng-container i18n>Asset Profile</ng-container> <ng-container i18n>Asset Profile</ng-container>
} @else if (element.name === 'GATHER_HISTORICAL_MARKET_DATA') { } @else if (element.name === 'GATHER_HISTORICAL_MARKET_DATA') {
<ng-container i18n>Historical Market Data</ng-container> <ng-container i18n>Historical Market Data</ng-container>
} @else if (element.name === 'PORTFOLIO') {
<ng-container i18n>Portfolio Snapshot</ng-container>
} }
</td> </td>
</ng-container> </ng-container>

View File

@ -40,6 +40,10 @@ export const DATA_GATHERING_QUEUE_PRIORITY_MEDIUM = Math.round(
DATA_GATHERING_QUEUE_PRIORITY_LOW / 2 DATA_GATHERING_QUEUE_PRIORITY_LOW / 2
); );
export const PORTFOLIO_SNAPSHOT_QUEUE = 'PORTFOLIO_SNAPSHOT_QUEUE';
export const PORTFOLIO_SNAPSHOT_QUEUE_PRIORITY_HIGH = 1;
export const PORTFOLIO_SNAPSHOT_QUEUE_PRIORITY_LOW = Number.MAX_SAFE_INTEGER;
export const DEFAULT_CURRENCY = 'USD'; export const DEFAULT_CURRENCY = 'USD';
export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy';
export const DEFAULT_LANGUAGE_CODE = 'en'; export const DEFAULT_LANGUAGE_CODE = 'en';
@ -76,9 +80,10 @@ export const GATHER_ASSET_PROFILE_PROCESS_OPTIONS: JobOptions = {
}, },
removeOnComplete: true removeOnComplete: true
}; };
export const GATHER_HISTORICAL_MARKET_DATA_PROCESS =
export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_NAME =
'GATHER_HISTORICAL_MARKET_DATA'; 'GATHER_HISTORICAL_MARKET_DATA';
export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_OPTIONS: JobOptions = { export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_JOB_OPTIONS: JobOptions = {
attempts: 12, attempts: 12,
backoff: { backoff: {
delay: ms('1 minute'), delay: ms('1 minute'),
@ -87,6 +92,11 @@ export const GATHER_HISTORICAL_MARKET_DATA_PROCESS_OPTIONS: JobOptions = {
removeOnComplete: true removeOnComplete: true
}; };
export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME = 'PORTFOLIO';
export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS: JobOptions = {
removeOnComplete: true
};
export const HEADER_KEY_IMPERSONATION = 'Impersonation-Id'; export const HEADER_KEY_IMPERSONATION = 'Impersonation-Id';
export const HEADER_KEY_TIMEZONE = 'Timezone'; export const HEADER_KEY_TIMEZONE = 'Timezone';
export const HEADER_KEY_TOKEN = 'Authorization'; export const HEADER_KEY_TOKEN = 'Authorization';