Browse Source

unit tests extended

pull/6468/head
Attila Cseh 2 weeks ago
parent
commit
81a79ab482
  1. 49
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  2. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts
  3. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  4. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
  5. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
  6. 24
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  7. 41
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  8. 24
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  9. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
  10. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts
  11. 9
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts
  12. 17
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  13. 12
      apps/api/src/app/portfolio/portfolio.service.ts

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

@ -18,12 +18,7 @@ import {
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH, PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_HIGH,
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper';
DATE_FORMAT,
getSum,
parseDate,
resetHours
} from '@ghostfolio/common/helper';
import { import {
Activity, Activity,
AssetProfileIdentifier, AssetProfileIdentifier,
@ -51,14 +46,13 @@ import {
format, format,
isAfter, isAfter,
isBefore, isBefore,
isSameYear,
isWithinInterval, isWithinInterval,
min, min,
startOfDay, startOfDay,
startOfYear, startOfYear,
subDays subDays
} from 'date-fns'; } from 'date-fns';
import { isNumber, sortBy, sum, uniqBy } from 'lodash'; import { groupBy as ldGroupBy, isNumber, sortBy, sum, uniqBy } from 'lodash';
export abstract class PortfolioCalculator { export abstract class PortfolioCalculator {
protected static readonly ENABLE_LOGGING = false; protected static readonly ENABLE_LOGGING = false;
@ -717,21 +711,14 @@ export abstract class PortfolioCalculator {
return this.snapshot.totalLiabilitiesWithCurrencyEffect; return this.snapshot.totalLiabilitiesWithCurrencyEffect;
} }
public async getPerformance({ end, start }) { public async getPerformance({ data }: { data: HistoricalDataItem[] }) {
await this.snapshotPromise;
const { historicalData } = this.snapshot;
const chart: HistoricalDataItem[] = []; const chart: HistoricalDataItem[] = [];
let netPerformanceAtStartDate: number; let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number; let netPerformanceWithCurrencyEffectAtStartDate: number;
const totalInvestmentValuesWithCurrencyEffect: number[] = []; const totalInvestmentValuesWithCurrencyEffect: number[] = [];
for (const historicalDataItem of historicalData) { for (const historicalDataItem of data) {
const date = resetHours(parseDate(historicalDataItem.date));
if (!isBefore(date, start) && !isAfter(date, end)) {
if (!isNumber(netPerformanceAtStartDate)) { if (!isNumber(netPerformanceAtStartDate)) {
netPerformanceAtStartDate = historicalDataItem.netPerformance; netPerformanceAtStartDate = historicalDataItem.netPerformance;
@ -780,42 +767,30 @@ export abstract class PortfolioCalculator {
// netWorth: 0 // netWorth: 0
}); });
} }
}
return { chart }; return { chart };
} }
public async getPerformanceByGroup({ public async getPerformanceByGroup({
endDate, data,
groupBy, groupBy
startDate
}: { }: {
endDate: Date; data: HistoricalDataItem[];
groupBy: GroupBy; groupBy: Extract<GroupBy, 'year'>;
startDate: Date;
}) { }) {
const interval = { start: startDate, end: endDate };
const chart: HistoricalDataItem[] = []; const chart: HistoricalDataItem[] = [];
if (groupBy === 'year') { if (groupBy === 'year') {
for (const year of eachYearOfInterval(interval)) { const dataByYear = ldGroupBy(data, (item) => item.date.slice(0, 4));
const yearStartDate = startOfYear(year);
const yearEndDate = endOfYear(year);
const yearIntervalStartDate = isSameYear(startDate, yearStartDate)
? startDate
: yearStartDate;
const yearIntervalEndDate = isSameYear(endDate, yearEndDate)
? endDate
: yearEndDate;
for (const year of Object.keys(dataByYear)) {
const { chart: yearChart } = await this.getPerformance({ const { chart: yearChart } = await this.getPerformance({
end: yearIntervalEndDate, data: Object.values(dataByYear[year])
start: yearIntervalStartDate
}); });
const yearPerformanceItem = { const yearPerformanceItem = {
...(yearChart.at(-1) ?? ({} as HistoricalDataItem)), ...(yearChart.at(-1) ?? ({} as HistoricalDataItem)),
date: format(yearStartDate, DATE_FORMAT) date: format(startOfYear(year), DATE_FORMAT)
}; };
chart.push(yearPerformanceItem); chart.push(yearPerformanceItem);

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

@ -138,6 +138,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('595.6'), currentValueInBaseCurrency: new Big('595.6'),
errors: [], errors: [],
@ -212,6 +219,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2021-01-01', investment: 559 } { date: '2021-01-01', investment: 559 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2021-01-01',
netPerformance: 33.4,
netPerformanceInPercentage: 0.06986689805847808,
netPerformanceInPercentageWithCurrencyEffect: 0.06986689805847808,
netPerformanceWithCurrencyEffect: 33.4
})
]);
}); });
}); });
}); });

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

