Browse Source

unit tests extended

pull/6468/head
Attila Cseh 2 weeks ago
parent
commit
81a79ab482
  1. 131
      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

131
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_LOW
} from '@ghostfolio/common/config';
import {
DATE_FORMAT,
getSum,
parseDate,
resetHours
} from '@ghostfolio/common/helper';
import { DATE_FORMAT, getSum, parseDate } from '@ghostfolio/common/helper';
import {
Activity,
AssetProfileIdentifier,
@ -51,14 +46,13 @@ import {
format,
isAfter,
isBefore,
isSameYear,
isWithinInterval,
min,
startOfDay,
startOfYear,
subDays
} from 'date-fns';
import { isNumber, sortBy, sum, uniqBy } from 'lodash';
import { groupBy as ldGroupBy, isNumber, sortBy, sum, uniqBy } from 'lodash';
export abstract class PortfolioCalculator {
protected static readonly ENABLE_LOGGING = false;
@ -717,105 +711,86 @@ export abstract class PortfolioCalculator {
return this.snapshot.totalLiabilitiesWithCurrencyEffect;
}
public async getPerformance({ end, start }) {
await this.snapshotPromise;
const { historicalData } = this.snapshot;
public async getPerformance({ data }: { data: HistoricalDataItem[] }) {
const chart: HistoricalDataItem[] = [];
let netPerformanceAtStartDate: number;
let netPerformanceWithCurrencyEffectAtStartDate: number;
const totalInvestmentValuesWithCurrencyEffect: number[] = [];
for (const historicalDataItem of historicalData) {
const date = resetHours(parseDate(historicalDataItem.date));
for (const historicalDataItem of data) {
if (!isNumber(netPerformanceAtStartDate)) {
netPerformanceAtStartDate = historicalDataItem.netPerformance;
if (!isBefore(date, start) && !isAfter(date, end)) {
if (!isNumber(netPerformanceAtStartDate)) {
netPerformanceAtStartDate = historicalDataItem.netPerformance;
netPerformanceWithCurrencyEffectAtStartDate =
historicalDataItem.netPerformanceWithCurrencyEffect;
}
netPerformanceWithCurrencyEffectAtStartDate =
historicalDataItem.netPerformanceWithCurrencyEffect;
}
const netPerformanceSinceStartDate =
historicalDataItem.netPerformance - netPerformanceAtStartDate;
const netPerformanceSinceStartDate =
historicalDataItem.netPerformance - netPerformanceAtStartDate;
const netPerformanceWithCurrencyEffectSinceStartDate =
historicalDataItem.netPerformanceWithCurrencyEffect -
netPerformanceWithCurrencyEffectAtStartDate;
const netPerformanceWithCurrencyEffectSinceStartDate =
historicalDataItem.netPerformanceWithCurrencyEffect -
netPerformanceWithCurrencyEffectAtStartDate;
if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) {
totalInvestmentValuesWithCurrencyEffect.push(
historicalDataItem.totalInvestmentValueWithCurrencyEffect
);
}
if (historicalDataItem.totalInvestmentValueWithCurrencyEffect > 0) {
totalInvestmentValuesWithCurrencyEffect.push(
historicalDataItem.totalInvestmentValueWithCurrencyEffect
);
}
const timeWeightedInvestmentValue =
totalInvestmentValuesWithCurrencyEffect.length > 0
? sum(totalInvestmentValuesWithCurrencyEffect) /
totalInvestmentValuesWithCurrencyEffect.length
: 0;
const timeWeightedInvestmentValue =
totalInvestmentValuesWithCurrencyEffect.length > 0
? sum(totalInvestmentValuesWithCurrencyEffect) /
totalInvestmentValuesWithCurrencyEffect.length
: 0;
chart.push({
...historicalDataItem,
netPerformance:
historicalDataItem.netPerformance - netPerformanceAtStartDate,
netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffectSinceStartDate,
netPerformanceInPercentage:
timeWeightedInvestmentValue === 0
? 0
: netPerformanceSinceStartDate / timeWeightedInvestmentValue,
netPerformanceInPercentageWithCurrencyEffect:
timeWeightedInvestmentValue === 0
? 0
: netPerformanceWithCurrencyEffectSinceStartDate /
timeWeightedInvestmentValue
// TODO: Add net worth
// netWorth: totalCurrentValueWithCurrencyEffect
// .plus(totalAccountBalanceWithCurrencyEffect)
// .toNumber()
// netWorth: 0
});
}
chart.push({
...historicalDataItem,
netPerformance:
historicalDataItem.netPerformance - netPerformanceAtStartDate,
netPerformanceWithCurrencyEffect:
netPerformanceWithCurrencyEffectSinceStartDate,
netPerformanceInPercentage:
timeWeightedInvestmentValue === 0
? 0
: netPerformanceSinceStartDate / timeWeightedInvestmentValue,
netPerformanceInPercentageWithCurrencyEffect:
timeWeightedInvestmentValue === 0
? 0
: netPerformanceWithCurrencyEffectSinceStartDate /
timeWeightedInvestmentValue
// TODO: Add net worth
// netWorth: totalCurrentValueWithCurrencyEffect
// .plus(totalAccountBalanceWithCurrencyEffect)
// .toNumber()
// netWorth: 0
});
}
return { chart };
}
public async getPerformanceByGroup({
endDate,
groupBy,
startDate
data,
groupBy
}: {
endDate: Date;
groupBy: GroupBy;
startDate: Date;
data: HistoricalDataItem[];
groupBy: Extract<GroupBy, 'year'>;
}) {
const interval = { start: startDate, end: endDate };
const chart: HistoricalDataItem[] = [];
if (groupBy === 'year') {
for (const year of eachYearOfInterval(interval)) {
const yearStartDate = startOfYear(year);
const yearEndDate = endOfYear(year);
const yearIntervalStartDate = isSameYear(startDate, yearStartDate)
? startDate
: yearStartDate;
const yearIntervalEndDate = isSameYear(endDate, yearEndDate)
? endDate
: yearEndDate;
const dataByYear = ldGroupBy(data, (item) => item.date.slice(0, 4));
for (const year of Object.keys(dataByYear)) {
const { chart: yearChart } = await this.getPerformance({
end: yearIntervalEndDate,
start: yearIntervalStartDate
data: Object.values(dataByYear[year])
});
const yearPerformanceItem = {
...(yearChart.at(-1) ?? ({} as HistoricalDataItem)),
date: format(yearStartDate, DATE_FORMAT)
date: format(startOfYear(year), DATE_FORMAT)
};
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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('595.6'),
errors: [],
@ -212,6 +219,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'),
errors: [],
@ -226,6 +233,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'),
errors: [],
@ -210,6 +217,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('297.8'),
errors: [],
@ -209,6 +216,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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 () => {

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

@ -137,6 +137,13 @@ describe('PortfolioCalculator', () => {
groupBy: 'year'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11',
investmentValueWithCurrencyEffect: 0,
@ -255,6 +262,23 @@ describe('PortfolioCalculator', () => {
{ date: '2021-01-01', investment: 44558.42 },
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('13298.425356'),
errors: [],
@ -266,6 +273,40 @@ describe('PortfolioCalculator', () => {
{ date: '2017-01-01', investment: -318.54266729999995 },
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot.historicalData[0]).toEqual({
date: '2021-12-11',
investmentValueWithCurrencyEffect: 0,
@ -255,6 +262,23 @@ describe('PortfolioCalculator', () => {
{ date: '2021-01-01', investment: 44558.42 },
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('103.10483'),
errors: [],
@ -228,6 +235,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('0'),
errors: [],
@ -185,6 +192,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big(0),
hasErrors: false,
@ -115,6 +122,8 @@ describe('PortfolioCalculator', () => {
expect(investmentsByMonth).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'
});
const performanceByYear = await portfolioCalculator.getPerformanceByGroup(
{
data: portfolioSnapshot.historicalData,
groupBy: 'year'
}
);
expect(portfolioSnapshot).toMatchObject({
currentValueInBaseCurrency: new Big('87.8'),
errors: [],
@ -208,6 +215,16 @@ describe('PortfolioCalculator', () => {
expect(investmentsByYear).toEqual([
{ 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 userCurrency = this.getUserCurrency(user);
const { endDate, startDate } = getIntervalFromDateRange(dateRange);
const [accountBalanceItems, { activities }] = await Promise.all([
this.accountBalanceService.getAccountBalanceItems({
filters,
@ -1049,11 +1051,12 @@ export class PortfolioService {
const { errors, hasErrors, historicalData } =
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({
end: endDate,
start: startDate
data: items
});
let chart = intervalChart;
@ -1061,8 +1064,7 @@ export class PortfolioService {
if (groupBy) {
const { chart: groupedChart } =
await portfolioCalculator.getPerformanceByGroup({
startDate,
endDate,
data: items,
groupBy
});

Loading…
Cancel
Save