Feature/set up caching in portfolio calculator (#3335)
* Set up caching * Update changelog
This commit is contained in:
parent
cd07802400
commit
4f41bac328
@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Extended the content of the _Self-Hosting_ section by the custom asset instructions on the Frequently Asked Questions (FAQ) page
|
- Extended the content of the _Self-Hosting_ section by the custom asset instructions on the Frequently Asked Questions (FAQ) page
|
||||||
- Set up an event system to follow portfolio changes
|
- Added the caching to the portfolio calculator (experimental)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
||||||
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
|
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
import {
|
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||||
SymbolMetrics,
|
|
||||||
TimelinePosition,
|
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
|
||||||
|
|
||||||
export class MWRPortfolioCalculator extends PortfolioCalculator {
|
export class MWRPortfolioCalculator extends PortfolioCalculator {
|
||||||
protected calculateOverallPerformance(
|
protected calculateOverallPerformance(
|
||||||
|
@ -24,3 +24,7 @@ export const symbolProfileDummyData = {
|
|||||||
sectors: [],
|
sectors: [],
|
||||||
updatedAt: undefined
|
updatedAt: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const userDummyData = {
|
||||||
|
id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
||||||
|
};
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.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 { HistoricalDataItem } from '@ghostfolio/common/interfaces';
|
import { HistoricalDataItem } from '@ghostfolio/common/interfaces';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange, UserWithSettings } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
@ -18,8 +20,10 @@ export enum PerformanceCalculationType {
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class PortfolioCalculatorFactory {
|
export class PortfolioCalculatorFactory {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private readonly configurationService: ConfigurationService,
|
||||||
private readonly currentRateService: CurrentRateService,
|
private readonly currentRateService: CurrentRateService,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
|
private readonly redisCacheService: RedisCacheService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public createCalculator({
|
public createCalculator({
|
||||||
@ -27,13 +31,17 @@ export class PortfolioCalculatorFactory {
|
|||||||
activities,
|
activities,
|
||||||
calculationType,
|
calculationType,
|
||||||
currency,
|
currency,
|
||||||
dateRange = 'max'
|
dateRange = 'max',
|
||||||
|
isExperimentalFeatures = false,
|
||||||
|
userId
|
||||||
}: {
|
}: {
|
||||||
accountBalanceItems?: HistoricalDataItem[];
|
accountBalanceItems?: HistoricalDataItem[];
|
||||||
activities: Activity[];
|
activities: Activity[];
|
||||||
calculationType: PerformanceCalculationType;
|
calculationType: PerformanceCalculationType;
|
||||||
currency: string;
|
currency: string;
|
||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
|
isExperimentalFeatures?: boolean;
|
||||||
|
userId: string;
|
||||||
}): PortfolioCalculator {
|
}): PortfolioCalculator {
|
||||||
switch (calculationType) {
|
switch (calculationType) {
|
||||||
case PerformanceCalculationType.MWR:
|
case PerformanceCalculationType.MWR:
|
||||||
@ -42,8 +50,12 @@ export class PortfolioCalculatorFactory {
|
|||||||
activities,
|
activities,
|
||||||
currency,
|
currency,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
isExperimentalFeatures,
|
||||||
|
userId,
|
||||||
|
configurationService: this.configurationService,
|
||||||
currentRateService: this.currentRateService,
|
currentRateService: this.currentRateService,
|
||||||
exchangeRateDataService: this.exchangeRateDataService
|
exchangeRateDataService: this.exchangeRateDataService,
|
||||||
|
redisCacheService: this.redisCacheService
|
||||||
});
|
});
|
||||||
case PerformanceCalculationType.TWR:
|
case PerformanceCalculationType.TWR:
|
||||||
return new TWRPortfolioCalculator({
|
return new TWRPortfolioCalculator({
|
||||||
@ -52,7 +64,11 @@ export class PortfolioCalculatorFactory {
|
|||||||
currency,
|
currency,
|
||||||
currentRateService: this.currentRateService,
|
currentRateService: this.currentRateService,
|
||||||
dateRange,
|
dateRange,
|
||||||
exchangeRateDataService: this.exchangeRateDataService
|
isExperimentalFeatures,
|
||||||
|
userId,
|
||||||
|
configurationService: this.configurationService,
|
||||||
|
exchangeRateDataService: this.exchangeRateDataService,
|
||||||
|
redisCacheService: this.redisCacheService
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid calculation type');
|
throw new Error('Invalid calculation type');
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
||||||
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
|
|
||||||
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
|
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
|
||||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
import {
|
import {
|
||||||
getFactor,
|
getFactor,
|
||||||
getInterval
|
getInterval
|
||||||
} from '@ghostfolio/api/helper/portfolio.helper';
|
} from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
|
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 { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
|
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
|
||||||
@ -23,12 +24,14 @@ import {
|
|||||||
InvestmentItem,
|
InvestmentItem,
|
||||||
ResponseError,
|
ResponseError,
|
||||||
SymbolMetrics,
|
SymbolMetrics,
|
||||||
TimelinePosition,
|
|
||||||
UniqueAsset
|
UniqueAsset
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||||
import { DateRange, GroupBy } from '@ghostfolio/common/types';
|
import { DateRange, GroupBy } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
differenceInDays,
|
differenceInDays,
|
||||||
eachDayOfInterval,
|
eachDayOfInterval,
|
||||||
@ -41,6 +44,7 @@ import {
|
|||||||
subDays
|
subDays
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { first, last, uniq, uniqBy } from 'lodash';
|
import { first, last, uniq, uniqBy } from 'lodash';
|
||||||
|
import ms from 'ms';
|
||||||
|
|
||||||
export abstract class PortfolioCalculator {
|
export abstract class PortfolioCalculator {
|
||||||
protected static readonly ENABLE_LOGGING = false;
|
protected static readonly ENABLE_LOGGING = false;
|
||||||
@ -48,52 +52,78 @@ export abstract class PortfolioCalculator {
|
|||||||
protected accountBalanceItems: HistoricalDataItem[];
|
protected accountBalanceItems: HistoricalDataItem[];
|
||||||
protected orders: PortfolioOrder[];
|
protected orders: PortfolioOrder[];
|
||||||
|
|
||||||
|
private configurationService: ConfigurationService;
|
||||||
private currency: string;
|
private currency: string;
|
||||||
private currentRateService: CurrentRateService;
|
private currentRateService: CurrentRateService;
|
||||||
private dataProviderInfos: DataProviderInfo[];
|
private dataProviderInfos: DataProviderInfo[];
|
||||||
private endDate: Date;
|
private endDate: Date;
|
||||||
private exchangeRateDataService: ExchangeRateDataService;
|
private exchangeRateDataService: ExchangeRateDataService;
|
||||||
|
private isExperimentalFeatures: boolean;
|
||||||
|
private redisCacheService: RedisCacheService;
|
||||||
private snapshot: PortfolioSnapshot;
|
private snapshot: PortfolioSnapshot;
|
||||||
private snapshotPromise: Promise<void>;
|
private snapshotPromise: Promise<void>;
|
||||||
private startDate: Date;
|
private startDate: Date;
|
||||||
private transactionPoints: TransactionPoint[];
|
private transactionPoints: TransactionPoint[];
|
||||||
|
private userId: string;
|
||||||
|
|
||||||
public constructor({
|
public constructor({
|
||||||
accountBalanceItems,
|
accountBalanceItems,
|
||||||
activities,
|
activities,
|
||||||
|
configurationService,
|
||||||
currency,
|
currency,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
dateRange,
|
dateRange,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
isExperimentalFeatures,
|
||||||
|
redisCacheService,
|
||||||
|
userId
|
||||||
}: {
|
}: {
|
||||||
accountBalanceItems: HistoricalDataItem[];
|
accountBalanceItems: HistoricalDataItem[];
|
||||||
activities: Activity[];
|
activities: Activity[];
|
||||||
|
configurationService: ConfigurationService;
|
||||||
currency: string;
|
currency: string;
|
||||||
currentRateService: CurrentRateService;
|
currentRateService: CurrentRateService;
|
||||||
dateRange: DateRange;
|
dateRange: DateRange;
|
||||||
exchangeRateDataService: ExchangeRateDataService;
|
exchangeRateDataService: ExchangeRateDataService;
|
||||||
|
isExperimentalFeatures: boolean;
|
||||||
|
redisCacheService: RedisCacheService;
|
||||||
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
this.accountBalanceItems = accountBalanceItems;
|
this.accountBalanceItems = accountBalanceItems;
|
||||||
|
this.configurationService = configurationService;
|
||||||
this.currency = currency;
|
this.currency = currency;
|
||||||
this.currentRateService = currentRateService;
|
this.currentRateService = currentRateService;
|
||||||
this.exchangeRateDataService = exchangeRateDataService;
|
this.exchangeRateDataService = exchangeRateDataService;
|
||||||
this.orders = activities.map(
|
this.isExperimentalFeatures = isExperimentalFeatures;
|
||||||
({ date, fee, quantity, SymbolProfile, tags = [], type, unitPrice }) => {
|
|
||||||
return {
|
|
||||||
SymbolProfile,
|
|
||||||
tags,
|
|
||||||
type,
|
|
||||||
date: format(date, DATE_FORMAT),
|
|
||||||
fee: new Big(fee),
|
|
||||||
quantity: new Big(quantity),
|
|
||||||
unitPrice: new Big(unitPrice)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.orders.sort((a, b) => {
|
this.orders = activities
|
||||||
return a.date?.localeCompare(b.date);
|
.map(
|
||||||
});
|
({
|
||||||
|
date,
|
||||||
|
fee,
|
||||||
|
quantity,
|
||||||
|
SymbolProfile,
|
||||||
|
tags = [],
|
||||||
|
type,
|
||||||
|
unitPrice
|
||||||
|
}) => {
|
||||||
|
return {
|
||||||
|
SymbolProfile,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
date: format(date, DATE_FORMAT),
|
||||||
|
fee: new Big(fee),
|
||||||
|
quantity: new Big(quantity),
|
||||||
|
unitPrice: new Big(unitPrice)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
return a.date?.localeCompare(b.date);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.redisCacheService = redisCacheService;
|
||||||
|
this.userId = userId;
|
||||||
|
|
||||||
const { endDate, startDate } = getInterval(dateRange);
|
const { endDate, startDate } = getInterval(dateRange);
|
||||||
|
|
||||||
@ -1011,6 +1041,48 @@ export abstract class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initialize() {
|
private async initialize() {
|
||||||
this.snapshot = await this.computeSnapshot(this.startDate, this.endDate);
|
if (this.isExperimentalFeatures) {
|
||||||
|
const startTimeTotal = performance.now();
|
||||||
|
|
||||||
|
const cachedSnapshot = await this.redisCacheService.get(
|
||||||
|
this.redisCacheService.getPortfolioSnapshotKey(this.userId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cachedSnapshot) {
|
||||||
|
this.snapshot = plainToClass(
|
||||||
|
PortfolioSnapshot,
|
||||||
|
JSON.parse(cachedSnapshot)
|
||||||
|
);
|
||||||
|
|
||||||
|
Logger.debug(
|
||||||
|
`Fetched portfolio snapshot from cache in ${(
|
||||||
|
(performance.now() - startTimeTotal) /
|
||||||
|
1000
|
||||||
|
).toFixed(3)} seconds`,
|
||||||
|
'PortfolioCalculator'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.snapshot = await this.computeSnapshot(
|
||||||
|
this.startDate,
|
||||||
|
this.endDate
|
||||||
|
);
|
||||||
|
|
||||||
|
this.redisCacheService.set(
|
||||||
|
this.redisCacheService.getPortfolioSnapshotKey(this.userId),
|
||||||
|
JSON.stringify(this.snapshot),
|
||||||
|
this.configurationService.get('CACHE_QUOTES_TTL')
|
||||||
|
);
|
||||||
|
|
||||||
|
Logger.debug(
|
||||||
|
`Computed portfolio snapshot in ${(
|
||||||
|
(performance.now() - startTimeTotal) /
|
||||||
|
1000
|
||||||
|
).toFixed(3)} seconds`,
|
||||||
|
'PortfolioCalculator'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.snapshot = await this.computeSnapshot(this.startDate, this.endDate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -101,7 +122,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PerformanceCalculationType,
|
PerformanceCalculationType,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,7 +107,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
@ -24,6 +28,15 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
||||||
() => {
|
() => {
|
||||||
@ -37,11 +50,15 @@ jest.mock(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -51,9 +68,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -99,7 +120,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'USD'
|
currency: 'USD',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
@ -24,6 +28,15 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
||||||
() => {
|
() => {
|
||||||
@ -37,11 +50,15 @@ jest.mock(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -51,9 +68,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,7 +105,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'USD'
|
currency: 'USD',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PortfolioCalculatorFactory,
|
PortfolioCalculatorFactory,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'USD'
|
currency: 'USD',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PerformanceCalculationType,
|
PerformanceCalculationType,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
@ -24,6 +28,15 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock(
|
jest.mock(
|
||||||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
||||||
() => {
|
() => {
|
||||||
@ -37,11 +50,15 @@ jest.mock(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -51,9 +68,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -99,7 +120,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'USD'
|
currency: 'USD',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PerformanceCalculationType,
|
PerformanceCalculationType,
|
||||||
PortfolioCalculatorFactory
|
PortfolioCalculatorFactory
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -19,12 +23,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -34,9 +51,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,7 +70,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities: [],
|
activities: [],
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const start = subDays(new Date(Date.now()), 10);
|
const start = subDays(new Date(Date.now()), 10);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PerformanceCalculationType,
|
PerformanceCalculationType,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,7 +107,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import {
|
import {
|
||||||
activityDummyData,
|
activityDummyData,
|
||||||
symbolProfileDummyData
|
symbolProfileDummyData,
|
||||||
|
userDummyData
|
||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||||
import {
|
import {
|
||||||
PerformanceCalculationType,
|
PerformanceCalculationType,
|
||||||
@ -9,6 +10,9 @@ import {
|
|||||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
|
||||||
|
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 { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
@ -23,12 +27,25 @@ jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
|
||||||
|
return {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
RedisCacheService: jest.fn().mockImplementation(() => {
|
||||||
|
return RedisCacheServiceMock;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,7 +107,8 @@ describe('PortfolioCalculator', () => {
|
|||||||
const portfolioCalculator = factory.createCalculator({
|
const portfolioCalculator = factory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: 'CHF'
|
currency: 'CHF',
|
||||||
|
userId: userDummyData.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const chartData = await portfolioCalculator.getChartData({
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.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';
|
||||||
|
|
||||||
describe('PortfolioCalculator', () => {
|
describe('PortfolioCalculator', () => {
|
||||||
|
let configurationService: ConfigurationService;
|
||||||
let currentRateService: CurrentRateService;
|
let currentRateService: CurrentRateService;
|
||||||
let exchangeRateDataService: ExchangeRateDataService;
|
let exchangeRateDataService: ExchangeRateDataService;
|
||||||
let factory: PortfolioCalculatorFactory;
|
let factory: PortfolioCalculatorFactory;
|
||||||
|
let redisCacheService: RedisCacheService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
configurationService = new ConfigurationService();
|
||||||
|
|
||||||
currentRateService = new CurrentRateService(null, null, null, null);
|
currentRateService = new CurrentRateService(null, null, null, null);
|
||||||
|
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
@ -17,9 +23,13 @@ describe('PortfolioCalculator', () => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
redisCacheService = new RedisCacheService(null, null);
|
||||||
|
|
||||||
factory = new PortfolioCalculatorFactory(
|
factory = new PortfolioCalculatorFactory(
|
||||||
|
configurationService,
|
||||||
currentRateService,
|
currentRateService,
|
||||||
exchangeRateDataService
|
exchangeRateDataService,
|
||||||
|
redisCacheService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
||||||
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
|
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
|
||||||
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
|
|
||||||
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
import {
|
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
SymbolMetrics,
|
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||||
TimelinePosition,
|
|
||||||
UniqueAsset
|
|
||||||
} from '@ghostfolio/common/interfaces';
|
|
||||||
|
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
|
|
||||||
|
|
||||||
import { Big } from 'big.js';
|
|
||||||
|
|
||||||
export interface PortfolioSnapshot extends ResponseError {
|
|
||||||
currentValueInBaseCurrency: Big;
|
|
||||||
grossPerformance: Big;
|
|
||||||
grossPerformanceWithCurrencyEffect: Big;
|
|
||||||
grossPerformancePercentage: Big;
|
|
||||||
grossPerformancePercentageWithCurrencyEffect: Big;
|
|
||||||
netAnnualizedPerformance?: Big;
|
|
||||||
netAnnualizedPerformanceWithCurrencyEffect?: Big;
|
|
||||||
netPerformance: Big;
|
|
||||||
netPerformanceWithCurrencyEffect: Big;
|
|
||||||
netPerformancePercentage: Big;
|
|
||||||
netPerformancePercentageWithCurrencyEffect: Big;
|
|
||||||
positions: TimelinePosition[];
|
|
||||||
totalFeesWithCurrencyEffect: Big;
|
|
||||||
totalInterestWithCurrencyEffect: Big;
|
|
||||||
totalInvestment: Big;
|
|
||||||
totalInvestmentWithCurrencyEffect: Big;
|
|
||||||
totalLiabilitiesWithCurrencyEffect: Big;
|
|
||||||
totalValuablesWithCurrencyEffect: Big;
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module';
|
|||||||
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
|
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
|
||||||
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
import { UserModule } from '@ghostfolio/api/app/user/user.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';
|
||||||
@ -35,6 +36,7 @@ import { RulesService } from './rules.service';
|
|||||||
MarketDataModule,
|
MarketDataModule,
|
||||||
OrderModule,
|
OrderModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
|
RedisCacheModule,
|
||||||
SymbolProfileModule,
|
SymbolProfileModule,
|
||||||
UserModule
|
UserModule
|
||||||
],
|
],
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
EnhancedSymbolProfile,
|
EnhancedSymbolProfile,
|
||||||
Filter,
|
Filter,
|
||||||
HistoricalDataItem,
|
HistoricalDataItem,
|
||||||
|
InvestmentItem,
|
||||||
PortfolioDetails,
|
PortfolioDetails,
|
||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
@ -36,10 +37,9 @@ import {
|
|||||||
PortfolioReport,
|
PortfolioReport,
|
||||||
PortfolioSummary,
|
PortfolioSummary,
|
||||||
Position,
|
Position,
|
||||||
TimelinePosition,
|
|
||||||
UserSettings
|
UserSettings
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||||
import type {
|
import type {
|
||||||
AccountWithValue,
|
AccountWithValue,
|
||||||
DateRange,
|
DateRange,
|
||||||
@ -277,8 +277,11 @@ export class PortfolioService {
|
|||||||
|
|
||||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
|
userId,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: this.request.user.Settings.settings.baseCurrency
|
currency: this.request.user.Settings.settings.baseCurrency,
|
||||||
|
isExperimentalFeatures:
|
||||||
|
this.request.user.Settings.settings.isExperimentalFeatures
|
||||||
});
|
});
|
||||||
|
|
||||||
const items = await portfolioCalculator.getChart({
|
const items = await portfolioCalculator.getChart({
|
||||||
@ -352,8 +355,11 @@ export class PortfolioService {
|
|||||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
userId,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: userCurrency
|
currency: userCurrency,
|
||||||
|
isExperimentalFeatures:
|
||||||
|
this.request.user.Settings.settings.isExperimentalFeatures
|
||||||
});
|
});
|
||||||
|
|
||||||
const { currentValueInBaseCurrency, hasErrors, positions } =
|
const { currentValueInBaseCurrency, hasErrors, positions } =
|
||||||
@ -648,11 +654,14 @@ export class PortfolioService {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||||
|
userId,
|
||||||
activities: orders.filter((order) => {
|
activities: orders.filter((order) => {
|
||||||
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
|
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
|
||||||
}),
|
}),
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: userCurrency
|
currency: userCurrency,
|
||||||
|
isExperimentalFeatures:
|
||||||
|
this.request.user.Settings.settings.isExperimentalFeatures
|
||||||
});
|
});
|
||||||
|
|
||||||
const portfolioStart = portfolioCalculator.getStartDate();
|
const portfolioStart = portfolioCalculator.getStartDate();
|
||||||
@ -919,8 +928,11 @@ export class PortfolioService {
|
|||||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
userId,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: this.request.user.Settings.settings.baseCurrency
|
currency: this.request.user.Settings.settings.baseCurrency,
|
||||||
|
isExperimentalFeatures:
|
||||||
|
this.request.user.Settings.settings.isExperimentalFeatures
|
||||||
});
|
});
|
||||||
|
|
||||||
let { hasErrors, positions } = await portfolioCalculator.getSnapshot();
|
let { hasErrors, positions } = await portfolioCalculator.getSnapshot();
|
||||||
@ -1108,8 +1120,11 @@ export class PortfolioService {
|
|||||||
accountBalanceItems,
|
accountBalanceItems,
|
||||||
activities,
|
activities,
|
||||||
dateRange,
|
dateRange,
|
||||||
|
userId,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: userCurrency
|
currency: userCurrency,
|
||||||
|
isExperimentalFeatures:
|
||||||
|
this.request.user.Settings.settings.isExperimentalFeatures
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -1202,8 +1217,11 @@ export class PortfolioService {
|
|||||||
|
|
||||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||||
activities,
|
activities,
|
||||||
|
userId,
|
||||||
calculationType: PerformanceCalculationType.TWR,
|
calculationType: PerformanceCalculationType.TWR,
|
||||||
currency: this.request.user.Settings.settings.baseCurrency
|
currency: this.request.user.Settings.settings.baseCurrency,
|
||||||
|
isExperimentalFeatures:
|
||||||
|
this.request.user.Settings.settings.isExperimentalFeatures
|
||||||
});
|
});
|
||||||
|
|
||||||
let { totalFeesWithCurrencyEffect, positions, totalInvestment } =
|
let { totalFeesWithCurrencyEffect, positions, totalInvestment } =
|
||||||
|
13
apps/api/src/app/redis-cache/redis-cache.service.mock.ts
Normal file
13
apps/api/src/app/redis-cache/redis-cache.service.mock.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { RedisCacheService } from './redis-cache.service';
|
||||||
|
|
||||||
|
export const RedisCacheServiceMock = {
|
||||||
|
get: (key: string): Promise<string> => {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
getPortfolioSnapshotKey: (userId: string): string => {
|
||||||
|
return `portfolio-snapshot-${userId}`;
|
||||||
|
},
|
||||||
|
set: (key: string, value: string, ttlInSeconds?: number): Promise<string> => {
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
};
|
@ -24,6 +24,10 @@ export class RedisCacheService {
|
|||||||
return this.cache.get(key);
|
return this.cache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPortfolioSnapshotKey(userId: string) {
|
||||||
|
return `portfolio-snapshot-${userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
public getQuoteKey({ dataSource, symbol }: UniqueAsset) {
|
public getQuoteKey({ dataSource, symbol }: UniqueAsset) {
|
||||||
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
|
return `quote-${getAssetProfileIdentifier({ dataSource, symbol })}`;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { PortfolioChangedListener } from './portfolio-changed.listener';
|
import { PortfolioChangedListener } from './portfolio-changed.listener';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [RedisCacheModule],
|
||||||
providers: [PortfolioChangedListener]
|
providers: [PortfolioChangedListener]
|
||||||
})
|
})
|
||||||
export class EventsModule {}
|
export class EventsModule {}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
@ -5,11 +7,17 @@ import { PortfolioChangedEvent } from './portfolio-changed.event';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PortfolioChangedListener {
|
export class PortfolioChangedListener {
|
||||||
|
public constructor(private readonly redisCacheService: RedisCacheService) {}
|
||||||
|
|
||||||
@OnEvent(PortfolioChangedEvent.getName())
|
@OnEvent(PortfolioChangedEvent.getName())
|
||||||
handlePortfolioChangedEvent(event: PortfolioChangedEvent) {
|
handlePortfolioChangedEvent(event: PortfolioChangedEvent) {
|
||||||
Logger.log(
|
Logger.log(
|
||||||
`Portfolio of user with id ${event.getUserId()} has changed`,
|
`Portfolio of user with id ${event.getUserId()} has changed`,
|
||||||
'PortfolioChangedListener'
|
'PortfolioChangedListener'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.redisCacheService.remove(
|
||||||
|
this.redisCacheService.getPortfolioSnapshotKey(event.getUserId())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
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 { groupBy } from '@ghostfolio/common/helper';
|
import { groupBy } from '@ghostfolio/common/helper';
|
||||||
import { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces';
|
import { UserSettings } from '@ghostfolio/common/interfaces';
|
||||||
|
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
import { EvaluationResult } from './interfaces/evaluation-result.interface';
|
import { EvaluationResult } from './interfaces/evaluation-result.interface';
|
||||||
import { RuleInterface } from './interfaces/rule.interface';
|
import { RuleInterface } from './interfaces/rule.interface';
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
import { Rule } from '@ghostfolio/api/models/rule';
|
import { Rule } from '@ghostfolio/api/models/rule';
|
||||||
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 { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces';
|
import { UserSettings } from '@ghostfolio/common/interfaces';
|
||||||
|
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
|
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
|
||||||
private positions: TimelinePosition[];
|
private positions: TimelinePosition[];
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||||
import { Rule } from '@ghostfolio/api/models/rule';
|
import { Rule } from '@ghostfolio/api/models/rule';
|
||||||
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 { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces';
|
import { UserSettings } from '@ghostfolio/common/interfaces';
|
||||||
|
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||||
private positions: TimelinePosition[];
|
private positions: TimelinePosition[];
|
||||||
|
@ -399,7 +399,8 @@ export class DataProviderService {
|
|||||||
numberOfItemsInCache > 1 ? 's' : ''
|
numberOfItemsInCache > 1 ? 's' : ''
|
||||||
} from cache in ${((performance.now() - startTimeTotal) / 1000).toFixed(
|
} from cache in ${((performance.now() - startTimeTotal) / 1000).toFixed(
|
||||||
3
|
3
|
||||||
)} seconds`
|
)} seconds`,
|
||||||
|
'DataProviderService'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +506,8 @@ export class DataProviderService {
|
|||||||
} from ${dataSource} in ${(
|
} from ${dataSource} in ${(
|
||||||
(performance.now() - startTimeDataSource) /
|
(performance.now() - startTimeDataSource) /
|
||||||
1000
|
1000
|
||||||
).toFixed(3)} seconds`
|
).toFixed(3)} seconds`,
|
||||||
|
'DataProviderService'
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -535,14 +537,15 @@ export class DataProviderService {
|
|||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
Logger.debug('------------------------------------------------');
|
Logger.debug('--------------------------------------------------------');
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
`Fetched ${items.length} quote${items.length > 1 ? 's' : ''} in ${(
|
`Fetched ${items.length} quote${items.length > 1 ? 's' : ''} in ${(
|
||||||
(performance.now() - startTimeTotal) /
|
(performance.now() - startTimeTotal) /
|
||||||
1000
|
1000
|
||||||
).toFixed(3)} seconds`
|
).toFixed(3)} seconds`,
|
||||||
|
'DataProviderService'
|
||||||
);
|
);
|
||||||
Logger.debug('================================================');
|
Logger.debug('========================================================');
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
9
libs/common/src/lib/class-transformer.ts
Normal file
9
libs/common/src/lib/class-transformer.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Big } from 'big.js';
|
||||||
|
|
||||||
|
export function transformToBig({ value }: { value: string }): Big {
|
||||||
|
if (value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Big(value);
|
||||||
|
}
|
@ -48,7 +48,6 @@ import type { Subscription } from './subscription.interface';
|
|||||||
import type { SymbolMetrics } from './symbol-metrics.interface';
|
import type { SymbolMetrics } from './symbol-metrics.interface';
|
||||||
import type { SystemMessage } from './system-message.interface';
|
import type { SystemMessage } from './system-message.interface';
|
||||||
import type { TabConfiguration } from './tab-configuration.interface';
|
import type { TabConfiguration } from './tab-configuration.interface';
|
||||||
import type { TimelinePosition } from './timeline-position.interface';
|
|
||||||
import type { UniqueAsset } from './unique-asset.interface';
|
import type { UniqueAsset } from './unique-asset.interface';
|
||||||
import type { UserSettings } from './user-settings.interface';
|
import type { UserSettings } from './user-settings.interface';
|
||||||
import type { User } from './user.interface';
|
import type { User } from './user.interface';
|
||||||
@ -102,7 +101,6 @@ export {
|
|||||||
Subscription,
|
Subscription,
|
||||||
SymbolMetrics,
|
SymbolMetrics,
|
||||||
TabConfiguration,
|
TabConfiguration,
|
||||||
TimelinePosition,
|
|
||||||
UniqueAsset,
|
UniqueAsset,
|
||||||
User,
|
User,
|
||||||
UserSettings
|
UserSettings
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
import { DataSource, Tag } from '@prisma/client';
|
|
||||||
import { Big } from 'big.js';
|
|
||||||
|
|
||||||
export interface TimelinePosition {
|
|
||||||
averagePrice: Big;
|
|
||||||
currency: string;
|
|
||||||
dataSource: DataSource;
|
|
||||||
dividend: Big;
|
|
||||||
dividendInBaseCurrency: Big;
|
|
||||||
fee: Big;
|
|
||||||
firstBuyDate: string;
|
|
||||||
grossPerformance: Big;
|
|
||||||
grossPerformancePercentage: Big;
|
|
||||||
grossPerformancePercentageWithCurrencyEffect: Big;
|
|
||||||
grossPerformanceWithCurrencyEffect: Big;
|
|
||||||
investment: Big;
|
|
||||||
investmentWithCurrencyEffect: Big;
|
|
||||||
marketPrice: number;
|
|
||||||
marketPriceInBaseCurrency: number;
|
|
||||||
netPerformance: Big;
|
|
||||||
netPerformancePercentage: Big;
|
|
||||||
netPerformancePercentageWithCurrencyEffect: Big;
|
|
||||||
netPerformanceWithCurrencyEffect: Big;
|
|
||||||
quantity: Big;
|
|
||||||
symbol: string;
|
|
||||||
tags?: Tag[];
|
|
||||||
timeWeightedInvestment: Big;
|
|
||||||
timeWeightedInvestmentWithCurrencyEffect: Big;
|
|
||||||
transactionCount: number;
|
|
||||||
valueInBaseCurrency: Big;
|
|
||||||
}
|
|
4
libs/common/src/lib/models/index.ts
Normal file
4
libs/common/src/lib/models/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { PortfolioSnapshot } from './portfolio-snapshot';
|
||||||
|
import { TimelinePosition } from './timeline-position';
|
||||||
|
|
||||||
|
export { PortfolioSnapshot, TimelinePosition };
|
82
libs/common/src/lib/models/portfolio-snapshot.ts
Normal file
82
libs/common/src/lib/models/portfolio-snapshot.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { transformToBig } from '@ghostfolio/common/class-transformer';
|
||||||
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
|
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||||
|
|
||||||
|
import { Big } from 'big.js';
|
||||||
|
import { Transform, Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export class PortfolioSnapshot {
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
currentValueInBaseCurrency: Big;
|
||||||
|
errors?: UniqueAsset[];
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformance: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformanceWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformancePercentage: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformancePercentageWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
hasErrors: boolean;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netAnnualizedPerformance?: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netAnnualizedPerformanceWithCurrencyEffect?: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformance: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformanceWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformancePercentage: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformancePercentageWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Type(() => TimelinePosition)
|
||||||
|
positions: TimelinePosition[];
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
totalFeesWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
totalInterestWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
totalInvestment: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
totalInvestmentWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
totalLiabilitiesWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
totalValuablesWithCurrencyEffect: Big;
|
||||||
|
}
|
92
libs/common/src/lib/models/timeline-position.ts
Normal file
92
libs/common/src/lib/models/timeline-position.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { transformToBig } from '@ghostfolio/common/class-transformer';
|
||||||
|
|
||||||
|
import { DataSource, Tag } from '@prisma/client';
|
||||||
|
import { Big } from 'big.js';
|
||||||
|
import { Transform, Type } from 'class-transformer';
|
||||||
|
|
||||||
|
export class TimelinePosition {
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
averagePrice: Big;
|
||||||
|
|
||||||
|
currency: string;
|
||||||
|
dataSource: DataSource;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
dividend: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
dividendInBaseCurrency: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
fee: Big;
|
||||||
|
|
||||||
|
firstBuyDate: string;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformance: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformancePercentage: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformancePercentageWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
grossPerformanceWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
investment: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
investmentWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
marketPrice: number;
|
||||||
|
marketPriceInBaseCurrency: number;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformance: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformancePercentage: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformancePercentageWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
netPerformanceWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
quantity: Big;
|
||||||
|
|
||||||
|
symbol: string;
|
||||||
|
tags?: Tag[];
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
timeWeightedInvestment: Big;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
timeWeightedInvestmentWithCurrencyEffect: Big;
|
||||||
|
|
||||||
|
transactionCount: number;
|
||||||
|
|
||||||
|
@Transform(transformToBig, { toClassOnly: true })
|
||||||
|
@Type(() => Big)
|
||||||
|
valueInBaseCurrency: Big;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user