diff --git a/CHANGELOG.md b/CHANGELOG.md index 8070c4526..57b344467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Migrated the search functionality to `yahoo-finance2` +### Fixed + +- Fixed an issue in the average price / investment calculation for sell activities + ## 1.136.0 - 13.04.2022 ### Changed diff --git a/apps/api/src/app/portfolio/current-rate.service.mock.ts b/apps/api/src/app/portfolio/current-rate.service.mock.ts index 124a27f45..c91ed9d9a 100644 --- a/apps/api/src/app/portfolio/current-rate.service.mock.ts +++ b/apps/api/src/app/portfolio/current-rate.service.mock.ts @@ -20,6 +20,13 @@ function mockGetValue(symbol: string, date: Date) { return { marketPrice: 0 }; + case 'NOVN.SW': + if (isSameDay(parseDate('2022-04-11'), date)) { + return { marketPrice: 87.8 }; + } + + return { marketPrice: 0 }; + default: return { marketPrice: 0 }; } diff --git a/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts new file mode 100644 index 000000000..d215f9e1e --- /dev/null +++ b/apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts @@ -0,0 +1,96 @@ +import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; +import { parseDate } from '@ghostfolio/common/helper'; +import Big from 'big.js'; + +import { CurrentRateServiceMock } from './current-rate.service.mock'; +import { PortfolioCalculator } from './portfolio-calculator'; + +jest.mock('@ghostfolio/api/app/portfolio/current-rate.service', () => { + return { + // eslint-disable-next-line @typescript-eslint/naming-convention + CurrentRateService: jest.fn().mockImplementation(() => { + return CurrentRateServiceMock; + }) + }; +}); + +describe('PortfolioCalculator', () => { + let currentRateService: CurrentRateService; + + beforeEach(() => { + currentRateService = new CurrentRateService(null, null, null); + }); + + describe('get current positions', () => { + it.only('with BALN.SW buy and sell', async () => { + const portfolioCalculator = new PortfolioCalculator({ + currentRateService, + currency: 'CHF', + orders: [ + { + currency: 'CHF', + date: '2022-03-07', + dataSource: 'YAHOO', + fee: new Big(1.3), + name: 'Novartis AG', + quantity: new Big(2), + symbol: 'NOVN.SW', + type: 'BUY', + unitPrice: new Big(75.8) + }, + { + currency: 'CHF', + date: '2022-04-08', + dataSource: 'YAHOO', + fee: new Big(2.95), + name: 'Novartis AG', + quantity: new Big(1), + symbol: 'NOVN.SW', + type: 'SELL', + unitPrice: new Big(85.73) + } + ] + }); + + portfolioCalculator.computeTransactionPoints(); + + const spy = jest + .spyOn(Date, 'now') + .mockImplementation(() => parseDate('2022-04-11').getTime()); + + const currentPositions = await portfolioCalculator.getCurrentPositions( + parseDate('2022-03-07') + ); + + spy.mockRestore(); + + expect(currentPositions).toEqual({ + currentValue: new Big('87.8'), + errors: [], + grossPerformance: new Big('21.93'), + grossPerformancePercentage: new Big('0.14465699208443271768'), + hasErrors: false, + netPerformance: new Big('17.68'), + netPerformancePercentage: new Big('0.11662269129287598945'), + positions: [ + { + averagePrice: new Big('75.80'), + currency: 'CHF', + dataSource: 'YAHOO', + firstBuyDate: '2022-03-07', + grossPerformance: new Big('21.93'), + grossPerformancePercentage: new Big('0.14465699208443271768'), + investment: new Big('75.80'), + netPerformance: new Big('17.68'), + netPerformancePercentage: new Big('0.11662269129287598945'), + marketPrice: 87.8, + quantity: new Big('1'), + symbol: 'NOVN.SW', + transactionCount: 2 + } + ], + totalInvestment: new Big('75.80') + }); + }); + }); +}); diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index 20b0a8709..4f8631a3f 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -77,17 +77,30 @@ export class PortfolioCalculator { const newQuantity = order.quantity .mul(factor) .plus(oldAccumulatedSymbol.quantity); + + let investment = new Big(0); + + if (newQuantity.gt(0)) { + if (order.type === 'BUY') { + investment = oldAccumulatedSymbol.investment.plus( + order.quantity.mul(unitPrice) + ); + } else if (order.type === 'SELL') { + const averagePrice = oldAccumulatedSymbol.investment.div( + oldAccumulatedSymbol.quantity + ); + investment = oldAccumulatedSymbol.investment.minus( + order.quantity.mul(averagePrice) + ); + } + } + currentTransactionPointItem = { + investment, currency: order.currency, dataSource: order.dataSource, fee: order.fee.plus(oldAccumulatedSymbol.fee), firstBuyDate: oldAccumulatedSymbol.firstBuyDate, - investment: newQuantity.eq(0) - ? new Big(0) - : unitPrice - .mul(order.quantity) - .mul(factor) - .plus(oldAccumulatedSymbol.investment), quantity: newQuantity, symbol: order.symbol, transactionCount: oldAccumulatedSymbol.transactionCount + 1 diff --git a/libs/ui/src/lib/activities-table/activities-table.component.html b/libs/ui/src/lib/activities-table/activities-table.component.html index 7cbfffe05..a7449a0be 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.html +++ b/libs/ui/src/lib/activities-table/activities-table.component.html @@ -271,6 +271,7 @@