Feature/set up portfolio snapshot queue (#3725)
* Set up portfolio snapshot queue * Update changelog
This commit is contained in:
parent
383a02519a
commit
403ee2741d
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
@ -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 {}
|
||||||
|
@ -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;
|
||||||
})
|
})
|
||||||
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
@ -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 {
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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';
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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'),
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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'),
|
||||||
|
@ -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)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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: [],
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -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()
|
||||||
|
@ -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';
|
||||||
|
|
@ -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);
|
@ -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,
|
@ -0,0 +1,7 @@
|
|||||||
|
import { Filter } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
|
export interface IPortfolioSnapshotQueueJob {
|
||||||
|
filters: Filter[];
|
||||||
|
userCurrency: string;
|
||||||
|
userId: string;
|
||||||
|
}
|
@ -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 {}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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>>>()
|
||||||
|
};
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user