Browse Source

Feature/add performance calculation type to user settings (#4567)

* Add performance calculation type to user settings

* Update changelog
pull/4583/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
71fc3906c7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 5
      apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts
  3. 25
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  4. 5
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  5. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  6. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
  7. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
  8. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  9. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts
  10. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
  11. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts
  12. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts
  13. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  14. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts
  15. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  16. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  17. 5
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  18. 29
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts
  19. 5
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts
  20. 39
      apps/api/src/app/portfolio/portfolio.service.ts
  21. 7
      apps/api/src/app/user/user.service.ts
  22. 2
      apps/api/src/services/queues/portfolio-snapshot/interfaces/portfolio-snapshot-queue-job.interface.ts
  23. 7
      apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts
  24. 64
      apps/client/src/app/components/user-account-settings/user-account-settings.html
  25. 2
      libs/common/src/lib/interfaces/user-settings.interface.ts
  26. 6
      libs/common/src/lib/types/performance-calculation-type.type.ts

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Extended the benchmark detail dialog by the current market price
- Added the performance calculation type to the user settings (experimental)
- Added `watchlist` to the `User` database schema as a preparation for watching assets
### Changed

5
apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts

@ -4,12 +4,17 @@ import {
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot } from '@ghostfolio/common/models';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
export class MwrPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance(): PortfolioSnapshot {
throw new Error('Method not implemented.');
}
protected getPerformanceCalculationType() {
return PerformanceCalculationType.MWR;
}
protected getSymbolMetrics({}: {
end: Date;
exchangeRates: { [dateString: string]: number };

25
apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts

@ -5,20 +5,16 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { Filter, HistoricalDataItem } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Injectable } from '@nestjs/common';
import { MwrPortfolioCalculator } from './mwr/portfolio-calculator';
import { PortfolioCalculator } from './portfolio-calculator';
import { RoaiPortfolioCalculator } from './roai/portfolio-calculator';
import { RoiPortfolioCalculator } from './roi/portfolio-calculator';
import { TwrPortfolioCalculator } from './twr/portfolio-calculator';
export enum PerformanceCalculationType {
MWR = 'MWR', // Money-Weighted Rate of Return
ROAI = 'ROAI', // Return on Average Investment
TWR = 'TWR' // Time-Weighted Rate of Return
}
@Injectable()
export class PortfolioCalculatorFactory {
public constructor(
@ -58,6 +54,7 @@ export class PortfolioCalculatorFactory {
portfolioSnapshotService: this.portfolioSnapshotService,
redisCacheService: this.redisCacheService
});
case PerformanceCalculationType.ROAI:
return new RoaiPortfolioCalculator({
accountBalanceItems,
@ -71,6 +68,21 @@ export class PortfolioCalculatorFactory {
portfolioSnapshotService: this.portfolioSnapshotService,
redisCacheService: this.redisCacheService
});
case PerformanceCalculationType.ROI:
return new RoiPortfolioCalculator({
accountBalanceItems,
activities,
currency,
filters,
userId,
configurationService: this.configurationService,
currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService,
portfolioSnapshotService: this.portfolioSnapshotService,
redisCacheService: this.redisCacheService
});
case PerformanceCalculationType.TWR:
return new TwrPortfolioCalculator({
accountBalanceItems,
@ -84,6 +96,7 @@ export class PortfolioCalculatorFactory {
portfolioSnapshotService: this.portfolioSnapshotService,
redisCacheService: this.redisCacheService
});
default:
throw new Error('Invalid calculation type');
}

5
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -35,6 +35,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { GroupBy } from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Logger } from '@nestjs/common';
import { Big } from 'big.js';
@ -623,6 +624,8 @@ export abstract class PortfolioCalculator {
};
}
protected abstract getPerformanceCalculationType(): PerformanceCalculationType;
public getDataProviderInfos() {
return this.dataProviderInfos;
}
@ -1073,6 +1076,7 @@ export abstract class PortfolioCalculator {
// Compute in the background
this.portfolioSnapshotService.addJobToQueue({
data: {
calculationType: this.getPerformanceCalculationType(),
filters: this.filters,
userCurrency: this.currency,
userId: this.userId
@ -1089,6 +1093,7 @@ export abstract class PortfolioCalculator {
// Wait for computation
await this.portfolioSnapshotService.addJobToQueue({
data: {
calculationType: this.getPerformanceCalculationType(),
filters: this.filters,
userCurrency: this.currency,
userId: this.userId

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -17,6 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PerformanceCalculationType,
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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -17,6 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -17,6 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -18,6 +15,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -17,6 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -18,6 +15,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-item.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -17,6 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PortfolioCalculatorFactory,
PerformanceCalculationType
} 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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -17,6 +14,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -4,10 +4,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PerformanceCalculationType,
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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -18,6 +15,7 @@ import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-r
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts

@ -1,8 +1,5 @@
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 { 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';
@ -12,6 +9,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -6,10 +6,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PerformanceCalculationType,
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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -19,6 +16,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'path';

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -6,10 +6,7 @@ import {
symbolProfileDummyData,
userDummyData
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator-test-utils';
import {
PerformanceCalculationType,
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 { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
@ -19,6 +16,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Big } from 'big.js';
import { join } from 'path';

5
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts

@ -9,6 +9,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
import { DateRange } from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Logger } from '@nestjs/common';
import { Big } from 'big.js';
@ -112,6 +113,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
};
}
protected getPerformanceCalculationType() {
return PerformanceCalculationType.ROAI;
}
protected getSymbolMetrics({
chartDateMap,
dataSource,

29
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts

@ -0,0 +1,29 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import {
AssetProfileIdentifier,
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot } from '@ghostfolio/common/models';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
export class RoiPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance(): PortfolioSnapshot {
throw new Error('Method not implemented.');
}
protected getPerformanceCalculationType() {
return PerformanceCalculationType.ROI;
}
protected getSymbolMetrics({}: {
end: Date;
exchangeRates: { [dateString: string]: number };
marketSymbolMap: {
[date: string]: { [symbol: string]: Big };
};
start: Date;
step?: number;
} & AssetProfileIdentifier): SymbolMetrics {
throw new Error('Method not implemented.');
}
}

5
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -4,12 +4,17 @@ import {
SymbolMetrics
} from '@ghostfolio/common/interfaces';
import { PortfolioSnapshot } from '@ghostfolio/common/models';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
export class TwrPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance(): PortfolioSnapshot {
throw new Error('Method not implemented.');
}
protected getPerformanceCalculationType() {
return PerformanceCalculationType.TWR;
}
protected getSymbolMetrics({}: {
end: Date;
exchangeRates: { [dateString: string]: number };

39
apps/api/src/app/portfolio/portfolio.service.ts

@ -50,13 +50,14 @@ import {
UserSettings
} from '@ghostfolio/common/interfaces';
import { TimelinePosition } from '@ghostfolio/common/models';
import type {
import {
AccountWithValue,
DateRange,
GroupBy,
RequestWithUser,
UserWithSettings
} from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Inject, Injectable } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
@ -85,10 +86,7 @@ import {
import { isEmpty } from 'lodash';
import { PortfolioCalculator } from './calculator/portfolio-calculator';
import {
PerformanceCalculationType,
PortfolioCalculatorFactory
} from './calculator/portfolio-calculator.factory';
import { PortfolioCalculatorFactory } from './calculator/portfolio-calculator.factory';
import { PortfolioHoldingDetail } from './interfaces/portfolio-holding-detail.interface';
import { RulesService } from './rules.service';
@ -278,14 +276,16 @@ export class PortfolioService {
savingsRate: number;
}): Promise<PortfolioInvestments> {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user);
const { endDate, startDate } = getIntervalFromDateRange(dateRange);
const { activities } =
await this.orderService.getOrdersForPortfolioCalculator({
filters,
userId,
userCurrency: this.getUserCurrency()
userCurrency,
userId
});
if (activities.length === 0) {
@ -299,8 +299,8 @@ export class PortfolioService {
activities,
filters,
userId,
calculationType: PerformanceCalculationType.ROAI,
currency: this.request.user.Settings.settings.baseCurrency
calculationType: this.getUserPerformanceCalculationType(user),
currency: userCurrency
});
const { historicalData } = await portfolioCalculator.getSnapshot();
@ -376,7 +376,7 @@ export class PortfolioService {
activities,
filters,
userId,
calculationType: PerformanceCalculationType.ROAI,
calculationType: this.getUserPerformanceCalculationType(user),
currency: userCurrency
});
@ -684,7 +684,7 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({
activities,
userId,
calculationType: PerformanceCalculationType.ROAI,
calculationType: this.getUserPerformanceCalculationType(user),
currency: userCurrency
});
@ -935,12 +935,13 @@ export class PortfolioService {
})?.id;
const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user);
const { activities } =
await this.orderService.getOrdersForPortfolioCalculator({
filters,
userId,
userCurrency: this.getUserCurrency()
userCurrency,
userId
});
if (activities.length === 0) {
@ -954,8 +955,8 @@ export class PortfolioService {
activities,
filters,
userId,
calculationType: PerformanceCalculationType.ROAI,
currency: this.request.user.Settings.settings.baseCurrency
calculationType: this.getUserPerformanceCalculationType(user),
currency: userCurrency
});
const portfolioSnapshot = await portfolioCalculator.getSnapshot();
@ -1120,7 +1121,7 @@ export class PortfolioService {
activities,
filters,
userId,
calculationType: PerformanceCalculationType.ROAI,
calculationType: this.getUserPerformanceCalculationType(user),
currency: userCurrency
});
@ -2021,6 +2022,12 @@ export class PortfolioService {
return impersonationUserId || aUserId;
}
private getUserPerformanceCalculationType(
aUser: UserWithSettings
): PerformanceCalculationType {
return aUser?.Settings?.settings.performanceCalculationType;
}
private async getValueOfAccountsAndPlatforms({
activities,
filters = [],

7
apps/api/src/app/user/user.service.ts

@ -41,6 +41,7 @@ import {
permissions
} from '@ghostfolio/common/permissions';
import { UserWithSettings } from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
@ -246,6 +247,12 @@ export class UserService {
? 'max'
: ((user.Settings.settings as UserSettings)?.dateRange ?? 'max');
// Set default value for performance calculation type
if (!(user.Settings.settings as UserSettings)?.performanceCalculationType) {
(user.Settings.settings as UserSettings).performanceCalculationType =
PerformanceCalculationType.ROAI;
}
// Set default value for view mode
if (!(user.Settings.settings as UserSettings).viewMode) {
(user.Settings.settings as UserSettings).viewMode = 'DEFAULT';

2
apps/api/src/services/queues/portfolio-snapshot/interfaces/portfolio-snapshot-queue-job.interface.ts

@ -1,6 +1,8 @@
import { Filter } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
export interface IPortfolioSnapshotQueueJob {
calculationType: PerformanceCalculationType;
filters: Filter[];
userCurrency: string;
userId: string;

7
apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts

@ -1,9 +1,6 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import {
PerformanceCalculationType,
PortfolioCalculatorFactory
} from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
@ -68,7 +65,7 @@ export class PortfolioSnapshotProcessor {
const portfolioCalculator = this.calculatorFactory.createCalculator({
accountBalanceItems,
activities,
calculationType: PerformanceCalculationType.ROAI,
calculationType: job.data.calculationType,
currency: job.data.userCurrency,
filters: job.data.filters,
userId: job.data.userId

64
apps/client/src/app/components/user-account-settings/user-account-settings.html

@ -2,25 +2,7 @@
<h1 class="d-none d-sm-block h3 mb-3 text-center" i18n>Settings</h1>
<div class="row">
<div class="col">
<div class="align-items-center d-flex py-1">
<div class="pr-1 w-50">
<div i18n>Presenter View</div>
<div class="hint-text text-muted" i18n>
Protection for sensitive information like absolute performances and
quantity values
</div>
</div>
<div class="pl-1 w-50">
<mat-slide-toggle
color="primary"
hideIcon="true"
[checked]="user.settings.isRestrictedView"
[disabled]="!hasPermissionToUpdateUserSettings"
(change)="onRestrictedViewChange($event)"
/>
</div>
</div>
<div class="d-flex mt-4 py-1">
<div class="d-flex py-1">
<form #changeUserSettingsForm="ngForm" class="w-100">
<div class="d-flex mb-2">
<div class="align-items-center d-flex pt-1 pt-1 w-50">
@ -43,6 +25,32 @@
</mat-form-field>
</div>
</div>
@if (user?.settings?.isExperimentalFeatures && !user?.subscription) {
<div class="d-flex mb-2">
<div class="align-items-center d-flex pt-1 pt-1 w-50">
<ng-container i18n>Performance Calculation</ng-container>
</div>
<div class="pl-1 w-50">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-select
name="performanceCalculationType"
[disabled]="true"
[value]="user.settings.performanceCalculationType"
(selectionChange)="
onChangeUserSetting(
'performanceCalculationType',
$event.value
)
"
>
<mat-option value="ROAI"
>Return on Average Investment (ROAI)</mat-option
>
</mat-select>
</mat-form-field>
</div>
</div>
}
<div class="align-items-center d-flex mb-2">
<div class="pr-1 w-50">
<div i18n>Language</div>
@ -172,6 +180,24 @@
</div>
</form>
</div>
<div class="align-items-center d-flex mt-4 py-1">
<div class="pr-1 w-50">
<div i18n>Presenter View</div>
<div class="hint-text text-muted" i18n>
Protection for sensitive information like absolute performances and
quantity values
</div>
</div>
<div class="pl-1 w-50">
<mat-slide-toggle
color="primary"
hideIcon="true"
[checked]="user.settings.isRestrictedView"
[disabled]="!hasPermissionToUpdateUserSettings"
(change)="onRestrictedViewChange($event)"
/>
</div>
</div>
<div class="d-flex mt-4 py-1">
<div class="pr-1 w-50">
<div i18n>Zen Mode</div>

2
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -5,6 +5,7 @@ import {
HoldingsViewMode,
ViewMode
} from '@ghostfolio/common/types';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
export interface UserSettings {
annualInterestRate?: number;
@ -22,6 +23,7 @@ export interface UserSettings {
isRestrictedView?: boolean;
language?: string;
locale?: string;
performanceCalculationType?: PerformanceCalculationType;
projectedTotalAmount?: number;
retirementDate?: string;
savingsRate?: number;

6
libs/common/src/lib/types/performance-calculation-type.type.ts

@ -0,0 +1,6 @@
export enum PerformanceCalculationType {
MWR = 'MWR', // Money-Weighted Rate of Return
ROAI = 'ROAI', // Return on Average Investment
ROI = 'ROI', // Return on Investment
TWR = 'TWR' // Time-Weighted Rate of Return
}
Loading…
Cancel
Save