@ -154,6 +154,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
@ -226,6 +233,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2021-01-01', investment: 0 } { date: '2021-01-01', investment: 0 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2021-01-01',
netPerformance: -15.8,
netPerformanceInPercentage: -0.05528341497550735,
netPerformanceInPercentageWithCurrencyEffect: -0.05528341497550735,
netPerformanceWithCurrencyEffect: -15.8
})
]);
}); });
}); });
}); });

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

@ -138,6 +138,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
@ -210,6 +217,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2021-01-01', investment: 0 } { date: '2021-01-01', investment: 0 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2021-01-01',
netPerformance: -15.8,
netPerformanceInPercentage: -0.05528341497550735,
netPerformanceInPercentageWithCurrencyEffect: -0.05528341497550735,
netPerformanceWithCurrencyEffect: -15.8
})
]);
}); });
}); });
}); });

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

@ -128,6 +128,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('297.8'), currentValueInBaseCurrency: new Big('297.8'),
errors: [], errors: [],
@ -209,6 +216,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2021-01-01', investment: 273.2 } { date: '2021-01-01', investment: 273.2 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2021-01-01',
netPerformance: 23.05,
netPerformanceInPercentage: 0.08437042459736459,
netPerformanceInPercentageWithCurrencyEffect: 0.08437042459736459,
netPerformanceWithCurrencyEffect: 23.05
})
]);
}); });
it.only('with BALN.SW buy (with unit price lower than closing price)', async () => { it.only('with BALN.SW buy (with unit price lower than closing price)', async () => {

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

@ -137,6 +137,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot.historicalData[0]).toEqual({ expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11', date: '2021-12-11',
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
@ -255,6 +262,23 @@ describe('PortfolioCalculator', () => {
{ date: '2021-01-01', investment: 44558.42 }, { date: '2021-01-01', investment: 44558.42 },
{ date: '2022-01-01', investment: 0 } { date: '2022-01-01', investment: 0 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2021-01-01',
netPerformance: -1463.18,
netPerformanceInPercentage: -0.03283734028271199,
netPerformanceInPercentageWithCurrencyEffect: -0.03283734028271199,
netPerformanceWithCurrencyEffect: -1463.18
}),
expect.objectContaining({
date: '2022-01-01',
netPerformance: 0,
netPerformanceInPercentage: 0,
netPerformanceInPercentageWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0
})
]);
}); });
}); });
}); });

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

@ -151,6 +151,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('13298.425356'), currentValueInBaseCurrency: new Big('13298.425356'),
errors: [], errors: [],
@ -266,6 +273,40 @@ describe('PortfolioCalculator', () => {
{ date: '2017-01-01', investment: -318.54266729999995 }, { date: '2017-01-01', investment: -318.54266729999995 },
{ date: '2018-01-01', investment: 0 } { date: '2018-01-01', investment: 0 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2014-01-01',
netPerformance: 0,
netPerformanceInPercentage: 0,
netPerformanceInPercentageWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0
}),
expect.objectContaining({
date: '2015-01-01',
netPerformance: 25984.861407,
netPerformanceInPercentage: 40.787097105782344,
netPerformanceInPercentageWithCurrencyEffect: 41.893291864515064,
netPerformanceWithCurrencyEffect: 26689.601865
}),
expect.objectContaining({
date: '2016-01-01',
netPerformance: 0,
netPerformanceInPercentage: 0,
netPerformanceInPercentageWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0
}),
expect.objectContaining({
date: '2017-01-01',
netPerformance: 972.1720319999986,
netPerformanceInPercentage: 1.5306926710921496,
netPerformanceInPercentageWithCurrencyEffect: 0.6224618479468005,
netPerformanceWithCurrencyEffect: 395.3373599999977
}),
expect.objectContaining({
date: '2018-01-01'
})
]);
}); });
}); });
}); });

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

