Feature/extend holding endpoint by performances (#4660)

* Extend holding endpoint by performances

* Update changelog
This commit is contained in:
Kenrick Tandrian 2025-05-06 22:43:03 +07:00 committed by GitHub
parent 40d3eaa023
commit c38dab5ab0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 52 additions and 2 deletions

View File

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Extended the endpoint to get a holding by the date of the last all time high and the current change to the all time high
### Changed
- Improved the language localization for Turkish (`tr`)

View File

@ -8,6 +8,7 @@ import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
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 { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
@ -27,6 +28,7 @@ import { AiService } from './ai.service';
controllers: [AiController],
imports: [
ApiModule,
BenchmarkModule,
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,

View File

@ -9,6 +9,7 @@ import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
@ -25,6 +26,7 @@ import { PublicController } from './public.controller';
controllers: [PublicController],
imports: [
AccessModule,
BenchmarkModule,
DataProviderModule,
ExchangeRateDataModule,
ImpersonationModule,

View File

@ -9,6 +9,7 @@ import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redac
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
@ -33,6 +34,7 @@ import { RulesService } from './rules.service';
imports: [
AccessModule,
ApiModule,
BenchmarkModule,
ConfigurationModule,
DataGatheringModule,
DataProviderModule,

View File

@ -20,6 +20,7 @@ import { RegionalMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models
import { RegionalMarketClusterRiskEurope } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/europe';
import { RegionalMarketClusterRiskJapan } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/japan';
import { RegionalMarketClusterRiskNorthAmerica } from '@ghostfolio/api/models/rules/regional-market-cluster-risk/north-america';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
@ -100,6 +101,7 @@ export class PortfolioService {
public constructor(
private readonly accountBalanceService: AccountBalanceService,
private readonly accountService: AccountService,
private readonly benchmarkService: BenchmarkService,
private readonly calculatorFactory: PortfolioCalculatorFactory,
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
@ -669,6 +671,7 @@ export class PortfolioService {
netPerformancePercent: undefined,
netPerformancePercentWithCurrencyEffect: undefined,
netPerformanceWithCurrencyEffect: undefined,
performances: undefined,
quantity: undefined,
SymbolProfile: undefined,
tags: [],
@ -752,6 +755,10 @@ export class PortfolioService {
activitiesOfHolding[0].unitPriceInAssetProfileCurrency,
marketPrice
);
let marketPriceMaxDate =
marketPrice > activitiesOfHolding[0].unitPriceInAssetProfileCurrency
? new Date()
: activitiesOfHolding[0].date;
let marketPriceMin = Math.min(
activitiesOfHolding[0].unitPriceInAssetProfileCurrency,
marketPrice
@ -793,7 +800,10 @@ export class PortfolioService {
quantity: currentQuantity
});
marketPriceMax = Math.max(marketPrice ?? 0, marketPriceMax);
if (marketPrice > marketPriceMax) {
marketPriceMax = marketPrice;
marketPriceMaxDate = parseISO(date);
}
marketPriceMin = Math.min(
marketPrice ?? Number.MAX_SAFE_INTEGER,
marketPriceMin
@ -809,6 +819,12 @@ export class PortfolioService {
});
}
const performancePercent =
this.benchmarkService.calculateChangeInPercentage(
marketPriceMax,
marketPrice
);
return {
firstBuyDate,
marketPrice,
@ -846,6 +862,12 @@ export class PortfolioService {
]?.toNumber(),
netPerformanceWithCurrencyEffect:
position.netPerformanceWithCurrencyEffectMap?.['max']?.toNumber(),
performances: {
allTimeHigh: {
performancePercent,
date: marketPriceMaxDate
}
},
quantity: quantity.toNumber(),
value: this.exchangeRateDataService.toCurrency(
quantity.mul(marketPrice ?? 0).toNumber(),
@ -885,6 +907,7 @@ export class PortfolioService {
const historicalDataArray: HistoricalDataItem[] = [];
let marketPriceMax = marketPrice;
let marketPriceMaxDate = new Date();
let marketPriceMin = marketPrice;
for (const [date, { marketPrice }] of Object.entries(
@ -895,13 +918,22 @@ export class PortfolioService {
value: marketPrice
});
marketPriceMax = Math.max(marketPrice ?? 0, marketPriceMax);
if (marketPrice > marketPriceMax) {
marketPriceMax = marketPrice;
marketPriceMaxDate = parseISO(date);
}
marketPriceMin = Math.min(
marketPrice ?? Number.MAX_SAFE_INTEGER,
marketPriceMin
);
}
const performancePercent =
this.benchmarkService.calculateChangeInPercentage(
marketPriceMax,
marketPrice
);
return {
marketPrice,
marketPriceMax,
@ -925,6 +957,12 @@ export class PortfolioService {
netPerformancePercent: undefined,
netPerformancePercentWithCurrencyEffect: undefined,
netPerformanceWithCurrencyEffect: undefined,
performances: {
allTimeHigh: {
performancePercent,
date: marketPriceMaxDate
}
},
quantity: 0,
tags: [],
transactionCount: undefined,

View File

@ -1,5 +1,6 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import {
Benchmark,
DataProviderInfo,
EnhancedSymbolProfile,
HistoricalDataItem
@ -29,6 +30,7 @@ export interface PortfolioHoldingResponse {
netPerformancePercent: number;
netPerformancePercentWithCurrencyEffect: number;
netPerformanceWithCurrencyEffect: number;
performances: Benchmark['performances'];
quantity: number;
SymbolProfile: EnhancedSymbolProfile;
tags: Tag[];