Browse Source

fix performance of combination of investments

pull/239/head
Valentin Zickner 4 years ago
committed by Thomas
parent
commit
9c51a257ae
  1. 18
      apps/api/src/app/core/portfolio-calculator.spec.ts
  2. 56
      apps/api/src/app/core/portfolio-calculator.ts
  3. 36
      apps/api/src/app/portfolio/portfolio.service.ts

18
apps/api/src/app/core/portfolio-calculator.spec.ts

@ -623,6 +623,9 @@ describe('PortfolioCalculator', () => {
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
hasErrors: false, hasErrors: false,
currentValue: new Big('657.62'),
grossPerformance: new Big('-61.84'),
grossPerformancePercentage: new Big('-0.08456342256692519389'),
positions: [ positions: [
{ {
averagePrice: new Big('719.46'), averagePrice: new Big('719.46'),
@ -658,6 +661,9 @@ describe('PortfolioCalculator', () => {
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
hasErrors: false, hasErrors: false,
currentValue: new Big('657.62'),
grossPerformance: new Big('-61.84'),
grossPerformancePercentage: new Big('-0.08456342256692519389'),
positions: [ positions: [
{ {
averagePrice: new Big('719.46'), averagePrice: new Big('719.46'),
@ -693,6 +699,9 @@ describe('PortfolioCalculator', () => {
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
hasErrors: false, hasErrors: false,
currentValue: new Big('657.62'),
grossPerformance: new Big('-9.04'),
grossPerformancePercentage: new Big('-0.01206012060120601206'),
positions: [ positions: [
{ {
averagePrice: new Big('719.46'), averagePrice: new Big('719.46'),
@ -728,6 +737,9 @@ describe('PortfolioCalculator', () => {
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
hasErrors: false, hasErrors: false,
currentValue: new Big('4871.5'),
grossPerformance: new Big('240.4'),
grossPerformancePercentage: new Big('0.08908669575467971768'),
positions: [ positions: [
{ {
averagePrice: new Big('178.438'), averagePrice: new Big('178.438'),
@ -805,6 +817,9 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
hasErrors: false, hasErrors: false,
currentValue: new Big('3897.2'),
grossPerformance: new Big('303.2'),
grossPerformancePercentage: new Big('0.2759628350186678759'),
positions: [ positions: [
{ {
averagePrice: new Big('146.185'), averagePrice: new Big('146.185'),
@ -875,6 +890,9 @@ describe('PortfolioCalculator', () => {
expect(currentPositions).toEqual({ expect(currentPositions).toEqual({
hasErrors: false, hasErrors: false,
currentValue: new Big('1192327.999656600298238721'),
grossPerformance: new Big('92327.999656600898394721'),
grossPerformancePercentage: new Big('0.09788598099999947809'),
positions: [ positions: [
{ {
averagePrice: new Big('1.01287018290924923237'), // 1'100'000 / 1'086'022.689344542 averagePrice: new Big('1.01287018290924923237'), // 1'100'000 / 1'086'022.689344542

56
apps/api/src/app/core/portfolio-calculator.ts

@ -114,11 +114,17 @@ export class PortfolioCalculator {
public async getCurrentPositions(start: Date): Promise<{ public async getCurrentPositions(start: Date): Promise<{
hasErrors: boolean; hasErrors: boolean;
positions: TimelinePosition[]; positions: TimelinePosition[];
grossPerformance: Big;
grossPerformancePercentage: Big;
currentValue: Big;
}> { }> {
if (!this.transactionPoints?.length) { if (!this.transactionPoints?.length) {
return { return {
hasErrors: false, hasErrors: false,
positions: [] positions: [],
grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0),
currentValue: new Big(0)
}; };
} }
@ -197,6 +203,8 @@ export class PortfolioCalculator {
const invalidSymbols = []; const invalidSymbols = [];
const lastInvestments: { [symbol: string]: Big } = {}; const lastInvestments: { [symbol: string]: Big } = {};
const lastQuantities: { [symbol: string]: Big } = {}; const lastQuantities: { [symbol: string]: Big } = {};
const initialValues: { [symbol: string]: Big } = {};
for (let i = firstIndex; i < this.transactionPoints.length; i++) { for (let i = firstIndex; i < this.transactionPoints.length; i++) {
const currentDate = const currentDate =
i === firstIndex ? startString : this.transactionPoints[i].date; i === firstIndex ? startString : this.transactionPoints[i].date;
@ -233,6 +241,9 @@ export class PortfolioCalculator {
initialValue = item.investment; initialValue = item.investment;
investedValue = item.investment; investedValue = item.investment;
} }
if (i === firstIndex || !initialValues[item.symbol]) {
initialValues[item.symbol] = initialValue;
}
if (!initialValue) { if (!initialValue) {
invalidSymbols.push(item.symbol); invalidSymbols.push(item.symbol);
hasErrors = true; hasErrors = true;
@ -287,7 +298,48 @@ export class PortfolioCalculator {
}); });
} }
return { hasErrors, positions }; let currentValue = new Big(0);
let overallGrossPerformance = new Big(0);
let grossPerformancePercentage = new Big(1);
let completeInitialValue = new Big(0);
for (const currentPosition of positions) {
currentValue = currentValue.add(
new Big(currentPosition.marketPrice).mul(currentPosition.quantity)
);
if (currentPosition.grossPerformance) {
overallGrossPerformance = overallGrossPerformance.plus(
currentPosition.grossPerformance
);
} else {
hasErrors = true;
}
if (
currentPosition.grossPerformancePercentage &&
initialValues[currentPosition.symbol]
) {
const currentInitialValue = initialValues[currentPosition.symbol];
completeInitialValue = completeInitialValue.plus(currentInitialValue);
grossPerformancePercentage = grossPerformancePercentage.plus(
currentPosition.grossPerformancePercentage.mul(currentInitialValue)
);
} else {
console.log(initialValues);
console.error(
'initial value is missing for symbol',
currentPosition.symbol
);
hasErrors = true;
}
}
return {
hasErrors,
positions,
grossPerformance: overallGrossPerformance,
grossPerformancePercentage:
grossPerformancePercentage.div(completeInitialValue),
currentValue
};
} }
public async calculateTimeline( public async calculateTimeline(

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

@ -484,34 +484,12 @@ export class PortfolioService {
startDate startDate
); );
let currentValue = new Big(0); const hasErrors = currentPositions.hasErrors;
let grossPerformance = new Big(0); const currentValue = currentPositions.currentValue.toNumber();
let grossPerformancePercentage = new Big(1); const currentGrossPerformance =
let hasErrors = false; currentPositions.grossPerformance.toNumber();
for (const currentPosition of currentPositions.positions) { const currentGrossPerformancePercent =
currentValue = currentValue.add( currentPositions.grossPerformancePercentage.toNumber();
new Big(currentPosition.marketPrice).mul(currentPosition.quantity)
);
if (currentPosition.grossPerformance) {
grossPerformance = grossPerformance.plus(
currentPosition.grossPerformance
);
} else {
hasErrors = true;
}
if (currentPosition.grossPerformancePercentage) {
grossPerformancePercentage = grossPerformancePercentage.mul(
currentPosition.grossPerformancePercentage.plus(1)
);
} else {
hasErrors = true;
}
}
const currentGrossPerformance = grossPerformance.toNumber();
const currentGrossPerformancePercent = grossPerformancePercentage
.minus(1)
.toNumber();
return { return {
hasErrors: currentPositions.hasErrors || hasErrors, hasErrors: currentPositions.hasErrors || hasErrors,
performance: { performance: {
@ -520,7 +498,7 @@ export class PortfolioService {
// TODO: the next two should include fees // TODO: the next two should include fees
currentNetPerformance: currentGrossPerformance, currentNetPerformance: currentGrossPerformance,
currentNetPerformancePercent: currentGrossPerformancePercent, currentNetPerformancePercent: currentGrossPerformancePercent,
currentValue: currentValue.toNumber() currentValue: currentValue
} }
}; };
} }

Loading…
Cancel
Save