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
|
||||
|
||||
- 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
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
||||
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
|
||||
import {
|
||||
SymbolMetrics,
|
||||
TimelinePosition,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||
|
||||
export class MWRPortfolioCalculator extends PortfolioCalculator {
|
||||
protected calculateOverallPerformance(
|
||||
|
@ -24,3 +24,7 @@ export const symbolProfileDummyData = {
|
||||
sectors: [],
|
||||
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 { 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 { HistoricalDataItem } from '@ghostfolio/common/interfaces';
|
||||
import { DateRange } from '@ghostfolio/common/types';
|
||||
import { DateRange, UserWithSettings } from '@ghostfolio/common/types';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@ -18,8 +20,10 @@ export enum PerformanceCalculationType {
|
||||
@Injectable()
|
||||
export class PortfolioCalculatorFactory {
|
||||
public constructor(
|
||||
private readonly configurationService: ConfigurationService,
|
||||
private readonly currentRateService: CurrentRateService,
|
||||
private readonly exchangeRateDataService: ExchangeRateDataService
|
||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||
private readonly redisCacheService: RedisCacheService
|
||||
) {}
|
||||
|
||||
public createCalculator({
|
||||
@ -27,13 +31,17 @@ export class PortfolioCalculatorFactory {
|
||||
activities,
|
||||
calculationType,
|
||||
currency,
|
||||
dateRange = 'max'
|
||||
dateRange = 'max',
|
||||
isExperimentalFeatures = false,
|
||||
userId
|
||||
}: {
|
||||
accountBalanceItems?: HistoricalDataItem[];
|
||||
activities: Activity[];
|
||||
calculationType: PerformanceCalculationType;
|
||||
currency: string;
|
||||
dateRange?: DateRange;
|
||||
isExperimentalFeatures?: boolean;
|
||||
userId: string;
|
||||
}): PortfolioCalculator {
|
||||
switch (calculationType) {
|
||||
case PerformanceCalculationType.MWR:
|
||||
@ -42,8 +50,12 @@ export class PortfolioCalculatorFactory {
|
||||
activities,
|
||||
currency,
|
||||
dateRange,
|
||||
isExperimentalFeatures,
|
||||
userId,
|
||||
configurationService: this.configurationService,
|
||||
currentRateService: this.currentRateService,
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
redisCacheService: this.redisCacheService
|
||||
});
|
||||
case PerformanceCalculationType.TWR:
|
||||
return new TWRPortfolioCalculator({
|
||||
@ -52,7 +64,11 @@ export class PortfolioCalculatorFactory {
|
||||
currency,
|
||||
currentRateService: this.currentRateService,
|
||||
dateRange,
|
||||
exchangeRateDataService: this.exchangeRateDataService
|
||||
isExperimentalFeatures,
|
||||
userId,
|
||||
configurationService: this.configurationService,
|
||||
exchangeRateDataService: this.exchangeRateDataService,
|
||||
redisCacheService: this.redisCacheService
|
||||
});
|
||||
default:
|
||||
throw new Error('Invalid calculation type');
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||
import {
|
||||
getFactor,
|
||||
getInterval
|
||||
} 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 { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
|
||||
@ -23,12 +24,14 @@ import {
|
||||
InvestmentItem,
|
||||
ResponseError,
|
||||
SymbolMetrics,
|
||||
TimelinePosition,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||
import { DateRange, GroupBy } from '@ghostfolio/common/types';
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Big } from 'big.js';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import {
|
||||
differenceInDays,
|
||||
eachDayOfInterval,
|
||||
@ -41,6 +44,7 @@ import {
|
||||
subDays
|
||||
} from 'date-fns';
|
||||
import { first, last, uniq, uniqBy } from 'lodash';
|
||||
import ms from 'ms';
|
||||
|
||||
export abstract class PortfolioCalculator {
|
||||
protected static readonly ENABLE_LOGGING = false;
|
||||
@ -48,37 +52,61 @@ export abstract class PortfolioCalculator {
|
||||
protected accountBalanceItems: HistoricalDataItem[];
|
||||
protected orders: PortfolioOrder[];
|
||||
|
||||
private configurationService: ConfigurationService;
|
||||
private currency: string;
|
||||
private currentRateService: CurrentRateService;
|
||||
private dataProviderInfos: DataProviderInfo[];
|
||||
private endDate: Date;
|
||||
private exchangeRateDataService: ExchangeRateDataService;
|
||||
private isExperimentalFeatures: boolean;
|
||||
private redisCacheService: RedisCacheService;
|
||||
private snapshot: PortfolioSnapshot;
|
||||
private snapshotPromise: Promise<void>;
|
||||
private startDate: Date;
|
||||
private transactionPoints: TransactionPoint[];
|
||||
private userId: string;
|
||||
|
||||
public constructor({
|
||||
accountBalanceItems,
|
||||
activities,
|
||||
configurationService,
|
||||
currency,
|
||||
currentRateService,
|
||||
dateRange,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
isExperimentalFeatures,
|
||||
redisCacheService,
|
||||
userId
|
||||
}: {
|
||||
accountBalanceItems: HistoricalDataItem[];
|
||||
activities: Activity[];
|
||||
configurationService: ConfigurationService;
|
||||
currency: string;
|
||||
currentRateService: CurrentRateService;
|
||||
dateRange: DateRange;
|
||||
exchangeRateDataService: ExchangeRateDataService;
|
||||
isExperimentalFeatures: boolean;
|
||||
redisCacheService: RedisCacheService;
|
||||
userId: string;
|
||||
}) {
|
||||
this.accountBalanceItems = accountBalanceItems;
|
||||
this.configurationService = configurationService;
|
||||
this.currency = currency;
|
||||
this.currentRateService = currentRateService;
|
||||
this.exchangeRateDataService = exchangeRateDataService;
|
||||
this.orders = activities.map(
|
||||
({ date, fee, quantity, SymbolProfile, tags = [], type, unitPrice }) => {
|
||||
this.isExperimentalFeatures = isExperimentalFeatures;
|
||||
|
||||
this.orders = activities
|
||||
.map(
|
||||
({
|
||||
date,
|
||||
fee,
|
||||
quantity,
|
||||
SymbolProfile,
|
||||
tags = [],
|
||||
type,
|
||||
unitPrice
|
||||
}) => {
|
||||
return {
|
||||
SymbolProfile,
|
||||
tags,
|
||||
@ -89,12 +117,14 @@ export abstract class PortfolioCalculator {
|
||||
unitPrice: new Big(unitPrice)
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
this.orders.sort((a, b) => {
|
||||
)
|
||||
.sort((a, b) => {
|
||||
return a.date?.localeCompare(b.date);
|
||||
});
|
||||
|
||||
this.redisCacheService = redisCacheService;
|
||||
this.userId = userId;
|
||||
|
||||
const { endDate, startDate } = getInterval(dateRange);
|
||||
|
||||
this.endDate = endDate;
|
||||
@ -1011,6 +1041,48 @@ export abstract class PortfolioCalculator {
|
||||
}
|
||||
|
||||
private async initialize() {
|
||||
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 {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -101,7 +122,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PerformanceCalculationType,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -86,7 +107,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
|
||||
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(
|
||||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
||||
() => {
|
||||
@ -37,11 +50,15 @@ jest.mock(
|
||||
);
|
||||
|
||||
describe('PortfolioCalculator', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -51,9 +68,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -99,7 +120,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'USD'
|
||||
currency: 'USD',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
|
||||
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(
|
||||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
||||
() => {
|
||||
@ -37,11 +50,15 @@ jest.mock(
|
||||
);
|
||||
|
||||
describe('PortfolioCalculator', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -51,9 +68,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -84,7 +105,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'USD'
|
||||
currency: 'USD',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PortfolioCalculatorFactory,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -71,7 +92,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'USD'
|
||||
currency: 'USD',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PerformanceCalculationType,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
|
||||
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(
|
||||
'@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service',
|
||||
() => {
|
||||
@ -37,11 +50,15 @@ jest.mock(
|
||||
);
|
||||
|
||||
describe('PortfolioCalculator', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -51,9 +68,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -99,7 +120,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'USD'
|
||||
currency: 'USD',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
|
||||
|
@ -1,9 +1,13 @@
|
||||
import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PerformanceCalculationType,
|
||||
PortfolioCalculatorFactory
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -34,9 +51,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -49,7 +70,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities: [],
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const start = subDays(new Date(Date.now()), 10);
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PerformanceCalculationType,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -86,7 +107,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import {
|
||||
activityDummyData,
|
||||
symbolProfileDummyData
|
||||
symbolProfileDummyData,
|
||||
userDummyData
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
|
||||
import {
|
||||
PerformanceCalculationType,
|
||||
@ -9,6 +10,9 @@ import {
|
||||
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
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 { 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', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -38,9 +55,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
@ -86,7 +107,8 @@ describe('PortfolioCalculator', () => {
|
||||
const portfolioCalculator = factory.createCalculator({
|
||||
activities,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: 'CHF'
|
||||
currency: 'CHF',
|
||||
userId: userDummyData.id
|
||||
});
|
||||
|
||||
const chartData = await portfolioCalculator.getChartData({
|
||||
|
@ -1,13 +1,19 @@
|
||||
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
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';
|
||||
|
||||
describe('PortfolioCalculator', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let factory: PortfolioCalculatorFactory;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
@ -17,9 +23,13 @@ describe('PortfolioCalculator', () => {
|
||||
null
|
||||
);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
factory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService
|
||||
exchangeRateDataService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
|
||||
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 { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
SymbolMetrics,
|
||||
TimelinePosition,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { SymbolMetrics, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
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 { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||
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 { ApiModule } from '@ghostfolio/api/services/api/api.module';
|
||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||
@ -35,6 +36,7 @@ import { RulesService } from './rules.service';
|
||||
MarketDataModule,
|
||||
OrderModule,
|
||||
PrismaModule,
|
||||
RedisCacheModule,
|
||||
SymbolProfileModule,
|
||||
UserModule
|
||||
],
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
EnhancedSymbolProfile,
|
||||
Filter,
|
||||
HistoricalDataItem,
|
||||
InvestmentItem,
|
||||
PortfolioDetails,
|
||||
PortfolioInvestments,
|
||||
PortfolioPerformanceResponse,
|
||||
@ -36,10 +37,9 @@ import {
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
TimelinePosition,
|
||||
UserSettings
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||
import { TimelinePosition } from '@ghostfolio/common/models';
|
||||
import type {
|
||||
AccountWithValue,
|
||||
DateRange,
|
||||
@ -277,8 +277,11 @@ export class PortfolioService {
|
||||
|
||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||
activities,
|
||||
userId,
|
||||
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({
|
||||
@ -352,8 +355,11 @@ export class PortfolioService {
|
||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||
activities,
|
||||
dateRange,
|
||||
userId,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: userCurrency
|
||||
currency: userCurrency,
|
||||
isExperimentalFeatures:
|
||||
this.request.user.Settings.settings.isExperimentalFeatures
|
||||
});
|
||||
|
||||
const { currentValueInBaseCurrency, hasErrors, positions } =
|
||||
@ -648,11 +654,14 @@ export class PortfolioService {
|
||||
]);
|
||||
|
||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||
userId,
|
||||
activities: orders.filter((order) => {
|
||||
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type);
|
||||
}),
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: userCurrency
|
||||
currency: userCurrency,
|
||||
isExperimentalFeatures:
|
||||
this.request.user.Settings.settings.isExperimentalFeatures
|
||||
});
|
||||
|
||||
const portfolioStart = portfolioCalculator.getStartDate();
|
||||
@ -919,8 +928,11 @@ export class PortfolioService {
|
||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||
activities,
|
||||
dateRange,
|
||||
userId,
|
||||
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();
|
||||
@ -1108,8 +1120,11 @@ export class PortfolioService {
|
||||
accountBalanceItems,
|
||||
activities,
|
||||
dateRange,
|
||||
userId,
|
||||
calculationType: PerformanceCalculationType.TWR,
|
||||
currency: userCurrency
|
||||
currency: userCurrency,
|
||||
isExperimentalFeatures:
|
||||
this.request.user.Settings.settings.isExperimentalFeatures
|
||||
});
|
||||
|
||||
const {
|
||||
@ -1202,8 +1217,11 @@ export class PortfolioService {
|
||||
|
||||
const portfolioCalculator = this.calculatorFactory.createCalculator({
|
||||
activities,
|
||||
userId,
|
||||
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 } =
|
||||
|
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);
|
||||
}
|
||||
|
||||
public getPortfolioSnapshotKey(userId: string) {
|
||||
return `portfolio-snapshot-${userId}`;
|
||||
}
|
||||
|
||||
public getQuoteKey({ dataSource, symbol }: UniqueAsset) {
|
||||
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 { PortfolioChangedListener } from './portfolio-changed.listener';
|
||||
|
||||
@Module({
|
||||
imports: [RedisCacheModule],
|
||||
providers: [PortfolioChangedListener]
|
||||
})
|
||||
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 { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
@ -5,11 +7,17 @@ import { PortfolioChangedEvent } from './portfolio-changed.event';
|
||||
|
||||
@Injectable()
|
||||
export class PortfolioChangedListener {
|
||||
public constructor(private readonly redisCacheService: RedisCacheService) {}
|
||||
|
||||
@OnEvent(PortfolioChangedEvent.getName())
|
||||
handlePortfolioChangedEvent(event: PortfolioChangedEvent) {
|
||||
Logger.log(
|
||||
`Portfolio of user with id ${event.getUserId()} has changed`,
|
||||
'PortfolioChangedListener'
|
||||
);
|
||||
|
||||
this.redisCacheService.remove(
|
||||
this.redisCacheService.getPortfolioSnapshotKey(event.getUserId())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
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 { RuleInterface } from './interfaces/rule.interface';
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||
import { Rule } from '@ghostfolio/api/models/rule';
|
||||
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> {
|
||||
private positions: TimelinePosition[];
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
|
||||
import { Rule } from '@ghostfolio/api/models/rule';
|
||||
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> {
|
||||
private positions: TimelinePosition[];
|
||||
|
@ -399,7 +399,8 @@ export class DataProviderService {
|
||||
numberOfItemsInCache > 1 ? 's' : ''
|
||||
} from cache in ${((performance.now() - startTimeTotal) / 1000).toFixed(
|
||||
3
|
||||
)} seconds`
|
||||
)} seconds`,
|
||||
'DataProviderService'
|
||||
);
|
||||
}
|
||||
|
||||
@ -505,7 +506,8 @@ export class DataProviderService {
|
||||
} from ${dataSource} in ${(
|
||||
(performance.now() - startTimeDataSource) /
|
||||
1000
|
||||
).toFixed(3)} seconds`
|
||||
).toFixed(3)} seconds`,
|
||||
'DataProviderService'
|
||||
);
|
||||
|
||||
try {
|
||||
@ -535,14 +537,15 @@ export class DataProviderService {
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
Logger.debug('------------------------------------------------');
|
||||
Logger.debug('--------------------------------------------------------');
|
||||
Logger.debug(
|
||||
`Fetched ${items.length} quote${items.length > 1 ? 's' : ''} in ${(
|
||||
(performance.now() - startTimeTotal) /
|
||||
1000
|
||||
).toFixed(3)} seconds`
|
||||
).toFixed(3)} seconds`,
|
||||
'DataProviderService'
|
||||
);
|
||||
Logger.debug('================================================');
|
||||
Logger.debug('========================================================');
|
||||
|
||||
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 { SystemMessage } from './system-message.interface';
|
||||
import type { TabConfiguration } from './tab-configuration.interface';
|
||||
import type { TimelinePosition } from './timeline-position.interface';
|
||||
import type { UniqueAsset } from './unique-asset.interface';
|
||||
import type { UserSettings } from './user-settings.interface';
|
||||
import type { User } from './user.interface';
|
||||
@ -102,7 +101,6 @@ export {
|
||||
Subscription,
|
||||
SymbolMetrics,
|
||||
TabConfiguration,
|
||||
TimelinePosition,
|
||||
UniqueAsset,
|
||||
User,
|
||||
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