Browse Source

Feature/refactor portfolio service (#5063)

* Refactor portfolio service
pull/5066/head
Thomas Kaul 1 day ago
committed by GitHub
parent
commit
7a97ec75f4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      apps/api/src/app/import/import.controller.ts
  2. 12
      apps/api/src/app/import/import.service.ts
  3. 36
      apps/api/src/app/portfolio/portfolio.controller.ts
  4. 102
      apps/api/src/app/portfolio/portfolio.service.ts

3
apps/api/src/app/import/import.controller.ts

@ -100,7 +100,8 @@ export class ImportController {
): Promise<ImportResponse> { ): Promise<ImportResponse> {
const activities = await this.importService.getDividends({ const activities = await this.importService.getDividends({
dataSource, dataSource,
symbol symbol,
userId: this.request.user.id
}); });
return { activities }; return { activities };

12
apps/api/src/app/import/import.service.ts

@ -48,11 +48,17 @@ export class ImportService {
public async getDividends({ public async getDividends({
dataSource, dataSource,
symbol symbol,
}: AssetProfileIdentifier): Promise<Activity[]> { userId
}: AssetProfileIdentifier & { userId: string }): Promise<Activity[]> {
try { try {
const { activities, firstBuyDate, historicalData } = const { activities, firstBuyDate, historicalData } =
await this.portfolioService.getHolding(dataSource, undefined, symbol); await this.portfolioService.getHolding({
dataSource,
symbol,
userId,
impersonationId: undefined
});
const [[assetProfile], dividends] = await Promise.all([ const [[assetProfile], dividends] = await Promise.all([
this.symbolProfileService.getSymbolProfiles([ this.symbolProfileService.getSymbolProfiles([

36
apps/api/src/app/portfolio/portfolio.controller.ts

@ -375,11 +375,12 @@ export class PortfolioController {
@Param('dataSource') dataSource: DataSource, @Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string @Param('symbol') symbol: string
): Promise<PortfolioHoldingResponse> { ): Promise<PortfolioHoldingResponse> {
const holding = await this.portfolioService.getHolding( const holding = await this.portfolioService.getHolding({
dataSource, dataSource,
impersonationId, impersonationId,
symbol symbol,
); userId: this.request.user.id
});
if (!holding) { if (!holding) {
throw new HttpException( throw new HttpException(
@ -453,7 +454,8 @@ export class PortfolioController {
filters, filters,
groupBy, groupBy,
impersonationId, impersonationId,
savingsRate: this.request.user?.Settings?.settings.savingsRate savingsRate: this.request.user?.Settings?.settings.savingsRate,
userId: this.request.user.id
}); });
if ( if (
@ -622,11 +624,12 @@ export class PortfolioController {
@Param('dataSource') dataSource: DataSource, @Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string @Param('symbol') symbol: string
): Promise<PortfolioHoldingResponse> { ): Promise<PortfolioHoldingResponse> {
const holding = await this.portfolioService.getHolding( const holding = await this.portfolioService.getHolding({
dataSource, dataSource,
impersonationId, impersonationId,
symbol symbol,
); userId: this.request.user.id
});
if (!holding) { if (!holding) {
throw new HttpException( throw new HttpException(
@ -643,7 +646,10 @@ export class PortfolioController {
public async getReport( public async getReport(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string
): Promise<PortfolioReportResponse> { ): Promise<PortfolioReportResponse> {
const report = await this.portfolioService.getReport(impersonationId); const report = await this.portfolioService.getReport({
impersonationId,
userId: this.request.user.id
});
if ( if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
@ -672,11 +678,12 @@ export class PortfolioController {
@Param('dataSource') dataSource: DataSource, @Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string @Param('symbol') symbol: string
): Promise<void> { ): Promise<void> {
const holding = await this.portfolioService.getHolding( const holding = await this.portfolioService.getHolding({
dataSource, dataSource,
impersonationId, impersonationId,
symbol symbol,
); userId: this.request.user.id
});
if (!holding) { if (!holding) {
throw new HttpException( throw new HttpException(
@ -707,11 +714,12 @@ export class PortfolioController {
@Param('dataSource') dataSource: DataSource, @Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string @Param('symbol') symbol: string
): Promise<void> { ): Promise<void> {
const holding = await this.portfolioService.getHolding( const holding = await this.portfolioService.getHolding({
dataSource, dataSource,
impersonationId, impersonationId,
symbol symbol,
); userId: this.request.user.id
});
if (!holding) { if (!holding) {
throw new HttpException( throw new HttpException(

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

@ -312,15 +312,17 @@ export class PortfolioService {
filters, filters,
groupBy, groupBy,
impersonationId, impersonationId,
savingsRate savingsRate,
userId
}: { }: {
dateRange: DateRange; dateRange: DateRange;
filters?: Filter[]; filters?: Filter[];
groupBy?: GroupBy; groupBy?: GroupBy;
impersonationId: string; impersonationId: string;
savingsRate: number; savingsRate: number;
userId: string;
}): Promise<PortfolioInvestments> { }): Promise<PortfolioInvestments> {
const userId = await this.getUserId(impersonationId, this.request.user.id); userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user); const userCurrency = this.getUserCurrency(user);
@ -676,12 +678,18 @@ export class PortfolioService {
}; };
} }
public async getHolding( public async getHolding({
aDataSource: DataSource, dataSource,
aImpersonationId: string, impersonationId,
aSymbol: string symbol,
): Promise<PortfolioHoldingResponse> { userId
const userId = await this.getUserId(aImpersonationId, this.request.user.id); }: {
dataSource: DataSource;
impersonationId: string;
symbol: string;
userId: string;
}): Promise<PortfolioHoldingResponse> {
userId = await this.getUserId(impersonationId, userId);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const userCurrency = this.getUserCurrency(user); const userCurrency = this.getUserCurrency(user);
@ -724,7 +732,7 @@ export class PortfolioService {
} }
const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([
{ dataSource: aDataSource, symbol: aSymbol } { dataSource, symbol }
]); ]);
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
@ -739,26 +747,33 @@ export class PortfolioService {
const { positions } = await portfolioCalculator.getSnapshot(); const { positions } = await portfolioCalculator.getSnapshot();
const position = positions.find(({ dataSource, symbol }) => { const holding = positions.find((position) => {
return dataSource === aDataSource && symbol === aSymbol; return position.dataSource === dataSource && position.symbol === symbol;
}); });
if (position) { if (holding) {
const { const {
averagePrice, averagePrice,
currency, currency,
dataSource,
dividendInBaseCurrency, dividendInBaseCurrency,
fee, fee,
firstBuyDate, firstBuyDate,
grossPerformance,
grossPerformancePercentage,
grossPerformancePercentageWithCurrencyEffect,
grossPerformanceWithCurrencyEffect,
investmentWithCurrencyEffect,
marketPrice, marketPrice,
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceWithCurrencyEffectMap,
quantity, quantity,
symbol,
tags, tags,
timeWeightedInvestment, timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect, timeWeightedInvestmentWithCurrencyEffect,
transactionCount transactionCount
} = position; } = holding;
const activitiesOfHolding = activities.filter(({ SymbolProfile }) => { const activitiesOfHolding = activities.filter(({ SymbolProfile }) => {
return ( return (
@ -787,7 +802,7 @@ export class PortfolioService {
}); });
const historicalData = await this.dataProviderService.getHistorical( const historicalData = await this.dataProviderService.getHistorical(
[{ dataSource, symbol: aSymbol }], [{ dataSource, symbol }],
'day', 'day',
parseISO(firstBuyDate), parseISO(firstBuyDate),
new Date() new Date()
@ -807,10 +822,10 @@ export class PortfolioService {
marketPrice marketPrice
); );
if (historicalData[aSymbol]) { if (historicalData[symbol]) {
let j = -1; let j = -1;
for (const [date, { marketPrice }] of Object.entries( for (const [date, { marketPrice }] of Object.entries(
historicalData[aSymbol] historicalData[symbol]
)) { )) {
while ( while (
j + 1 < transactionPoints.length && j + 1 < transactionPoints.length &&
@ -823,8 +838,8 @@ export class PortfolioService {
let currentQuantity = 0; let currentQuantity = 0;
const currentSymbol = transactionPoints[j]?.items.find( const currentSymbol = transactionPoints[j]?.items.find(
({ symbol }) => { (transactionPointSymbol) => {
return symbol === aSymbol; return transactionPointSymbol.symbol === symbol;
} }
); );
@ -888,24 +903,21 @@ export class PortfolioService {
SymbolProfile.currency, SymbolProfile.currency,
userCurrency userCurrency
), ),
grossPerformance: position.grossPerformance?.toNumber(), grossPerformance: grossPerformance?.toNumber(),
grossPerformancePercent: grossPerformancePercent: grossPerformancePercentage?.toNumber(),
position.grossPerformancePercentage?.toNumber(),
grossPerformancePercentWithCurrencyEffect: grossPerformancePercentWithCurrencyEffect:
position.grossPerformancePercentageWithCurrencyEffect?.toNumber(), grossPerformancePercentageWithCurrencyEffect?.toNumber(),
grossPerformanceWithCurrencyEffect: grossPerformanceWithCurrencyEffect:
position.grossPerformanceWithCurrencyEffect?.toNumber(), grossPerformanceWithCurrencyEffect?.toNumber(),
historicalData: historicalDataArray, historicalData: historicalDataArray,
investmentInBaseCurrencyWithCurrencyEffect: investmentInBaseCurrencyWithCurrencyEffect:
position.investmentWithCurrencyEffect?.toNumber(), investmentWithCurrencyEffect?.toNumber(),
netPerformance: position.netPerformance?.toNumber(), netPerformance: netPerformance?.toNumber(),
netPerformancePercent: position.netPerformancePercentage?.toNumber(), netPerformancePercent: netPerformancePercentage?.toNumber(),
netPerformancePercentWithCurrencyEffect: netPerformancePercentWithCurrencyEffect:
position.netPerformancePercentageWithCurrencyEffectMap?.[ netPerformancePercentageWithCurrencyEffectMap?.['max']?.toNumber(),
'max'
]?.toNumber(),
netPerformanceWithCurrencyEffect: netPerformanceWithCurrencyEffect:
position.netPerformanceWithCurrencyEffectMap?.['max']?.toNumber(), netPerformanceWithCurrencyEffectMap?.['max']?.toNumber(),
performances: { performances: {
allTimeHigh: { allTimeHigh: {
performancePercent, performancePercent,
@ -922,12 +934,12 @@ export class PortfolioService {
} else { } else {
const currentData = await this.dataProviderService.getQuotes({ const currentData = await this.dataProviderService.getQuotes({
user, user,
items: [{ dataSource: DataSource.YAHOO, symbol: aSymbol }] items: [{ symbol, dataSource: DataSource.YAHOO }]
}); });
const marketPrice = currentData[aSymbol]?.marketPrice; const marketPrice = currentData[symbol]?.marketPrice;
let historicalData = await this.dataProviderService.getHistorical( let historicalData = await this.dataProviderService.getHistorical(
[{ dataSource: DataSource.YAHOO, symbol: aSymbol }], [{ symbol, dataSource: DataSource.YAHOO }],
'day', 'day',
portfolioStart, portfolioStart,
new Date() new Date()
@ -936,15 +948,13 @@ export class PortfolioService {
if (isEmpty(historicalData)) { if (isEmpty(historicalData)) {
try { try {
historicalData = await this.dataProviderService.getHistoricalRaw({ historicalData = await this.dataProviderService.getHistoricalRaw({
assetProfileIdentifiers: [ assetProfileIdentifiers: [{ symbol, dataSource: DataSource.YAHOO }],
{ dataSource: DataSource.YAHOO, symbol: aSymbol }
],
from: portfolioStart, from: portfolioStart,
to: new Date() to: new Date()
}); });
} catch { } catch {
historicalData = { historicalData = {
[aSymbol]: {} [symbol]: {}
}; };
} }
} }
@ -955,7 +965,7 @@ export class PortfolioService {
let marketPriceMin = marketPrice; let marketPriceMin = marketPrice;
for (const [date, { marketPrice }] of Object.entries( for (const [date, { marketPrice }] of Object.entries(
historicalData[aSymbol] historicalData[symbol]
)) { )) {
historicalDataArray.push({ historicalDataArray.push({
date, date,
@ -1116,10 +1126,14 @@ export class PortfolioService {
}; };
} }
public async getReport( public async getReport({
impersonationId: string impersonationId,
): Promise<PortfolioReportResponse> { userId
const userId = await this.getUserId(impersonationId, this.request.user.id); }: {
impersonationId: string;
userId: string;
}): Promise<PortfolioReportResponse> {
userId = await this.getUserId(impersonationId, userId);
const userSettings = this.request.user.Settings.settings as UserSettings; const userSettings = this.request.user.Settings.settings as UserSettings;
const { accounts, holdings, markets, marketsAdvanced, summary } = const { accounts, holdings, markets, marketsAdvanced, summary } =

Loading…
Cancel
Save