Browse Source

Refactorings

pull/5027/head
Dan 2 months ago
parent
commit
9d5dd5507d
  1. 87
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 29
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-symbolmetrics-helper.ts
  3. 1
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator.ts
  4. 806
      apps/api/src/app/portfolio/portfolio.service.ts

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

@ -621,8 +621,7 @@ export abstract class PortfolioCalculator {
{} {}
); );
const totalInvestment = await Object.keys(holdings[endString]).reduce( return Object.keys(holdings[endString]).reduce((sum, holding) => {
(sum, holding) => {
if (!holdings[endString][holding].toNumber()) { if (!holdings[endString][holding].toNumber()) {
return sum; return sum;
} }
@ -641,10 +640,7 @@ export abstract class PortfolioCalculator {
); );
return sum.plus(new Big(price).mul(holdings[endString][holding])); return sum.plus(new Big(price).mul(holdings[endString][holding]));
} }
}, }, new Big(0));
new Big(0)
);
return totalInvestment;
} }
@LogPerformance @LogPerformance
@ -836,42 +832,6 @@ export abstract class PortfolioCalculator {
}; };
} }
private calculateTimeWeightedPerformance(
lastTimeWeightedPerformancePercentage: number,
historicalDataItem: HistoricalDataItem,
lastTimeWeightedPerformancePercentageWithCurrencyEffect: number,
timeWeightedPerformanceInPercentage: number,
timeWeightedPerformanceInPercentageWithCurrencyEffect: number
): {
timeWeightedPerformanceInPercentage: number;
timeWeightedPerformanceInPercentageWithCurrencyEffect: number;
lastTimeWeightedPerformancePercentage: number;
lastTimeWeightedPerformancePercentageWithCurrencyEffect: number;
} {
timeWeightedPerformanceInPercentage = lastTimeWeightedPerformancePercentage
? (1 + timeWeightedPerformanceInPercentage) *
((1 + historicalDataItem.timeWeightedPerformanceInPercentage) /
(1 + lastTimeWeightedPerformancePercentage)) -
1
: 0;
timeWeightedPerformanceInPercentageWithCurrencyEffect =
lastTimeWeightedPerformancePercentageWithCurrencyEffect
? (1 + timeWeightedPerformanceInPercentageWithCurrencyEffect) *
((1 +
historicalDataItem.timeWeightedPerformanceInPercentageWithCurrencyEffect) /
(1 + lastTimeWeightedPerformancePercentageWithCurrencyEffect)) -
1
: 0;
return {
timeWeightedPerformanceInPercentage,
timeWeightedPerformanceInPercentageWithCurrencyEffect,
lastTimeWeightedPerformancePercentage:
historicalDataItem.timeWeightedPerformanceInPercentage,
lastTimeWeightedPerformancePercentageWithCurrencyEffect:
historicalDataItem.timeWeightedPerformanceInPercentageWithCurrencyEffect
};
}
@LogPerformance @LogPerformance
protected getHistoricalDataItems(accumulatedValuesByDate: { protected getHistoricalDataItems(accumulatedValuesByDate: {
[date: string]: { [date: string]: {
@ -1375,6 +1335,41 @@ export abstract class PortfolioCalculator {
return this.snapshot.totalValuablesWithCurrencyEffect; return this.snapshot.totalValuablesWithCurrencyEffect;
} }
private calculateTimeWeightedPerformance(
lastTimeWeightedPerformancePercentage: number,
historicalDataItem: HistoricalDataItem,
lastTimeWeightedPerformancePercentageWithCurrencyEffect: number,
timeWeightedPerformanceInPercentage: number,
timeWeightedPerformanceInPercentageWithCurrencyEffect: number
): {
timeWeightedPerformanceInPercentage: number;
timeWeightedPerformanceInPercentageWithCurrencyEffect: number;
lastTimeWeightedPerformancePercentage: number;
lastTimeWeightedPerformancePercentageWithCurrencyEffect: number;
} {
timeWeightedPerformanceInPercentage = lastTimeWeightedPerformancePercentage
? (1 + timeWeightedPerformanceInPercentage) *
((1 + historicalDataItem.timeWeightedPerformanceInPercentage) /
(1 + lastTimeWeightedPerformancePercentage)) -
1
: 0;
timeWeightedPerformanceInPercentageWithCurrencyEffect =
lastTimeWeightedPerformancePercentageWithCurrencyEffect
? (1 + timeWeightedPerformanceInPercentageWithCurrencyEffect) *
((1 +
historicalDataItem.timeWeightedPerformanceInPercentageWithCurrencyEffect) /
(1 + lastTimeWeightedPerformancePercentageWithCurrencyEffect)) -
1
: 0;
return {
timeWeightedPerformanceInPercentage,
timeWeightedPerformanceInPercentageWithCurrencyEffect,
lastTimeWeightedPerformancePercentage:
historicalDataItem.timeWeightedPerformanceInPercentage,
lastTimeWeightedPerformancePercentageWithCurrencyEffect:
historicalDataItem.timeWeightedPerformanceInPercentageWithCurrencyEffect
};
}
private calculateHoldings( private calculateHoldings(
investmentByDate: { [date: string]: PortfolioOrder[] }, investmentByDate: { [date: string]: PortfolioOrder[] },
@ -1511,7 +1506,9 @@ export abstract class PortfolioCalculator {
totalNetPerformanceValue: new Big(0), totalNetPerformanceValue: new Big(0),
totalNetPerformanceValueWithCurrencyEffect: new Big(0), totalNetPerformanceValueWithCurrencyEffect: new Big(0),
totalTimeWeightedInvestmentValue: new Big(0), totalTimeWeightedInvestmentValue: new Big(0),
totalTimeWeightedInvestmentValueWithCurrencyEffect: new Big(0) totalTimeWeightedInvestmentValueWithCurrencyEffect: new Big(0),
totalCurrentValue: new Big(0),
totalCurrentValueWithCurrencyEffect: new Big(0)
}; };
const timeWeightedPerformanceCurrentPeriod = this.divideByOrZero( const timeWeightedPerformanceCurrentPeriod = this.divideByOrZero(
@ -1519,7 +1516,7 @@ export abstract class PortfolioCalculator {
totalNetPerformanceValue totalNetPerformanceValue
.minus(previousValues.totalNetPerformanceValue) .minus(previousValues.totalNetPerformanceValue)
.div(div), .div(div),
previousValues.totalTimeWeightedInvestmentValue previousValues.totalCurrentValue
); );
const timeWeightedPerformanceCurrentPeriodWithCurrencyEffect = const timeWeightedPerformanceCurrentPeriodWithCurrencyEffect =
this.divideByOrZero( this.divideByOrZero(
@ -1527,7 +1524,7 @@ export abstract class PortfolioCalculator {
totalNetPerformanceValueWithCurrencyEffect totalNetPerformanceValueWithCurrencyEffect
.minus(previousValues.totalNetPerformanceValueWithCurrencyEffect) .minus(previousValues.totalNetPerformanceValueWithCurrencyEffect)
.div(div), .div(div),
previousValues.totalTimeWeightedInvestmentValueWithCurrencyEffect previousValues.totalCurrentValueWithCurrencyEffect
); );
const timeWeightedPerformanceInPercentage = new Big(1) const timeWeightedPerformanceInPercentage = new Big(1)

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

@ -1,4 +1,3 @@
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { SymbolMetrics } from '@ghostfolio/common/interfaces'; import { SymbolMetrics } from '@ghostfolio/common/interfaces';
@ -28,7 +27,6 @@ export class RoiPortfolioCalculatorSymbolMetricsHelper {
this.chartDates = chartDates; this.chartDates = chartDates;
} }
@LogPerformance
public calculateNetPerformanceByDateRange( public calculateNetPerformanceByDateRange(
start: Date, start: Date,
symbolMetricsHelper: PortfolioCalculatorSymbolMetricsHelperObject symbolMetricsHelper: PortfolioCalculatorSymbolMetricsHelperObject
@ -61,20 +59,27 @@ export class RoiPortfolioCalculatorSymbolMetricsHelper {
new Big(0)) new Big(0))
) ?? new Big(0); ) ?? new Big(0);
let investmentBasis =
symbolMetricsHelper.symbolMetrics.currentValuesWithCurrencyEffect[
rangeStartDateString
];
if (
!symbolMetricsHelper.symbolMetrics.currentValuesWithCurrencyEffect[
rangeStartDateString
]?.gt(0)
) {
investmentBasis =
symbolMetricsHelper.symbolMetrics
.timeWeightedInvestmentValuesWithCurrencyEffect[rangeEndDateString];
}
symbolMetricsHelper.symbolMetrics.netPerformancePercentageWithCurrencyEffectMap[ symbolMetricsHelper.symbolMetrics.netPerformancePercentageWithCurrencyEffectMap[
dateRange dateRange
] = ] = investmentBasis.gt(0)
symbolMetricsHelper.symbolMetrics.timeWeightedInvestmentValuesWithCurrencyEffect[
rangeEndDateString
]?.gt(0)
? symbolMetricsHelper.symbolMetrics.netPerformanceWithCurrencyEffectMap[ ? symbolMetricsHelper.symbolMetrics.netPerformanceWithCurrencyEffectMap[
dateRange dateRange
].div( ].div(investmentBasis)
symbolMetricsHelper.symbolMetrics
.timeWeightedInvestmentValuesWithCurrencyEffect[
rangeEndDateString
]
)
: new Big(0); : new Big(0);
} }
} }

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

@ -79,7 +79,6 @@ export class RoiPortfolioCalculator extends PortfolioCalculator {
}; };
} }
@LogPerformance
protected getSymbolMetrics({ protected getSymbolMetrics({
chartDateMap, chartDateMap,
dataSource, dataSource,

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

@ -1356,145 +1356,6 @@ export class PortfolioService {
await this.orderService.assignTags({ dataSource, symbol, tags, userId }); await this.orderService.assignTags({ dataSource, symbol, tags, userId });
} }
private getAggregatedMarkets(holdings: Record<string, PortfolioPosition>): {
markets: PortfolioDetails['markets'];
marketsAdvanced: PortfolioDetails['marketsAdvanced'];
} {
const markets: PortfolioDetails['markets'] = {
[UNKNOWN_KEY]: {
id: UNKNOWN_KEY,
valueInBaseCurrency: 0,
valueInPercentage: 0
},
developedMarkets: {
id: 'developedMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
emergingMarkets: {
id: 'emergingMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
otherMarkets: {
id: 'otherMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
}
};
const marketsAdvanced: PortfolioDetails['marketsAdvanced'] = {
[UNKNOWN_KEY]: {
id: UNKNOWN_KEY,
valueInBaseCurrency: 0,
valueInPercentage: 0
},
asiaPacific: {
id: 'asiaPacific',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
emergingMarkets: {
id: 'emergingMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
europe: {
id: 'europe',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
japan: {
id: 'japan',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
northAmerica: {
id: 'northAmerica',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
otherMarkets: {
id: 'otherMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
}
};
for (const [, position] of Object.entries(holdings)) {
const value = position.valueInBaseCurrency;
if (position.assetClass !== AssetClass.LIQUIDITY) {
if (position.countries.length > 0) {
markets.developedMarkets.valueInBaseCurrency +=
position.markets.developedMarkets * value;
markets.emergingMarkets.valueInBaseCurrency +=
position.markets.emergingMarkets * value;
markets.otherMarkets.valueInBaseCurrency +=
position.markets.otherMarkets * value;
marketsAdvanced.asiaPacific.valueInBaseCurrency +=
position.marketsAdvanced.asiaPacific * value;
marketsAdvanced.emergingMarkets.valueInBaseCurrency +=
position.marketsAdvanced.emergingMarkets * value;
marketsAdvanced.europe.valueInBaseCurrency +=
position.marketsAdvanced.europe * value;
marketsAdvanced.japan.valueInBaseCurrency +=
position.marketsAdvanced.japan * value;
marketsAdvanced.northAmerica.valueInBaseCurrency +=
position.marketsAdvanced.northAmerica * value;
marketsAdvanced.otherMarkets.valueInBaseCurrency +=
position.marketsAdvanced.otherMarkets * value;
} else {
markets[UNKNOWN_KEY].valueInBaseCurrency += value;
marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency += value;
}
}
}
const marketsTotalInBaseCurrency = getSum(
Object.values(markets).map(({ valueInBaseCurrency }) => {
return new Big(valueInBaseCurrency);
})
).toNumber();
markets.developedMarkets.valueInPercentage =
markets.developedMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets.emergingMarkets.valueInPercentage =
markets.emergingMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets.otherMarkets.valueInPercentage =
markets.otherMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets[UNKNOWN_KEY].valueInPercentage =
markets[UNKNOWN_KEY].valueInBaseCurrency / marketsTotalInBaseCurrency;
const marketsAdvancedTotal =
marketsAdvanced.asiaPacific.valueInBaseCurrency +
marketsAdvanced.emergingMarkets.valueInBaseCurrency +
marketsAdvanced.europe.valueInBaseCurrency +
marketsAdvanced.japan.valueInBaseCurrency +
marketsAdvanced.northAmerica.valueInBaseCurrency +
marketsAdvanced.otherMarkets.valueInBaseCurrency +
marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency;
marketsAdvanced.asiaPacific.valueInPercentage =
marketsAdvanced.asiaPacific.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.emergingMarkets.valueInPercentage =
marketsAdvanced.emergingMarkets.valueInBaseCurrency /
marketsAdvancedTotal;
marketsAdvanced.europe.valueInPercentage =
marketsAdvanced.europe.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.japan.valueInPercentage =
marketsAdvanced.japan.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.northAmerica.valueInPercentage =
marketsAdvanced.northAmerica.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.otherMarkets.valueInPercentage =
marketsAdvanced.otherMarkets.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced[UNKNOWN_KEY].valueInPercentage =
marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency / marketsAdvancedTotal;
return { markets, marketsAdvanced };
}
@LogPerformance @LogPerformance
private async getCashPositions({ private async getCashPositions({
cashDetails, cashDetails,
@ -1546,68 +1407,6 @@ export class PortfolioService {
return cashPositions; return cashPositions;
} }
private getDividendsByGroup({
dividends,
groupBy
}: {
dividends: InvestmentItem[];
groupBy: GroupBy;
}): InvestmentItem[] {
if (dividends.length === 0) {
return [];
}
const dividendsByGroup: InvestmentItem[] = [];
let currentDate: Date;
let investmentByGroup = new Big(0);
for (const [index, dividend] of dividends.entries()) {
if (
isSameYear(parseDate(dividend.date), currentDate) &&
(groupBy === 'year' ||
isSameMonth(parseDate(dividend.date), currentDate))
) {
// Same group: Add up dividends
investmentByGroup = investmentByGroup.plus(dividend.investment);
} else {
// New group: Store previous group and reset
if (currentDate) {
dividendsByGroup.push({
date: format(
set(currentDate, {
date: 1,
month: groupBy === 'year' ? 0 : currentDate.getMonth()
}),
DATE_FORMAT
),
investment: investmentByGroup.toNumber()
});
}
currentDate = parseDate(dividend.date);
investmentByGroup = new Big(dividend.investment);
}
if (index === dividends.length - 1) {
// Store current month (latest order)
dividendsByGroup.push({
date: format(
set(currentDate, {
date: 1,
month: groupBy === 'year' ? 0 : currentDate.getMonth()
}),
DATE_FORMAT
),
investment: investmentByGroup.toNumber()
});
}
}
return dividendsByGroup;
}
@LogPerformance @LogPerformance
private getEmergencyFundHoldingsValueInBaseCurrency({ private getEmergencyFundHoldingsValueInBaseCurrency({
holdings holdings
@ -1634,145 +1433,23 @@ export class PortfolioService {
return valueInBaseCurrencyOfEmergencyFundHoldings.toNumber(); return valueInBaseCurrencyOfEmergencyFundHoldings.toNumber();
} }
private getInitialCashPosition({ @LogPerformance
balance, private getReportStatistics(
currency evaluatedRules: PortfolioReportResponse['rules']
}: { ): PortfolioReportResponse['statistics'] {
balance: number; const rulesActiveCount = Object.values(evaluatedRules)
currency: string; .flat()
}): PortfolioPosition { .filter((rule) => {
return { return rule?.isActive === true;
currency, }).length;
allocationInPercentage: 0,
assetClass: AssetClass.LIQUIDITY, const rulesFulfilledCount = Object.values(evaluatedRules)
assetSubClass: AssetSubClass.CASH, .flat()
countries: [], .filter((rule) => {
dataSource: undefined, return rule?.value === true;
dateOfFirstActivity: undefined, }).length;
dividend: 0,
grossPerformance: 0, return { rulesActiveCount, rulesFulfilledCount };
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: 0,
holdings: [],
investment: balance,
marketPrice: 0,
name: currency,
netPerformance: 0,
netPerformancePercent: 0,
netPerformancePercentWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0,
quantity: 0,
sectors: [],
symbol: currency,
tags: [],
transactionCount: 0,
valueInBaseCurrency: balance
};
}
private getMarkets({
assetProfile
}: {
assetProfile: EnhancedSymbolProfile;
}) {
const markets = {
[UNKNOWN_KEY]: 0,
developedMarkets: 0,
emergingMarkets: 0,
otherMarkets: 0
};
const marketsAdvanced = {
[UNKNOWN_KEY]: 0,
asiaPacific: 0,
emergingMarkets: 0,
europe: 0,
japan: 0,
northAmerica: 0,
otherMarkets: 0
};
if (assetProfile.countries.length > 0) {
for (const country of assetProfile.countries) {
if (developedMarkets.includes(country.code)) {
markets.developedMarkets = new Big(markets.developedMarkets)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
markets.emergingMarkets = new Big(markets.emergingMarkets)
.plus(country.weight)
.toNumber();
} else {
markets.otherMarkets = new Big(markets.otherMarkets)
.plus(country.weight)
.toNumber();
}
if (country.code === 'JP') {
marketsAdvanced.japan = new Big(marketsAdvanced.japan)
.plus(country.weight)
.toNumber();
} else if (country.code === 'CA' || country.code === 'US') {
marketsAdvanced.northAmerica = new Big(marketsAdvanced.northAmerica)
.plus(country.weight)
.toNumber();
} else if (asiaPacificMarkets.includes(country.code)) {
marketsAdvanced.asiaPacific = new Big(marketsAdvanced.asiaPacific)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
marketsAdvanced.emergingMarkets = new Big(
marketsAdvanced.emergingMarkets
)
.plus(country.weight)
.toNumber();
} else if (europeMarkets.includes(country.code)) {
marketsAdvanced.europe = new Big(marketsAdvanced.europe)
.plus(country.weight)
.toNumber();
} else {
marketsAdvanced.otherMarkets = new Big(marketsAdvanced.otherMarkets)
.plus(country.weight)
.toNumber();
}
}
}
markets[UNKNOWN_KEY] = new Big(1)
.minus(markets.developedMarkets)
.minus(markets.emergingMarkets)
.minus(markets.otherMarkets)
.toNumber();
marketsAdvanced[UNKNOWN_KEY] = new Big(1)
.minus(marketsAdvanced.asiaPacific)
.minus(marketsAdvanced.emergingMarkets)
.minus(marketsAdvanced.europe)
.minus(marketsAdvanced.japan)
.minus(marketsAdvanced.northAmerica)
.minus(marketsAdvanced.otherMarkets)
.toNumber();
return { markets, marketsAdvanced };
}
@LogPerformance
private getReportStatistics(
evaluatedRules: PortfolioReportResponse['rules']
): PortfolioReportResponse['statistics'] {
const rulesActiveCount = Object.values(evaluatedRules)
.flat()
.filter((rule) => {
return rule?.isActive === true;
}).length;
const rulesFulfilledCount = Object.values(evaluatedRules)
.flat()
.filter((rule) => {
return rule?.value === true;
}).length;
return { rulesActiveCount, rulesFulfilledCount };
} }
@LogPerformance @LogPerformance
@ -1981,69 +1658,6 @@ export class PortfolioService {
}; };
} }
@LogPerformance
private getSumOfActivityType({
activities,
activityType,
userCurrency
}: {
activities: Activity[];
activityType: ActivityType;
userCurrency: string;
}) {
return getSum(
activities
.filter(({ isDraft, type }) => {
return isDraft === false && type === activityType;
})
.map(({ quantity, SymbolProfile, unitPrice }) => {
return new Big(
this.exchangeRateDataService.toCurrency(
new Big(quantity).mul(unitPrice).toNumber(),
SymbolProfile.currency,
userCurrency
)
);
})
);
}
private getTotalEmergencyFund({
emergencyFundHoldingsValueInBaseCurrency,
userSettings
}: {
emergencyFundHoldingsValueInBaseCurrency: number;
userSettings: UserSettings;
}) {
return new Big(
Math.max(
emergencyFundHoldingsValueInBaseCurrency,
userSettings?.emergencyFund ?? 0
)
);
}
private getUserCurrency(aUser?: UserWithSettings) {
return (
aUser?.Settings?.settings.baseCurrency ??
this.request.user?.Settings?.settings.baseCurrency ??
DEFAULT_CURRENCY
);
}
private async getUserId(aImpersonationId: string, aUserId: string) {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(aImpersonationId);
return impersonationUserId || aUserId;
}
private getUserPerformanceCalculationType(
aUser: UserWithSettings
): PerformanceCalculationType {
return aUser?.Settings?.settings.performanceCalculationType;
}
@LogPerformance @LogPerformance
private async getValueOfAccountsAndPlatforms({ private async getValueOfAccountsAndPlatforms({
activities, activities,
@ -2176,4 +1790,390 @@ export class PortfolioService {
return { accounts, platforms }; return { accounts, platforms };
} }
@LogPerformance
private getSumOfActivityType({
activities,
activityType,
userCurrency
}: {
activities: Activity[];
activityType: ActivityType;
userCurrency: string;
}) {
return getSum(
activities
.filter(({ isDraft, type }) => {
return isDraft === false && type === activityType;
})
.map(({ quantity, SymbolProfile, unitPrice }) => {
return new Big(
this.exchangeRateDataService.toCurrency(
new Big(quantity).mul(unitPrice).toNumber(),
SymbolProfile.currency,
userCurrency
)
);
})
);
}
private getInitialCashPosition({
balance,
currency
}: {
balance: number;
currency: string;
}): PortfolioPosition {
return {
currency,
allocationInPercentage: 0,
assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH,
countries: [],
dataSource: undefined,
dateOfFirstActivity: undefined,
dividend: 0,
grossPerformance: 0,
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: 0,
holdings: [],
investment: balance,
marketPrice: 0,
name: currency,
netPerformance: 0,
netPerformancePercent: 0,
netPerformancePercentWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0,
quantity: 0,
sectors: [],
symbol: currency,
tags: [],
transactionCount: 0,
valueInBaseCurrency: balance
};
}
private getDividendsByGroup({
dividends,
groupBy
}: {
dividends: InvestmentItem[];
groupBy: GroupBy;
}): InvestmentItem[] {
if (dividends.length === 0) {
return [];
}
const dividendsByGroup: InvestmentItem[] = [];
let currentDate: Date;
let investmentByGroup = new Big(0);
for (const [index, dividend] of dividends.entries()) {
if (
isSameYear(parseDate(dividend.date), currentDate) &&
(groupBy === 'year' ||
isSameMonth(parseDate(dividend.date), currentDate))
) {
// Same group: Add up dividends
investmentByGroup = investmentByGroup.plus(dividend.investment);
} else {
// New group: Store previous group and reset
if (currentDate) {
dividendsByGroup.push({
date: format(
set(currentDate, {
date: 1,
month: groupBy === 'year' ? 0 : currentDate.getMonth()
}),
DATE_FORMAT
),
investment: investmentByGroup.toNumber()
});
}
currentDate = parseDate(dividend.date);
investmentByGroup = new Big(dividend.investment);
}
if (index === dividends.length - 1) {
// Store current month (latest order)
dividendsByGroup.push({
date: format(
set(currentDate, {
date: 1,
month: groupBy === 'year' ? 0 : currentDate.getMonth()
}),
DATE_FORMAT
),
investment: investmentByGroup.toNumber()
});
}
}
return dividendsByGroup;
}
private getMarkets({
assetProfile
}: {
assetProfile: EnhancedSymbolProfile;
}) {
const markets = {
[UNKNOWN_KEY]: 0,
developedMarkets: 0,
emergingMarkets: 0,
otherMarkets: 0
};
const marketsAdvanced = {
[UNKNOWN_KEY]: 0,
asiaPacific: 0,
emergingMarkets: 0,
europe: 0,
japan: 0,
northAmerica: 0,
otherMarkets: 0
};
if (assetProfile.countries.length > 0) {
for (const country of assetProfile.countries) {
if (developedMarkets.includes(country.code)) {
markets.developedMarkets = new Big(markets.developedMarkets)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
markets.emergingMarkets = new Big(markets.emergingMarkets)
.plus(country.weight)
.toNumber();
} else {
markets.otherMarkets = new Big(markets.otherMarkets)
.plus(country.weight)
.toNumber();
}
if (country.code === 'JP') {
marketsAdvanced.japan = new Big(marketsAdvanced.japan)
.plus(country.weight)
.toNumber();
} else if (country.code === 'CA' || country.code === 'US') {
marketsAdvanced.northAmerica = new Big(marketsAdvanced.northAmerica)
.plus(country.weight)
.toNumber();
} else if (asiaPacificMarkets.includes(country.code)) {
marketsAdvanced.asiaPacific = new Big(marketsAdvanced.asiaPacific)
.plus(country.weight)
.toNumber();
} else if (emergingMarkets.includes(country.code)) {
marketsAdvanced.emergingMarkets = new Big(
marketsAdvanced.emergingMarkets
)
.plus(country.weight)
.toNumber();
} else if (europeMarkets.includes(country.code)) {
marketsAdvanced.europe = new Big(marketsAdvanced.europe)
.plus(country.weight)
.toNumber();
} else {
marketsAdvanced.otherMarkets = new Big(marketsAdvanced.otherMarkets)
.plus(country.weight)
.toNumber();
}
}
}
markets[UNKNOWN_KEY] = new Big(1)
.minus(markets.developedMarkets)
.minus(markets.emergingMarkets)
.minus(markets.otherMarkets)
.toNumber();
marketsAdvanced[UNKNOWN_KEY] = new Big(1)
.minus(marketsAdvanced.asiaPacific)
.minus(marketsAdvanced.emergingMarkets)
.minus(marketsAdvanced.europe)
.minus(marketsAdvanced.japan)
.minus(marketsAdvanced.northAmerica)
.minus(marketsAdvanced.otherMarkets)
.toNumber();
return { markets, marketsAdvanced };
}
private getTotalEmergencyFund({
emergencyFundHoldingsValueInBaseCurrency,
userSettings
}: {
emergencyFundHoldingsValueInBaseCurrency: number;
userSettings: UserSettings;
}) {
return new Big(
Math.max(
emergencyFundHoldingsValueInBaseCurrency,
userSettings?.emergencyFund ?? 0
)
);
}
private getUserCurrency(aUser?: UserWithSettings) {
return (
aUser?.Settings?.settings.baseCurrency ??
this.request.user?.Settings?.settings.baseCurrency ??
DEFAULT_CURRENCY
);
}
private async getUserId(aImpersonationId: string, aUserId: string) {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(aImpersonationId);
return impersonationUserId || aUserId;
}
private getUserPerformanceCalculationType(
aUser: UserWithSettings
): PerformanceCalculationType {
return aUser?.Settings?.settings.performanceCalculationType;
}
private getAggregatedMarkets(holdings: Record<string, PortfolioPosition>): {
markets: PortfolioDetails['markets'];
marketsAdvanced: PortfolioDetails['marketsAdvanced'];
} {
const markets: PortfolioDetails['markets'] = {
[UNKNOWN_KEY]: {
id: UNKNOWN_KEY,
valueInBaseCurrency: 0,
valueInPercentage: 0
},
developedMarkets: {
id: 'developedMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
emergingMarkets: {
id: 'emergingMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
otherMarkets: {
id: 'otherMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
}
};
const marketsAdvanced: PortfolioDetails['marketsAdvanced'] = {
[UNKNOWN_KEY]: {
id: UNKNOWN_KEY,
valueInBaseCurrency: 0,
valueInPercentage: 0
},
asiaPacific: {
id: 'asiaPacific',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
emergingMarkets: {
id: 'emergingMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
europe: {
id: 'europe',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
japan: {
id: 'japan',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
northAmerica: {
id: 'northAmerica',
valueInBaseCurrency: 0,
valueInPercentage: 0
},
otherMarkets: {
id: 'otherMarkets',
valueInBaseCurrency: 0,
valueInPercentage: 0
}
};
for (const [, position] of Object.entries(holdings)) {
const value = position.valueInBaseCurrency;
if (position.assetClass !== AssetClass.LIQUIDITY) {
if (position.countries.length > 0) {
markets.developedMarkets.valueInBaseCurrency +=
position.markets.developedMarkets * value;
markets.emergingMarkets.valueInBaseCurrency +=
position.markets.emergingMarkets * value;
markets.otherMarkets.valueInBaseCurrency +=
position.markets.otherMarkets * value;
marketsAdvanced.asiaPacific.valueInBaseCurrency +=
position.marketsAdvanced.asiaPacific * value;
marketsAdvanced.emergingMarkets.valueInBaseCurrency +=
position.marketsAdvanced.emergingMarkets * value;
marketsAdvanced.europe.valueInBaseCurrency +=
position.marketsAdvanced.europe * value;
marketsAdvanced.japan.valueInBaseCurrency +=
position.marketsAdvanced.japan * value;
marketsAdvanced.northAmerica.valueInBaseCurrency +=
position.marketsAdvanced.northAmerica * value;
marketsAdvanced.otherMarkets.valueInBaseCurrency +=
position.marketsAdvanced.otherMarkets * value;
} else {
markets[UNKNOWN_KEY].valueInBaseCurrency += value;
marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency += value;
}
}
}
const marketsTotalInBaseCurrency = getSum(
Object.values(markets).map(({ valueInBaseCurrency }) => {
return new Big(valueInBaseCurrency);
})
).toNumber();
markets.developedMarkets.valueInPercentage =
markets.developedMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets.emergingMarkets.valueInPercentage =
markets.emergingMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets.otherMarkets.valueInPercentage =
markets.otherMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets[UNKNOWN_KEY].valueInPercentage =
markets[UNKNOWN_KEY].valueInBaseCurrency / marketsTotalInBaseCurrency;
const marketsAdvancedTotal =
marketsAdvanced.asiaPacific.valueInBaseCurrency +
marketsAdvanced.emergingMarkets.valueInBaseCurrency +
marketsAdvanced.europe.valueInBaseCurrency +
marketsAdvanced.japan.valueInBaseCurrency +
marketsAdvanced.northAmerica.valueInBaseCurrency +
marketsAdvanced.otherMarkets.valueInBaseCurrency +
marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency;
marketsAdvanced.asiaPacific.valueInPercentage =
marketsAdvanced.asiaPacific.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.emergingMarkets.valueInPercentage =
marketsAdvanced.emergingMarkets.valueInBaseCurrency /
marketsAdvancedTotal;
marketsAdvanced.europe.valueInPercentage =
marketsAdvanced.europe.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.japan.valueInPercentage =
marketsAdvanced.japan.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.northAmerica.valueInPercentage =
marketsAdvanced.northAmerica.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced.otherMarkets.valueInPercentage =
marketsAdvanced.otherMarkets.valueInBaseCurrency / marketsAdvancedTotal;
marketsAdvanced[UNKNOWN_KEY].valueInPercentage =
marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency / marketsAdvancedTotal;
return { markets, marketsAdvanced };
}
} }

Loading…
Cancel
Save