@ -137,6 +137,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot.historicalData[0]).toEqual({ expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11', date: '2021-12-11',
investmentValueWithCurrencyEffect: 0, investmentValueWithCurrencyEffect: 0,
@ -255,6 +262,23 @@ describe('PortfolioCalculator', () => {
{ date: '2021-01-01', investment: 44558.42 }, { date: '2021-01-01', investment: 44558.42 },
{ date: '2022-01-01', investment: 0 } { date: '2022-01-01', investment: 0 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2021-01-01',
netPerformance: -1463.18,
netPerformanceInPercentage: -0.03283734028271199,
netPerformanceInPercentageWithCurrencyEffect: -0.03283734028271199,
netPerformanceWithCurrencyEffect: -1463.18
}),
expect.objectContaining({
date: '2022-01-01',
netPerformance: 0,
netPerformanceInPercentage: 0,
netPerformanceInPercentageWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0
})
]);
}); });
}); });
}); });

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

@ -134,6 +134,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('103.10483'), currentValueInBaseCurrency: new Big('103.10483'),
errors: [], errors: [],
@ -228,6 +235,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2023-01-01', investment: 82.329056 } { date: '2023-01-01', investment: 82.329056 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2023-01-01',
netPerformance: 23.312582,
netPerformanceInPercentage: 0.2831634799747973,
netPerformanceInPercentageWithCurrencyEffect: 0.2411296201428566,
netPerformanceWithCurrencyEffect: 19.851974
})
]);
}); });
}); });
}); });

17
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts

@ -134,6 +134,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
@ -185,6 +192,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2025-01-01', investment: 0 } { date: '2025-01-01', investment: 0 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2025-01-01',
netPerformance: 39.95,
netPerformanceInPercentage: 0.020208978362720148,
netPerformanceInPercentageWithCurrencyEffect: 0.020208978362720148,
netPerformanceWithCurrencyEffect: 39.95
})
]);
}); });
}); });
}); });

9
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-orders.spec.ts

@ -98,6 +98,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big(0), currentValueInBaseCurrency: new Big(0),
hasErrors: false, hasErrors: false,
@ -115,6 +122,8 @@ describe('PortfolioCalculator', () => {
expect(investmentsByMonth).toEqual([]); expect(investmentsByMonth).toEqual([]);
expect(investmentsByYear).toEqual([]); expect(investmentsByYear).toEqual([]);
expect(performanceByYear.chart).toEqual([]);
}); });
}); });
}); });

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

@ -134,6 +134,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year' groupBy: 'year'
}); });
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('87.8'), currentValueInBaseCurrency: new Big('87.8'),
errors: [], errors: [],
@ -208,6 +215,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([ expect(investmentsByYear).toEqual([
{ date: '2022-01-01', investment: 75.8 } { date: '2022-01-01', investment: 75.8 }
]); ]);
expect(performanceByYear.chart).toEqual([
expect.objectContaining({
date: '2022-01-01',
netPerformance: 17.68,
netPerformanceInPercentage: 0.12348284960422161,
netPerformanceInPercentageWithCurrencyEffect: 0.12348284960422161,
netPerformanceWithCurrencyEffect: 17.68
})
]);
}); });
}); });
}); });

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

@ -1006,6 +1006,8 @@ export class PortfolioService {
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);
const { endDate, startDate } = getIntervalFromDateRange(dateRange);
const [accountBalanceItems, { activities }] = await Promise.all([ const [accountBalanceItems, { activities }] = await Promise.all([
this.accountBalanceService.getAccountBalanceItems({ this.accountBalanceService.getAccountBalanceItems({
filters, filters,
@ -1049,11 +1051,12 @@ export class PortfolioService {
const { errors, hasErrors, historicalData } = const { errors, hasErrors, historicalData } =
await portfolioCalculator.getSnapshot(); await portfolioCalculator.getSnapshot();
const { endDate, startDate } = getIntervalFromDateRange(dateRange); const items = historicalData.filter(({ date }) => {
return !isBefore(date, startDate) && !isAfter(date, endDate);
});
const { chart: intervalChart } = await portfolioCalculator.getPerformance({ const { chart: intervalChart } = await portfolioCalculator.getPerformance({
end: endDate, data: items
start: startDate
}); });
let chart = intervalChart; let chart = intervalChart;
@ -1061,8 +1064,7 @@ export class PortfolioService {
if (groupBy) { if (groupBy) {
const { chart: groupedChart } = const { chart: groupedChart } =
await portfolioCalculator.getPerformanceByGroup({ await portfolioCalculator.getPerformanceByGroup({
startDate, data: items,
endDate,
groupBy groupBy
}); });

Loading…
Cancel
Save