Browse Source

Task/rename time weighted investment to average investment (#7918)

Rename time weighted investment to average investment
pull/7919/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
74d564a2b2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 111
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts
  3. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  4. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
  5. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
  6. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  7. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  8. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  9. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  10. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
  11. 8
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  12. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  13. 4
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts
  14. 94
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.ts
  15. 16
      apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts
  16. 4
      apps/api/src/app/portfolio/portfolio.service.spec.ts
  17. 14
      apps/api/src/app/portfolio/portfolio.service.ts
  18. 43
      libs/common/src/lib/models/portfolio-snapshot-holding.ts

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

@ -355,6 +355,8 @@ export abstract class PortfolioCalculator {
const accumulatedValuesByDate: {
[date: string]: {
investmentValueWithCurrencyEffect: Big;
totalAverageInvestmentValue: Big;
totalAverageInvestmentValueWithCurrencyEffect: Big;
totalCashValueWithCurrencyEffect: Big;
totalCurrentValue: Big;
totalCurrentValueWithCurrencyEffect: Big;
@ -363,13 +365,13 @@ export abstract class PortfolioCalculator {
totalNetPerformanceValue: Big;
totalNetPerformanceValueWithCurrencyEffect: Big;
totalNetWorthValueWithCurrencyEffect: Big;
totalTimeWeightedInvestmentValue: Big;
totalTimeWeightedInvestmentValueWithCurrencyEffect: Big;
};
} = {};
const valuesByAssetProfileIdentifier: {
[assetProfileIdentifier: string]: {
averageInvestmentValues: { [date: string]: Big };
averageInvestmentValuesWithCurrencyEffect: { [date: string]: Big };
currentValues: { [date: string]: Big };
currentValuesWithCurrencyEffect: { [date: string]: Big };
investmentValuesAccumulated: { [date: string]: Big };
@ -378,8 +380,6 @@ export abstract class PortfolioCalculator {
netPerformanceValues: { [date: string]: Big };
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
netWorthValuesWithCurrencyEffect: { [date: string]: Big };
timeWeightedInvestmentValues: { [date: string]: Big };
timeWeightedInvestmentValuesWithCurrencyEffect: { [date: string]: Big };
};
} = {};
@ -403,6 +403,10 @@ export abstract class PortfolioCalculator {
item.symbol === this.currency;
const {
averageInvestment,
averageInvestmentValues,
averageInvestmentValuesWithCurrencyEffect,
averageInvestmentWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
grossPerformance,
@ -419,10 +423,6 @@ export abstract class PortfolioCalculator {
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
timeWeightedInvestment,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect,
timeWeightedInvestmentWithCurrencyEffect,
totalDividend,
totalDividendInBaseCurrency,
totalInterestInBaseCurrency,
@ -450,6 +450,8 @@ export abstract class PortfolioCalculator {
valuesByAssetProfileIdentifier[assetProfileIdentifier] =
isCashInBaseCurrency
? {
averageInvestmentValues: {},
averageInvestmentValuesWithCurrencyEffect: {},
currentValues: {},
currentValuesWithCurrencyEffect: {},
investmentValuesAccumulated: {},
@ -457,11 +459,11 @@ export abstract class PortfolioCalculator {
investmentValuesWithCurrencyEffect: {},
netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {},
netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect,
timeWeightedInvestmentValues: {},
timeWeightedInvestmentValuesWithCurrencyEffect: {}
netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect
}
: {
averageInvestmentValues,
averageInvestmentValuesWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
investmentValuesAccumulated,
@ -469,14 +471,12 @@ export abstract class PortfolioCalculator {
investmentValuesWithCurrencyEffect,
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect,
netWorthValuesWithCurrencyEffect: currentValuesWithCurrencyEffect
};
positions.push({
timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect,
averageInvestment,
averageInvestmentWithCurrencyEffect,
activitiesCount: item.activitiesCount,
averagePrice: item.averagePrice,
currency: item.currency,
@ -586,12 +586,12 @@ export abstract class PortfolioCalculator {
assetProfileValues.netWorthValuesWithCurrencyEffect?.[dateString] ??
new Big(0);
const timeWeightedInvestmentValue =
assetProfileValues.timeWeightedInvestmentValues?.[dateString] ??
const averageInvestmentValue =
assetProfileValues.averageInvestmentValues?.[dateString] ??
new Big(0);
const timeWeightedInvestmentValueWithCurrencyEffect =
assetProfileValues.timeWeightedInvestmentValuesWithCurrencyEffect?.[
const averageInvestmentValueWithCurrencyEffect =
assetProfileValues.averageInvestmentValuesWithCurrencyEffect?.[
dateString
] ?? new Big(0);
@ -600,6 +600,14 @@ export abstract class PortfolioCalculator {
accumulatedValuesByDate[dateString]
?.investmentValueWithCurrencyEffect ?? new Big(0)
).add(investmentValueWithCurrencyEffect),
totalAverageInvestmentValue: (
accumulatedValuesByDate[dateString]?.totalAverageInvestmentValue ??
new Big(0)
).add(averageInvestmentValue),
totalAverageInvestmentValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalAverageInvestmentValueWithCurrencyEffect ?? new Big(0)
).add(averageInvestmentValueWithCurrencyEffect),
totalCashValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalCashValueWithCurrencyEffect ?? new Big(0)
@ -634,15 +642,7 @@ export abstract class PortfolioCalculator {
totalNetWorthValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalNetWorthValueWithCurrencyEffect ?? new Big(0)
).add(netWorthValueWithCurrencyEffect),
totalTimeWeightedInvestmentValue: (
accumulatedValuesByDate[dateString]
?.totalTimeWeightedInvestmentValue ?? new Big(0)
).add(timeWeightedInvestmentValue),
totalTimeWeightedInvestmentValueWithCurrencyEffect: (
accumulatedValuesByDate[dateString]
?.totalTimeWeightedInvestmentValueWithCurrencyEffect ?? new Big(0)
).add(timeWeightedInvestmentValueWithCurrencyEffect)
).add(netWorthValueWithCurrencyEffect)
};
}
}
@ -652,6 +652,8 @@ export abstract class PortfolioCalculator {
).map(([date, values]) => {
const {
investmentValueWithCurrencyEffect,
totalAverageInvestmentValue,
totalAverageInvestmentValueWithCurrencyEffect,
totalCashValueWithCurrencyEffect,
totalCurrentValue,
totalCurrentValueWithCurrencyEffect,
@ -659,22 +661,18 @@ export abstract class PortfolioCalculator {
totalInvestmentValueWithCurrencyEffect,
totalNetPerformanceValue,
totalNetPerformanceValueWithCurrencyEffect,
totalNetWorthValueWithCurrencyEffect,
totalTimeWeightedInvestmentValue,
totalTimeWeightedInvestmentValueWithCurrencyEffect
totalNetWorthValueWithCurrencyEffect
} = values;
const netPerformanceInPercentage = totalTimeWeightedInvestmentValue.eq(0)
const netPerformanceInPercentage = totalAverageInvestmentValue.eq(0)
? 0
: totalNetPerformanceValue
.div(totalTimeWeightedInvestmentValue)
.toNumber();
: totalNetPerformanceValue.div(totalAverageInvestmentValue).toNumber();
const netPerformanceInPercentageWithCurrencyEffect =
totalTimeWeightedInvestmentValueWithCurrencyEffect.eq(0)
totalAverageInvestmentValueWithCurrencyEffect.eq(0)
? 0
: totalNetPerformanceValueWithCurrencyEffect
.div(totalTimeWeightedInvestmentValueWithCurrencyEffect)
.div(totalAverageInvestmentValueWithCurrencyEffect)
.toNumber();
return {
@ -868,6 +866,10 @@ export abstract class PortfolioCalculator {
protected getEmptyHoldingPerformance(): HoldingPerformance {
return {
averageInvestment: new Big(0),
averageInvestmentValues: {},
averageInvestmentValuesWithCurrencyEffect: {},
averageInvestmentWithCurrencyEffect: new Big(0),
currentValues: {},
currentValuesWithCurrencyEffect: {},
grossPerformance: new Big(0),
@ -884,10 +886,6 @@ export abstract class PortfolioCalculator {
netPerformanceValues: {},
netPerformanceValuesWithCurrencyEffect: {},
netPerformanceWithCurrencyEffectMap: {},
timeWeightedInvestment: new Big(0),
timeWeightedInvestmentValues: {},
timeWeightedInvestmentValuesWithCurrencyEffect: {},
timeWeightedInvestmentWithCurrencyEffect: new Big(0),
totalDividend: new Big(0),
totalDividendInBaseCurrency: new Big(0),
totalInterestInBaseCurrency: new Big(0),
@ -1346,12 +1344,12 @@ export abstract class PortfolioCalculator {
const chart: HistoricalDataItem[] = [];
const averageInvestmentValues: number[] = [];
const averageInvestmentValuesWithCurrencyEffect: number[] = [];
let grossPerformanceAtStartDate: number;
let grossPerformanceWithCurrencyEffectAtStartDate: number;
let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number;
const timeWeightedInvestmentValues: number[] = [];
const timeWeightedInvestmentValuesWithCurrencyEffect: number[] = [];
for (const historicalDataItem of historicalData) {
const date = resetHours(parseDate(historicalDataItem.date));
@ -1382,28 +1380,27 @@ export abstract class PortfolioCalculator {
// investment of each day. Thus the range starts with the value of its
// first day, and subsequent buy and sell activities stay included.
if (historicalDataItem.totalInvestment > 0) {
timeWeightedInvestmentValues.push(
averageInvestmentValues.push(
historicalDataItem.totalInvestment + grossPerformanceAtStartDate
);
}
if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) {
timeWeightedInvestmentValuesWithCurrencyEffect.push(
averageInvestmentValuesWithCurrencyEffect.push(
historicalDataItem.totalInvestmentValueWithCurrencyEffect +
grossPerformanceWithCurrencyEffectAtStartDate
);
}
const timeWeightedInvestmentValue =
timeWeightedInvestmentValues.length > 0
? sum(timeWeightedInvestmentValues) /
timeWeightedInvestmentValues.length
const averageInvestmentValue =
averageInvestmentValues.length > 0
? sum(averageInvestmentValues) / averageInvestmentValues.length
: 0;
const timeWeightedInvestmentValueWithCurrencyEffect =
timeWeightedInvestmentValuesWithCurrencyEffect.length > 0
? sum(timeWeightedInvestmentValuesWithCurrencyEffect) /
timeWeightedInvestmentValuesWithCurrencyEffect.length
const averageInvestmentValueWithCurrencyEffect =
averageInvestmentValuesWithCurrencyEffect.length > 0
? sum(averageInvestmentValuesWithCurrencyEffect) /
averageInvestmentValuesWithCurrencyEffect.length
: 0;
chart.push({
@ -1412,13 +1409,13 @@ export abstract class PortfolioCalculator {
netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffectSinceStartDate,
netPerformanceInPercentage:
timeWeightedInvestmentValue > 0
? netPerformanceSinceStartDate / timeWeightedInvestmentValue
averageInvestmentValue > 0
? netPerformanceSinceStartDate / averageInvestmentValue
: 0,
netPerformanceInPercentageWithCurrencyEffect:
timeWeightedInvestmentValueWithCurrencyEffect > 0
averageInvestmentValueWithCurrencyEffect > 0
? netPerformanceWithCurrencyEffectSinceStartDate /
timeWeightedInvestmentValueWithCurrencyEffect
averageInvestmentValueWithCurrencyEffect
: 0
});
}

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

@ -148,6 +148,10 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 2,
averageInvestment: new Big('474.93846153846153846154'),
averageInvestmentWithCurrencyEffect: new Big(
'474.93846153846153846154'
),
averagePrice: new Big('139.75'),
currency: 'CHF',
dataSource: 'YAHOO',
@ -177,10 +181,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('4'),
symbol: 'BALN.SW',
tags: [],
timeWeightedInvestment: new Big('474.93846153846153846154'),
timeWeightedInvestmentWithCurrencyEffect: new Big(
'474.93846153846153846154'
),
valueInBaseCurrency: new Big('595.6')
}
],

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

@ -164,6 +164,10 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 3,
averageInvestment: new Big('285.80000000000000396627'),
averageInvestmentWithCurrencyEffect: new Big(
'285.80000000000000396627'
),
averagePrice: new Big('0'),
currency: 'CHF',
dataSource: 'YAHOO',
@ -191,10 +195,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('0'),
symbol: 'BALN.SW',
tags: [],
timeWeightedInvestment: new Big('285.80000000000000396627'),
timeWeightedInvestmentWithCurrencyEffect: new Big(
'285.80000000000000396627'
),
valueInBaseCurrency: new Big('0')
}
],

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

@ -155,6 +155,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 2,
averageInvestment: new Big('285.8'),
averageInvestmentWithCurrencyEffect: new Big('285.8'),
averagePrice: new Big('0'),
currency: 'CHF',
dataSource: 'YAHOO',
@ -184,8 +186,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('0'),
symbol: 'BALN.SW',
tags: [],
timeWeightedInvestment: new Big('285.8'),
timeWeightedInvestmentWithCurrencyEffect: new Big('285.8'),
valueInBaseCurrency: new Big('0')
}
],

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

@ -138,6 +138,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 1,
averageInvestment: new Big('273.2'),
averageInvestmentWithCurrencyEffect: new Big('273.2'),
averagePrice: new Big('136.6'),
currency: 'CHF',
dataSource: 'YAHOO',
@ -173,8 +175,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('2'),
symbol: 'BALN.SW',
tags: [],
timeWeightedInvestment: new Big('273.2'),
timeWeightedInvestmentWithCurrencyEffect: new Big('273.2'),
valueInBaseCurrency: new Big('297.8')
}
],

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts

@ -199,6 +199,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 1,
averageInvestment: new Big('44558.42'),
averageInvestmentWithCurrencyEffect: new Big('44558.42'),
averagePrice: new Big('44558.42'),
currency: 'USD',
dataSource: 'YAHOO',
@ -228,8 +230,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1'),
symbol: 'BTCUSD',
tags: [],
timeWeightedInvestment: new Big('44558.42'),
timeWeightedInvestmentWithCurrencyEffect: new Big('44558.42'),
valueInBaseCurrency: new Big('43099.7')
}
],

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

@ -167,6 +167,10 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 2,
averageInvestment: new Big('623.73992504096715328467'),
averageInvestmentWithCurrencyEffect: new Big(
'636.79469348020066587024'
),
averagePrice: new Big('320.43'),
currency: 'USD',
dataSource: 'YAHOO',
@ -202,10 +206,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1'),
symbol: 'BTCUSD',
tags: [],
timeWeightedInvestment: new Big('623.73992504096715328467'),
timeWeightedInvestmentWithCurrencyEffect: new Big(
'636.79469348020066587024'
),
valueInBaseCurrency: new Big('13298.425356')
}
],

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts

@ -199,6 +199,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 1,
averageInvestment: new Big('44558.42'),
averageInvestmentWithCurrencyEffect: new Big('44558.42'),
averagePrice: new Big('44558.42'),
currency: 'USD',
dataSource: 'YAHOO',
@ -228,8 +230,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1'),
symbol: 'BTCUSD',
tags: [],
timeWeightedInvestment: new Big('44558.42'),
timeWeightedInvestmentWithCurrencyEffect: new Big('44558.42'),
valueInBaseCurrency: new Big('43099.7')
}
],

8
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -252,6 +252,10 @@ describe('PortfolioCalculator', () => {
*/
expect(position).toMatchObject<PortfolioSnapshotHolding>({
activitiesCount: 2,
averageInvestment: new Big('912.47956403269754768392'),
averageInvestmentWithCurrencyEffect: new Big(
'852.45231607629427792916'
),
averagePrice: new Big(1),
currency: 'USD',
dataSource: DataSource.YAHOO,
@ -292,10 +296,6 @@ describe('PortfolioCalculator', () => {
},
quantity: new Big(2000),
symbol: 'USD',
timeWeightedInvestment: new Big('912.47956403269754768392'),
timeWeightedInvestmentWithCurrencyEffect: new Big(
'852.45231607629427792916'
),
valueInBaseCurrency: new Big(1820)
});

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

@ -144,6 +144,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 1,
averageInvestment: new Big('89.12').mul(0.8854),
averageInvestmentWithCurrencyEffect: new Big('82.329056'),
averagePrice: new Big('89.12'),
currency: 'USD',
dataSource: 'YAHOO',
@ -173,8 +175,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1'),
symbol: 'GOOGL',
tags: [],
timeWeightedInvestment: new Big('89.12').mul(0.8854),
timeWeightedInvestmentWithCurrencyEffect: new Big('82.329056'),
valueInBaseCurrency: new Big('103.10483')
}
],

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

@ -144,6 +144,10 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 2,
averageInvestment: new Big('145.10285714285714285714'),
averageInvestmentWithCurrencyEffect: new Big(
'145.10285714285714285714'
),
averagePrice: new Big('75.80'),
currency: 'CHF',
dataSource: 'YAHOO',
@ -173,10 +177,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1'),
symbol: 'NOVN.SW',
tags: [],
timeWeightedInvestment: new Big('145.10285714285714285714'),
timeWeightedInvestmentWithCurrencyEffect: new Big(
'145.10285714285714285714'
),
valueInBaseCurrency: new Big('87.8')
}
],

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

@ -197,6 +197,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 2,
averageInvestment: new Big('151.6'),
averageInvestmentWithCurrencyEffect: new Big('151.6'),
averagePrice: new Big('0'),
currency: 'CHF',
dataSource: 'YAHOO',
@ -226,8 +228,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('0'),
symbol: 'NOVN.SW',
tags: [],
timeWeightedInvestment: new Big('151.6'),
timeWeightedInvestmentWithCurrencyEffect: new Big('151.6'),
valueInBaseCurrency: new Big('0')
}
],

4
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts

@ -121,6 +121,8 @@ describe('PortfolioCalculator', () => {
positions: [
{
activitiesCount: 1,
averageInvestment: new Big('500000'),
averageInvestmentWithCurrencyEffect: new Big('500000'),
averagePrice: new Big('500000'),
currency: 'USD',
dataSource: 'MANUAL',
@ -148,8 +150,6 @@ describe('PortfolioCalculator', () => {
quantity: new Big('1'),
symbol: 'dac95060-d4f2-4653-a253-2c45e6fb5cde',
tags: [],
timeWeightedInvestment: new Big('500000'),
timeWeightedInvestmentWithCurrencyEffect: new Big('500000'),
valueInBaseCurrency: new Big('500000')
}
],

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

@ -31,12 +31,12 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
let grossPerformanceWithCurrencyEffect = new Big(0);
let hasErrors = false;
let netPerformance = new Big(0);
let totalAverageInvestment = new Big(0);
let totalAverageInvestmentWithCurrencyEffect = new Big(0);
let totalFeesWithCurrencyEffect = new Big(0);
const totalInterestWithCurrencyEffect = new Big(0);
let totalInvestment = new Big(0);
let totalInvestmentWithCurrencyEffect = new Big(0);
let totalTimeWeightedInvestment = new Big(0);
let totalTimeWeightedInvestmentWithCurrencyEffect = new Big(0);
for (const currentPosition of positions) {
if (currentPosition.valueInBaseCurrency) {
@ -83,14 +83,14 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
hasErrors = true;
}
if (currentPosition.timeWeightedInvestment) {
totalTimeWeightedInvestment = totalTimeWeightedInvestment.plus(
currentPosition.timeWeightedInvestment
if (currentPosition.averageInvestment) {
totalAverageInvestment = totalAverageInvestment.plus(
currentPosition.averageInvestment
);
totalTimeWeightedInvestmentWithCurrencyEffect =
totalTimeWeightedInvestmentWithCurrencyEffect.plus(
currentPosition.timeWeightedInvestmentWithCurrencyEffect
totalAverageInvestmentWithCurrencyEffect =
totalAverageInvestmentWithCurrencyEffect.plus(
currentPosition.averageInvestmentWithCurrencyEffect
);
} else if (!currentPosition.quantity.eq(0)) {
this.logger.warn(
@ -137,14 +137,14 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
};
start: Date;
} & AssetProfileIdentifier): HoldingPerformance {
let investmentAtStartDate: Big;
let investmentAtStartDateWithCurrencyEffect: Big;
const timeWeightedInvestmentValues: { [date: string]: Big } = {};
const averageInvestmentValues: { [date: string]: Big } = {};
const timeWeightedInvestmentValuesWithCurrencyEffect: {
const averageInvestmentValuesWithCurrencyEffect: {
[date: string]: Big;
} = {};
let investmentAtStartDate: Big;
let investmentAtStartDateWithCurrencyEffect: Big;
let valueAtStartDate: Big;
let valueAtStartDateWithCurrencyEffect: Big;
@ -248,9 +248,9 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
return itemType === 'end';
});
let sumOfWeightedInvestments = new Big(0);
let sumOfWeightedInvestmentsWithCurrencyEffect = new Big(0);
let totalInvestmentDays = 0;
let sumOfTimeWeightedInvestments = new Big(0);
let sumOfTimeWeightedInvestmentsWithCurrencyEffect = new Big(0);
for (let i = 0; i < items.length; i += 1) {
const item = items[i];
@ -269,7 +269,7 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
if (i > indexOfStartActivity) {
// Only consider periods with an investment for the calculation of
// the time weighted investment
// the average investment
if (
item.valueBeforeTransaction.gt(0) &&
['BUY', 'SELL'].includes(item.type)
@ -289,18 +289,18 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
}
// Sum up the total investment days since the start date to calculate
// the time weighted investment
// the average investment
totalInvestmentDays += daysSinceLastActivity;
sumOfTimeWeightedInvestments = sumOfTimeWeightedInvestments.add(
sumOfWeightedInvestments = sumOfWeightedInvestments.add(
valueAtStartDate
.minus(investmentAtStartDate)
.plus(item.investmentBeforeTransaction)
.mul(daysSinceLastActivity)
);
sumOfTimeWeightedInvestmentsWithCurrencyEffect =
sumOfTimeWeightedInvestmentsWithCurrencyEffect.add(
sumOfWeightedInvestmentsWithCurrencyEffect =
sumOfWeightedInvestmentsWithCurrencyEffect.add(
valueAtStartDateWithCurrencyEffect
.minus(investmentAtStartDateWithCurrencyEffect)
.plus(item.investmentBeforeTransactionWithCurrencyEffect)
@ -309,17 +309,17 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
}
// If duration is effectively zero (first day), use the actual investment as the base.
// Otherwise, use the calculated time-weighted average.
timeWeightedInvestmentValues[item.date] =
// Otherwise, use the calculated average investment.
averageInvestmentValues[item.date] =
totalInvestmentDays > Number.EPSILON
? sumOfTimeWeightedInvestments.div(totalInvestmentDays)
? sumOfWeightedInvestments.div(totalInvestmentDays)
: item.investment.gt(0)
? item.investment
: new Big(0);
timeWeightedInvestmentValuesWithCurrencyEffect[item.date] =
averageInvestmentValuesWithCurrencyEffect[item.date] =
totalInvestmentDays > Number.EPSILON
? sumOfTimeWeightedInvestmentsWithCurrencyEffect.div(
? sumOfWeightedInvestmentsWithCurrencyEffect.div(
totalInvestmentDays
)
: item.investmentWithCurrencyEffect.gt(0)
@ -363,31 +363,25 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
.minus(grossPerformanceAtStartDate)
.minus(fees.minus(feesAtStartDate));
const timeWeightedAverageInvestmentBetweenStartAndEndDate =
const averageInvestmentBetweenStartAndEndDate =
totalInvestmentDays > 0
? sumOfTimeWeightedInvestments.div(totalInvestmentDays)
? sumOfWeightedInvestments.div(totalInvestmentDays)
: new Big(0);
const timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect =
const averageInvestmentBetweenStartAndEndDateWithCurrencyEffect =
totalInvestmentDays > 0
? sumOfTimeWeightedInvestmentsWithCurrencyEffect.div(
totalInvestmentDays
)
? sumOfWeightedInvestmentsWithCurrencyEffect.div(totalInvestmentDays)
: new Big(0);
const grossPerformancePercentage =
timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0)
? totalGrossPerformance.div(
timeWeightedAverageInvestmentBetweenStartAndEndDate
)
averageInvestmentBetweenStartAndEndDate.gt(0)
? totalGrossPerformance.div(averageInvestmentBetweenStartAndEndDate)
: new Big(0);
const grossPerformancePercentageWithCurrencyEffect =
timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.gt(
0
)
averageInvestmentBetweenStartAndEndDateWithCurrencyEffect.gt(0)
? totalGrossPerformanceWithCurrencyEffect.div(
timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect
averageInvestmentBetweenStartAndEndDateWithCurrencyEffect
)
: new Big(0);
@ -401,11 +395,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
.div(totalQuantity)
: new Big(0);
const netPerformancePercentage =
timeWeightedAverageInvestmentBetweenStartAndEndDate.gt(0)
? totalNetPerformance.div(
timeWeightedAverageInvestmentBetweenStartAndEndDate
const netPerformancePercentage = averageInvestmentBetweenStartAndEndDate.gt(
0
)
? totalNetPerformance.div(averageInvestmentBetweenStartAndEndDate)
: new Big(0);
const netPerformancePercentageWithCurrencyEffectMap: {
@ -512,10 +505,10 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
Total investment with currency effect: ${totalInvestmentWithCurrencyEffect.toFixed(
2
)}
Time weighted investment: ${timeWeightedAverageInvestmentBetweenStartAndEndDate.toFixed(
Average investment: ${averageInvestmentBetweenStartAndEndDate.toFixed(
2
)}
Time weighted investment with currency effect: ${timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.toFixed(
Average investment with currency effect: ${averageInvestmentBetweenStartAndEndDateWithCurrencyEffect.toFixed(
2
)}
Total dividend: ${totalDividend.toFixed(2)}
@ -541,6 +534,8 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
}
return {
averageInvestmentValues,
averageInvestmentValuesWithCurrencyEffect,
currentValues,
currentValuesWithCurrencyEffect,
grossPerformancePercentage,
@ -553,23 +548,20 @@ export class RoaiPortfolioCalculator extends PortfolioCalculator {
netPerformanceValues,
netPerformanceValuesWithCurrencyEffect,
netPerformanceWithCurrencyEffectMap,
timeWeightedInvestmentValues,
timeWeightedInvestmentValuesWithCurrencyEffect,
totalDividend,
totalDividendInBaseCurrency,
totalInterestInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect,
totalLiabilitiesInBaseCurrency,
averageInvestment: averageInvestmentBetweenStartAndEndDate,
averageInvestmentWithCurrencyEffect:
averageInvestmentBetweenStartAndEndDateWithCurrencyEffect,
grossPerformance: totalGrossPerformance,
grossPerformanceWithCurrencyEffect:
totalGrossPerformanceWithCurrencyEffect,
hasErrors: totalQuantity.gt(0) && (!initialValue || !unitPriceAtEndDate),
netPerformance: totalNetPerformance,
timeWeightedInvestment:
timeWeightedAverageInvestmentBetweenStartAndEndDate,
timeWeightedInvestmentWithCurrencyEffect:
timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect
netPerformance: totalNetPerformance
};
}

16
apps/api/src/app/portfolio/interfaces/holding-performance.interface.ts

@ -3,6 +3,14 @@ import { DateRange } from '@ghostfolio/common/types';
import { Big } from 'big.js';
export interface HoldingPerformance {
averageInvestment: Big;
averageInvestmentValues: {
[date: string]: Big;
};
averageInvestmentValuesWithCurrencyEffect: {
[date: string]: Big;
};
averageInvestmentWithCurrencyEffect: Big;
currentValues: {
[date: string]: Big;
};
@ -31,14 +39,6 @@ export interface HoldingPerformance {
};
netPerformanceValuesWithCurrencyEffect: { [date: string]: Big };
netPerformanceWithCurrencyEffectMap: { [key: DateRange]: Big };
timeWeightedInvestment: Big;
timeWeightedInvestmentValues: {
[date: string]: Big;
};
timeWeightedInvestmentValuesWithCurrencyEffect: {
[date: string]: Big;
};
timeWeightedInvestmentWithCurrencyEffect: Big;
totalDividend: Big;
totalDividendInBaseCurrency: Big;
totalInterestInBaseCurrency: Big;

4
apps/api/src/app/portfolio/portfolio.service.spec.ts

@ -406,6 +406,8 @@ describe('PortfolioService', () => {
const usdPosition = {
activitiesCount: 1,
averageInvestment: new Big(0),
averageInvestmentWithCurrencyEffect: new Big(0),
averagePrice: new Big(1),
currency: 'USD',
dataSource: DataSource.YAHOO,
@ -429,8 +431,6 @@ describe('PortfolioService', () => {
quantity: new Big(quantity),
symbol: 'USD',
tags: [],
timeWeightedInvestment: new Big(0),
timeWeightedInvestmentWithCurrencyEffect: new Big(0),
valueInBaseCurrency: new Big(1820)
};

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

@ -984,6 +984,8 @@ export class PortfolioService {
const {
activitiesCount,
averageInvestment,
averageInvestmentWithCurrencyEffect,
averagePrice,
currency,
dateOfFirstActivity,
@ -1000,9 +1002,7 @@ export class PortfolioService {
netPerformancePercentageWithCurrencyEffectMap,
netPerformanceWithCurrencyEffectMap,
quantity,
tags,
timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect
tags
} = holding;
const activitiesOfHolding = activities.filter((activity) => {
@ -1017,9 +1017,9 @@ export class PortfolioService {
new Date(),
parseDate(dateOfFirstActivity)
),
netPerformancePercentage: timeWeightedInvestment.eq(0)
netPerformancePercentage: averageInvestment.eq(0)
? new Big(0)
: dividendInBaseCurrency.div(timeWeightedInvestment)
: dividendInBaseCurrency.div(averageInvestment)
});
const dividendYieldPercentWithCurrencyEffect =
@ -1028,9 +1028,9 @@ export class PortfolioService {
new Date(),
parseDate(dateOfFirstActivity)
),
netPerformancePercentage: timeWeightedInvestmentWithCurrencyEffect.eq(0)
netPerformancePercentage: averageInvestmentWithCurrencyEffect.eq(0)
? new Big(0)
: dividendInBaseCurrency.div(timeWeightedInvestmentWithCurrencyEffect)
: dividendInBaseCurrency.div(averageInvestmentWithCurrencyEffect)
});
const historicalData = await this.dataProviderService.getHistorical(

43
libs/common/src/lib/models/portfolio-snapshot-holding.ts

@ -6,11 +6,44 @@ import { DateRange } from '@ghostfolio/common/types';
import { DataSource, Tag } from '@prisma/client';
import { Big } from 'big.js';
import { Transform, Type } from 'class-transformer';
import { Expose, Transform, Type } from 'class-transformer';
/**
* @deprecated Backward compatibility to read the portfolio snapshots which
* have been cached with the former name of the property
*
* TODO: Remove the fallback with the next release, together with the
* `@Expose()` decorators of `averageInvestment` and
* `averageInvestmentWithCurrencyEffect`
*/
function transformToBigWithFallback(formerKey: string) {
return ({ obj, value }: { obj: Record<string, string>; value: string }) => {
const valueOrFallback = value ?? obj[formerKey];
return valueOrFallback === undefined
? undefined
: transformToBig({ value: valueOrFallback });
};
}
export class PortfolioSnapshotHolding {
activitiesCount: number;
@Expose()
@Transform(transformToBigWithFallback('timeWeightedInvestment'), {
toClassOnly: true
})
@Type(() => Big)
averageInvestment: Big;
@Expose()
@Transform(
transformToBigWithFallback('timeWeightedInvestmentWithCurrencyEffect'),
{ toClassOnly: true }
)
@Type(() => Big)
averageInvestmentWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
averagePrice: Big;
@ -83,14 +116,6 @@ export class PortfolioSnapshotHolding {
symbol: string;
tags?: Tag[];
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
timeWeightedInvestment: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
timeWeightedInvestmentWithCurrencyEffect: Big;
@Transform(transformToBig, { toClassOnly: true })
@Type(() => Big)
valueInBaseCurrency: Big;

Loading…
Cancel
Save