Browse Source

Move dividend calculation to portfolio calculator

pull/3267/head
Thomas Kaul 1 year ago
parent
commit
cd3f9c0bef
  1. 4
      apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts
  2. 7
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  3. 538
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  4. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  5. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts
  6. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts
  7. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  8. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts
  9. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts
  10. 14
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts
  11. 11
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  12. 12
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts
  13. 4
      apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts
  14. 2
      apps/api/src/app/portfolio/interfaces/portfolio-snapshot.interface.ts
  15. 3
      apps/api/src/app/portfolio/portfolio.controller.ts
  16. 47
      apps/api/src/app/portfolio/portfolio.service.ts
  17. 26
      apps/api/src/helper/portfolio.helper.ts

4
apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts

@ -1,5 +1,5 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface'; import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
import { import {
SymbolMetrics, SymbolMetrics,
TimelinePosition, TimelinePosition,
@ -9,7 +9,7 @@ import {
export class MWRPortfolioCalculator extends PortfolioCalculator { export class MWRPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance( protected calculateOverallPerformance(
positions: TimelinePosition[] positions: TimelinePosition[]
): CurrentPositions { ): PortfolioSnapshot {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }

7
apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts

@ -1,6 +1,7 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DateRange } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
@ -23,17 +24,20 @@ export class PortfolioCalculatorFactory {
public createCalculator({ public createCalculator({
activities, activities,
calculationType, calculationType,
currency currency,
dateRange = 'max'
}: { }: {
activities: Activity[]; activities: Activity[];
calculationType: PerformanceCalculationType; calculationType: PerformanceCalculationType;
currency: string; currency: string;
dateRange?: DateRange;
}): PortfolioCalculator { }): PortfolioCalculator {
switch (calculationType) { switch (calculationType) {
case PerformanceCalculationType.MWR: case PerformanceCalculationType.MWR:
return new MWRPortfolioCalculator({ return new MWRPortfolioCalculator({
activities, activities,
currency, currency,
dateRange,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
exchangeRateDataService: this.exchangeRateDataService exchangeRateDataService: this.exchangeRateDataService
}); });
@ -42,6 +46,7 @@ export class PortfolioCalculatorFactory {
activities, activities,
currency, currency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
dateRange,
exchangeRateDataService: this.exchangeRateDataService exchangeRateDataService: this.exchangeRateDataService
}); });
default: default:

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

@ -1,7 +1,7 @@
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface';
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface'; import { TransactionPointSymbol } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point-symbol.interface';
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
import { import {
@ -11,7 +11,12 @@ import {
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { MAX_CHART_ITEMS } from '@ghostfolio/common/config'; import { MAX_CHART_ITEMS } from '@ghostfolio/common/config';
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper'; import {
DATE_FORMAT,
getSum,
parseDate,
resetHours
} from '@ghostfolio/common/helper';
import { import {
DataProviderInfo, DataProviderInfo,
HistoricalDataItem, HistoricalDataItem,
@ -44,18 +49,24 @@ export abstract class PortfolioCalculator {
private currency: string; private currency: string;
private currentRateService: CurrentRateService; private currentRateService: CurrentRateService;
private dataProviderInfos: DataProviderInfo[]; private dataProviderInfos: DataProviderInfo[];
private endDate: Date;
private exchangeRateDataService: ExchangeRateDataService; private exchangeRateDataService: ExchangeRateDataService;
private snapshot: PortfolioSnapshot;
private snapshotPromise: Promise<void>;
private startDate: Date;
private transactionPoints: TransactionPoint[]; private transactionPoints: TransactionPoint[];
public constructor({ public constructor({
activities, activities,
currency, currency,
currentRateService, currentRateService,
dateRange,
exchangeRateDataService exchangeRateDataService
}: { }: {
activities: Activity[]; activities: Activity[];
currency: string; currency: string;
currentRateService: CurrentRateService; currentRateService: CurrentRateService;
dateRange: DateRange;
exchangeRateDataService: ExchangeRateDataService; exchangeRateDataService: ExchangeRateDataService;
}) { }) {
this.currency = currency; this.currency = currency;
@ -79,12 +90,267 @@ export abstract class PortfolioCalculator {
return a.date?.localeCompare(b.date); return a.date?.localeCompare(b.date);
}); });
const { endDate, startDate } = getInterval(dateRange);
this.endDate = endDate;
this.startDate = startDate;
this.computeTransactionPoints(); this.computeTransactionPoints();
this.snapshotPromise = this.initialize();
} }
protected abstract calculateOverallPerformance( protected abstract calculateOverallPerformance(
positions: TimelinePosition[] positions: TimelinePosition[]
): CurrentPositions; ): PortfolioSnapshot;
public async computeSnapshot(
start: Date,
end?: Date
): Promise<PortfolioSnapshot> {
const lastTransactionPoint = last(this.transactionPoints);
let endDate = end;
if (!endDate) {
endDate = new Date(Date.now());
if (lastTransactionPoint) {
endDate = max([endDate, parseDate(lastTransactionPoint.date)]);
}
}
const transactionPoints = this.transactionPoints?.filter(({ date }) => {
return isBefore(parseDate(date), endDate);
});
if (!transactionPoints.length) {
return {
currentValueInBaseCurrency: new Big(0),
grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: new Big(0),
grossPerformanceWithCurrencyEffect: new Big(0),
hasErrors: false,
netPerformance: new Big(0),
netPerformancePercentage: new Big(0),
netPerformancePercentageWithCurrencyEffect: new Big(0),
netPerformanceWithCurrencyEffect: new Big(0),
positions: [],
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0)
};
}
const currencies: { [symbol: string]: string } = {};
const dataGatheringItems: IDataGatheringItem[] = [];
let dates: Date[] = [];
let firstIndex = transactionPoints.length;
let firstTransactionPoint: TransactionPoint = null;
dates.push(resetHours(start));
for (const { currency, dataSource, symbol } of transactionPoints[
firstIndex - 1
].items) {
dataGatheringItems.push({
dataSource,
symbol
});
currencies[symbol] = currency;
}
for (let i = 0; i < transactionPoints.length; i++) {
if (
!isBefore(parseDate(transactionPoints[i].date), start) &&
firstTransactionPoint === null
) {
firstTransactionPoint = transactionPoints[i];
firstIndex = i;
}
if (firstTransactionPoint !== null) {
dates.push(resetHours(parseDate(transactionPoints[i].date)));
}
}
dates.push(resetHours(endDate));
// Add dates of last week for fallback
dates.push(subDays(resetHours(new Date()), 7));
dates.push(subDays(resetHours(new Date()), 6));
dates.push(subDays(resetHours(new Date()), 5));
dates.push(subDays(resetHours(new Date()), 4));
dates.push(subDays(resetHours(new Date()), 3));
dates.push(subDays(resetHours(new Date()), 2));
dates.push(subDays(resetHours(new Date()), 1));
dates.push(resetHours(new Date()));
dates = uniq(
dates.map((date) => {
return date.getTime();
})
)
.map((timestamp) => {
return new Date(timestamp);
})
.sort((a, b) => {
return a.getTime() - b.getTime();
});
let exchangeRatesByCurrency =
await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)),
endDate: endOfDay(endDate),
startDate: this.getStartDate(),
targetCurrency: this.currency
});
const {
dataProviderInfos,
errors: currentRateErrors,
values: marketSymbols
} = await this.currentRateService.getValues({
dataGatheringItems,
dateQuery: {
in: dates
}
});
this.dataProviderInfos = dataProviderInfos;
const marketSymbolMap: {
[date: string]: { [symbol: string]: Big };
} = {};
for (const marketSymbol of marketSymbols) {
const date = format(marketSymbol.date, DATE_FORMAT);
if (!marketSymbolMap[date]) {
marketSymbolMap[date] = {};
}
if (marketSymbol.marketPrice) {
marketSymbolMap[date][marketSymbol.symbol] = new Big(
marketSymbol.marketPrice
);
}
}
const endDateString = format(endDate, DATE_FORMAT);
if (firstIndex > 0) {
firstIndex--;
}
const positions: TimelinePosition[] = [];
let hasAnySymbolMetricsErrors = false;
const errors: ResponseError['errors'] = [];
for (const item of lastTransactionPoint.items) {
const marketPriceInBaseCurrency = (
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice
).mul(
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
endDateString
]
);
const {
grossPerformance,
grossPerformancePercentage,
grossPerformancePercentageWithCurrencyEffect,
grossPerformanceWithCurrencyEffect,
hasErrors,
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
netPerformanceWithCurrencyEffect,
timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect,
totalDividend,
totalDividendInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect
} = this.getSymbolMetrics({
marketSymbolMap,
start,
dataSource: item.dataSource,
end: endDate,
exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
symbol: item.symbol
});
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
positions.push({
dividend: totalDividend,
dividendInBaseCurrency: totalDividendInBaseCurrency,
timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect,
averagePrice: item.averagePrice,
currency: item.currency,
dataSource: item.dataSource,
fee: item.fee,
firstBuyDate: item.firstBuyDate,
grossPerformance: !hasErrors ? grossPerformance ?? null : null,
grossPerformancePercentage: !hasErrors
? grossPerformancePercentage ?? null
: null,
grossPerformancePercentageWithCurrencyEffect: !hasErrors
? grossPerformancePercentageWithCurrencyEffect ?? null
: null,
grossPerformanceWithCurrencyEffect: !hasErrors
? grossPerformanceWithCurrencyEffect ?? null
: null,
investment: totalInvestment,
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect,
marketPrice:
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null,
marketPriceInBaseCurrency:
marketPriceInBaseCurrency?.toNumber() ?? null,
netPerformance: !hasErrors ? netPerformance ?? null : null,
netPerformancePercentage: !hasErrors
? netPerformancePercentage ?? null
: null,
netPerformancePercentageWithCurrencyEffect: !hasErrors
? netPerformancePercentageWithCurrencyEffect ?? null
: null,
netPerformanceWithCurrencyEffect: !hasErrors
? netPerformanceWithCurrencyEffect ?? null
: null,
quantity: item.quantity,
symbol: item.symbol,
tags: item.tags,
transactionCount: item.transactionCount,
valueInBaseCurrency: new Big(marketPriceInBaseCurrency).mul(
item.quantity
)
});
if (
(hasErrors ||
currentRateErrors.find(({ dataSource, symbol }) => {
return dataSource === item.dataSource && symbol === item.symbol;
})) &&
item.investment.gt(0)
) {
errors.push({ dataSource: item.dataSource, symbol: item.symbol });
}
}
const overall = this.calculateOverallPerformance(positions);
return {
...overall,
errors,
positions,
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
};
}
public async getChart({ public async getChart({
dateRange = 'max', dateRange = 'max',
@ -380,256 +646,18 @@ export abstract class PortfolioCalculator {
}); });
} }
public async getCurrentPositions( public getDataProviderInfos() {
start: Date, return this.dataProviderInfos;
end?: Date }
): Promise<CurrentPositions> {
const lastTransactionPoint = last(this.transactionPoints);
let endDate = end;
if (!endDate) {
endDate = new Date(Date.now());
if (lastTransactionPoint) {
endDate = max([endDate, parseDate(lastTransactionPoint.date)]);
}
}
const transactionPoints = this.transactionPoints?.filter(({ date }) => {
return isBefore(parseDate(date), endDate);
});
if (!transactionPoints.length) {
return {
currentValueInBaseCurrency: new Big(0),
grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0),
grossPerformancePercentageWithCurrencyEffect: new Big(0),
grossPerformanceWithCurrencyEffect: new Big(0),
hasErrors: false,
netPerformance: new Big(0),
netPerformancePercentage: new Big(0),
netPerformancePercentageWithCurrencyEffect: new Big(0),
netPerformanceWithCurrencyEffect: new Big(0),
positions: [],
totalInvestment: new Big(0),
totalInvestmentWithCurrencyEffect: new Big(0)
};
}
const currencies: { [symbol: string]: string } = {};
const dataGatheringItems: IDataGatheringItem[] = [];
let dates: Date[] = [];
let firstIndex = transactionPoints.length;
let firstTransactionPoint: TransactionPoint = null;
dates.push(resetHours(start));
for (const { currency, dataSource, symbol } of transactionPoints[
firstIndex - 1
].items) {
dataGatheringItems.push({
dataSource,
symbol
});
currencies[symbol] = currency;
}
for (let i = 0; i < transactionPoints.length; i++) {
if (
!isBefore(parseDate(transactionPoints[i].date), start) &&
firstTransactionPoint === null
) {
firstTransactionPoint = transactionPoints[i];
firstIndex = i;
}
if (firstTransactionPoint !== null) {
dates.push(resetHours(parseDate(transactionPoints[i].date)));
}
}
dates.push(resetHours(endDate));
// Add dates of last week for fallback public async getDividendInBaseCurrency() {
dates.push(subDays(resetHours(new Date()), 7)); await this.snapshotPromise;
dates.push(subDays(resetHours(new Date()), 6));
dates.push(subDays(resetHours(new Date()), 5));
dates.push(subDays(resetHours(new Date()), 4));
dates.push(subDays(resetHours(new Date()), 3));
dates.push(subDays(resetHours(new Date()), 2));
dates.push(subDays(resetHours(new Date()), 1));
dates.push(resetHours(new Date()));
dates = uniq( return getSum(
dates.map((date) => { this.snapshot.positions.map(({ dividendInBaseCurrency }) => {
return date.getTime(); return dividendInBaseCurrency;
})
)
.map((timestamp) => {
return new Date(timestamp);
}) })
.sort((a, b) => { );
return a.getTime() - b.getTime();
});
let exchangeRatesByCurrency =
await this.exchangeRateDataService.getExchangeRatesByCurrency({
currencies: uniq(Object.values(currencies)),
endDate: endOfDay(endDate),
startDate: this.getStartDate(),
targetCurrency: this.currency
});
const {
dataProviderInfos,
errors: currentRateErrors,
values: marketSymbols
} = await this.currentRateService.getValues({
dataGatheringItems,
dateQuery: {
in: dates
}
});
this.dataProviderInfos = dataProviderInfos;
const marketSymbolMap: {
[date: string]: { [symbol: string]: Big };
} = {};
for (const marketSymbol of marketSymbols) {
const date = format(marketSymbol.date, DATE_FORMAT);
if (!marketSymbolMap[date]) {
marketSymbolMap[date] = {};
}
if (marketSymbol.marketPrice) {
marketSymbolMap[date][marketSymbol.symbol] = new Big(
marketSymbol.marketPrice
);
}
}
const endDateString = format(endDate, DATE_FORMAT);
if (firstIndex > 0) {
firstIndex--;
}
const positions: TimelinePosition[] = [];
let hasAnySymbolMetricsErrors = false;
const errors: ResponseError['errors'] = [];
for (const item of lastTransactionPoint.items) {
const marketPriceInBaseCurrency = (
marketSymbolMap[endDateString]?.[item.symbol] ?? item.averagePrice
).mul(
exchangeRatesByCurrency[`${item.currency}${this.currency}`]?.[
endDateString
]
);
const {
grossPerformance,
grossPerformancePercentage,
grossPerformancePercentageWithCurrencyEffect,
grossPerformanceWithCurrencyEffect,
hasErrors,
netPerformance,
netPerformancePercentage,
netPerformancePercentageWithCurrencyEffect,
netPerformanceWithCurrencyEffect,
timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect,
totalDividend,
totalDividendInBaseCurrency,
totalInvestment,
totalInvestmentWithCurrencyEffect
} = this.getSymbolMetrics({
marketSymbolMap,
start,
dataSource: item.dataSource,
end: endDate,
exchangeRates:
exchangeRatesByCurrency[`${item.currency}${this.currency}`],
symbol: item.symbol
});
hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors;
positions.push({
dividend: totalDividend,
dividendInBaseCurrency: totalDividendInBaseCurrency,
timeWeightedInvestment,
timeWeightedInvestmentWithCurrencyEffect,
averagePrice: item.averagePrice,
currency: item.currency,
dataSource: item.dataSource,
fee: item.fee,
firstBuyDate: item.firstBuyDate,
grossPerformance: !hasErrors ? grossPerformance ?? null : null,
grossPerformancePercentage: !hasErrors
? grossPerformancePercentage ?? null
: null,
grossPerformancePercentageWithCurrencyEffect: !hasErrors
? grossPerformancePercentageWithCurrencyEffect ?? null
: null,
grossPerformanceWithCurrencyEffect: !hasErrors
? grossPerformanceWithCurrencyEffect ?? null
: null,
investment: totalInvestment,
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect,
marketPrice:
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null,
marketPriceInBaseCurrency:
marketPriceInBaseCurrency?.toNumber() ?? null,
netPerformance: !hasErrors ? netPerformance ?? null : null,
netPerformancePercentage: !hasErrors
? netPerformancePercentage ?? null
: null,
netPerformancePercentageWithCurrencyEffect: !hasErrors
? netPerformancePercentageWithCurrencyEffect ?? null
: null,
netPerformanceWithCurrencyEffect: !hasErrors
? netPerformanceWithCurrencyEffect ?? null
: null,
quantity: item.quantity,
symbol: item.symbol,
tags: item.tags,
transactionCount: item.transactionCount,
valueInBaseCurrency: new Big(marketPriceInBaseCurrency).mul(
item.quantity
)
});
if (
(hasErrors ||
currentRateErrors.find(({ dataSource, symbol }) => {
return dataSource === item.dataSource && symbol === item.symbol;
})) &&
item.investment.gt(0)
) {
errors.push({ dataSource: item.dataSource, symbol: item.symbol });
}
}
const overall = this.calculateOverallPerformance(positions);
return {
...overall,
errors,
positions,
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
};
}
public getDataProviderInfos() {
return this.dataProviderInfos;
} }
public getInvestments(): { date: string; investment: Big }[] { public getInvestments(): { date: string; investment: Big }[] {
@ -672,6 +700,12 @@ export abstract class PortfolioCalculator {
})); }));
} }
public async getSnapshot() {
await this.snapshotPromise;
return this.snapshot;
}
public getStartDate() { public getStartDate() {
return this.transactionPoints.length > 0 return this.transactionPoints.length > 0
? parseDate(this.transactionPoints[0].date) ? parseDate(this.transactionPoints[0].date)
@ -804,4 +838,8 @@ export abstract class PortfolioCalculator {
lastDate = date; lastDate = date;
} }
} }
private async initialize() {
this.snapshot = await this.computeSnapshot(this.startDate, this.endDate);
}
} }

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

@ -46,6 +46,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with BALN.SW buy and sell in two activities', async () => { it.only('with BALN.SW buy and sell in two activities', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -100,15 +104,11 @@ describe('PortfolioCalculator', () => {
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2021-11-22') start: parseDate('2021-11-22')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2021-11-22') parseDate('2021-11-22')
); );
@ -121,7 +121,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
grossPerformance: new Big('-12.6'), grossPerformance: new Big('-12.6'),

12
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -46,6 +46,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with BALN.SW buy and sell', async () => { it.only('with BALN.SW buy and sell', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -85,15 +89,11 @@ describe('PortfolioCalculator', () => {
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2021-11-22') start: parseDate('2021-11-22')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2021-11-22') parseDate('2021-11-22')
); );
@ -106,7 +106,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
grossPerformance: new Big('-12.6'), grossPerformance: new Big('-12.6'),

12
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-baln-buy.spec.ts

@ -46,6 +46,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with BALN.SW buy', async () => { it.only('with BALN.SW buy', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -70,15 +74,11 @@ describe('PortfolioCalculator', () => {
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2021-11-30') start: parseDate('2021-11-30')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2021-11-30') parseDate('2021-11-30')
); );
@ -91,7 +91,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('297.8'), currentValueInBaseCurrency: new Big('297.8'),
errors: [], errors: [],
grossPerformance: new Big('24.6'), grossPerformance: new Big('24.6'),

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

@ -59,6 +59,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with BTCUSD buy and sell partially', async () => { it.only('with BTCUSD buy and sell partially', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2018-01-01').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -98,15 +102,11 @@ describe('PortfolioCalculator', () => {
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2018-01-01').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2015-01-01') start: parseDate('2015-01-01')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2015-01-01') parseDate('2015-01-01')
); );
@ -119,7 +119,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('13298.425356'), currentValueInBaseCurrency: new Big('13298.425356'),
errors: [], errors: [],
grossPerformance: new Big('27172.74'), grossPerformance: new Big('27172.74'),

12
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-googl-buy.spec.ts

@ -59,6 +59,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with GOOGL buy', async () => { it.only('with GOOGL buy', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2023-07-10').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -83,15 +87,11 @@ describe('PortfolioCalculator', () => {
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2023-07-10').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2023-01-03') start: parseDate('2023-01-03')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2023-01-03') parseDate('2023-01-03')
); );
@ -104,7 +104,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('103.10483'), currentValueInBaseCurrency: new Big('103.10483'),
errors: [], errors: [],
grossPerformance: new Big('27.33'), grossPerformance: new Big('27.33'),

12
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -59,6 +59,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with MSFT buy', async () => { it.only('with MSFT buy', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2023-07-10').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -98,17 +102,13 @@ describe('PortfolioCalculator', () => {
currency: 'USD' currency: 'USD'
}); });
const spy = jest const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2023-07-10').getTime());
const currentPositions = await portfolioCalculator.getCurrentPositions(
parseDate('2023-07-10') parseDate('2023-07-10')
); );
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toMatchObject({ expect(portfolioSnapshot).toMatchObject({
errors: [], errors: [],
hasErrors: false, hasErrors: false,
positions: [ positions: [

14
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-no-orders.spec.ts

@ -42,22 +42,22 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it('with no orders', async () => { it('with no orders', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const portfolioCalculator = factory.createCalculator({ const portfolioCalculator = factory.createCalculator({
activities: [], activities: [],
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2021-12-18').getTime());
const start = subDays(new Date(Date.now()), 10); const start = subDays(new Date(Date.now()), 10);
const chartData = await portfolioCalculator.getChartData({ start }); const chartData = await portfolioCalculator.getChartData({ start });
const currentPositions = const portfolioSnapshot =
await portfolioCalculator.getCurrentPositions(start); await portfolioCalculator.computeSnapshot(start);
const investments = portfolioCalculator.getInvestments(); const investments = portfolioCalculator.getInvestments();
@ -68,7 +68,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big(0), currentValueInBaseCurrency: new Big(0),
grossPerformance: new Big(0), grossPerformance: new Big(0),
grossPerformancePercentage: new Big(0), grossPerformancePercentage: new Big(0),

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

@ -46,6 +46,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with NOVN.SW buy and sell partially', async () => { it.only('with NOVN.SW buy and sell partially', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-04-11').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -84,15 +88,12 @@ describe('PortfolioCalculator', () => {
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-04-11').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2022-03-07') start: parseDate('2022-03-07')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2022-03-07') parseDate('2022-03-07')
); );
@ -105,7 +106,7 @@ describe('PortfolioCalculator', () => {
spy.mockRestore(); spy.mockRestore();
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('87.8'), currentValueInBaseCurrency: new Big('87.8'),
errors: [], errors: [],
grossPerformance: new Big('21.93'), grossPerformance: new Big('21.93'),

12
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -46,6 +46,10 @@ describe('PortfolioCalculator', () => {
describe('get current positions', () => { describe('get current positions', () => {
it.only('with NOVN.SW buy and sell', async () => { it.only('with NOVN.SW buy and sell', async () => {
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-04-11').getTime());
const activities: Activity[] = [ const activities: Activity[] = [
{ {
...activityDummyData, ...activityDummyData,
@ -85,15 +89,11 @@ describe('PortfolioCalculator', () => {
currency: 'CHF' currency: 'CHF'
}); });
const spy = jest
.spyOn(Date, 'now')
.mockImplementation(() => parseDate('2022-04-11').getTime());
const chartData = await portfolioCalculator.getChartData({ const chartData = await portfolioCalculator.getChartData({
start: parseDate('2022-03-07') start: parseDate('2022-03-07')
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const portfolioSnapshot = await portfolioCalculator.computeSnapshot(
parseDate('2022-03-07') parseDate('2022-03-07')
); );
@ -132,7 +132,7 @@ describe('PortfolioCalculator', () => {
valueWithCurrencyEffect: 0 valueWithCurrencyEffect: 0
}); });
expect(currentPositions).toEqual({ expect(portfolioSnapshot).toEqual({
currentValueInBaseCurrency: new Big('0'), currentValueInBaseCurrency: new Big('0'),
errors: [], errors: [],
grossPerformance: new Big('19.86'), grossPerformance: new Big('19.86'),

4
apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts

@ -1,6 +1,6 @@
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator'; import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator';
import { CurrentPositions } from '@ghostfolio/api/app/portfolio/interfaces/current-positions.interface';
import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface'; import { PortfolioOrderItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order-item.interface';
import { PortfolioSnapshot } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-snapshot.interface';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { import {
@ -23,7 +23,7 @@ import { cloneDeep, first, last, sortBy } from 'lodash';
export class TWRPortfolioCalculator extends PortfolioCalculator { export class TWRPortfolioCalculator extends PortfolioCalculator {
protected calculateOverallPerformance( protected calculateOverallPerformance(
positions: TimelinePosition[] positions: TimelinePosition[]
): CurrentPositions { ): PortfolioSnapshot {
let currentValueInBaseCurrency = new Big(0); let currentValueInBaseCurrency = new Big(0);
let grossPerformance = new Big(0); let grossPerformance = new Big(0);
let grossPerformanceWithCurrencyEffect = new Big(0); let grossPerformanceWithCurrencyEffect = new Big(0);

2
apps/api/src/app/portfolio/interfaces/current-positions.interface.ts → apps/api/src/app/portfolio/interfaces/portfolio-snapshot.interface.ts

@ -2,7 +2,7 @@ import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
import { Big } from 'big.js'; import { Big } from 'big.js';
export interface CurrentPositions extends ResponseError { export interface PortfolioSnapshot extends ResponseError {
currentValueInBaseCurrency: Big; currentValueInBaseCurrency: Big;
grossPerformance: Big; grossPerformance: Big;
grossPerformanceWithCurrencyEffect: Big; grossPerformanceWithCurrencyEffect: Big;

3
apps/api/src/app/portfolio/portfolio.controller.ts

@ -107,7 +107,8 @@ export class PortfolioController {
dateRange, dateRange,
filters, filters,
impersonationId, impersonationId,
withLiabilities, // TODO
// withLiabilities,
withMarkets, withMarkets,
userId: this.request.user.id, userId: this.request.user.id,
withSummary: true withSummary: true

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

@ -78,6 +78,7 @@ import {
} from 'date-fns'; } from 'date-fns';
import { isEmpty, isNumber, last, uniq, uniqBy } from 'lodash'; import { isEmpty, isNumber, last, uniq, uniqBy } from 'lodash';
import { PortfolioCalculator } from './calculator/portfolio-calculator';
import { import {
PerformanceCalculationType, PerformanceCalculationType,
PortfolioCalculatorFactory PortfolioCalculatorFactory
@ -369,16 +370,12 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
dateRange,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency currency: userCurrency
}); });
const { startDate } = getInterval( const currentPositions = await portfolioCalculator.getSnapshot();
dateRange,
portfolioCalculator.getStartDate()
);
const currentPositions =
await portfolioCalculator.getCurrentPositions(startDate);
const cashDetails = await this.accountService.getCashDetails({ const cashDetails = await this.accountService.getCashDetails({
filters, filters,
@ -593,6 +590,7 @@ export class PortfolioService {
filteredValueInBaseCurrency, filteredValueInBaseCurrency,
holdings, holdings,
impersonationId, impersonationId,
portfolioCalculator,
userCurrency, userCurrency,
userId, userId,
balanceInBaseCurrency: cashDetails.balanceInBaseCurrency, balanceInBaseCurrency: cashDetails.balanceInBaseCurrency,
@ -681,8 +679,7 @@ export class PortfolioService {
const portfolioStart = portfolioCalculator.getStartDate(); const portfolioStart = portfolioCalculator.getStartDate();
const transactionPoints = portfolioCalculator.getTransactionPoints(); const transactionPoints = portfolioCalculator.getTransactionPoints();
const currentPositions = const currentPositions = await portfolioCalculator.getSnapshot();
await portfolioCalculator.getCurrentPositions(portfolioStart);
const position = currentPositions.positions.find(({ symbol }) => { const position = currentPositions.positions.find(({ symbol }) => {
return symbol === aSymbol; return symbol === aSymbol;
@ -916,7 +913,7 @@ export class PortfolioService {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId }); const user = await this.userService.user({ id: userId });
const { endDate, startDate } = getInterval(dateRange); const { endDate } = getInterval(dateRange);
const { activities } = await this.orderService.getOrders({ const { activities } = await this.orderService.getOrders({
endDate, endDate,
@ -935,14 +932,12 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
dateRange,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: this.request.user.Settings.settings.baseCurrency currency: this.request.user.Settings.settings.baseCurrency
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const currentPositions = await portfolioCalculator.getSnapshot();
startDate,
endDate
);
let positions = currentPositions.positions.filter(({ quantity }) => { let positions = currentPositions.positions.filter(({ quantity }) => {
return !quantity.eq(0); return !quantity.eq(0);
@ -1097,7 +1092,9 @@ export class PortfolioService {
userCurrency, userCurrency,
userId, userId,
withExcludedAccounts, withExcludedAccounts,
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] types: withItems
? ['BUY', 'DIVIDEND', 'ITEM', 'SELL']
: ['BUY', 'DIVIDEND', 'SELL']
}); });
if (accountBalanceItems?.length <= 0 && activities?.length <= 0) { if (accountBalanceItems?.length <= 0 && activities?.length <= 0) {
@ -1123,6 +1120,7 @@ export class PortfolioService {
const portfolioCalculator = this.calculatorFactory.createCalculator({ const portfolioCalculator = this.calculatorFactory.createCalculator({
activities, activities,
dateRange,
calculationType: PerformanceCalculationType.TWR, calculationType: PerformanceCalculationType.TWR,
currency: userCurrency currency: userCurrency
}); });
@ -1140,7 +1138,7 @@ export class PortfolioService {
netPerformancePercentageWithCurrencyEffect, netPerformancePercentageWithCurrencyEffect,
netPerformanceWithCurrencyEffect, netPerformanceWithCurrencyEffect,
totalInvestment totalInvestment
} = await portfolioCalculator.getCurrentPositions(startDate, endDate); } = await portfolioCalculator.getSnapshot();
let currentNetPerformance = netPerformance; let currentNetPerformance = netPerformance;
@ -1241,9 +1239,7 @@ export class PortfolioService {
currency: this.request.user.Settings.settings.baseCurrency currency: this.request.user.Settings.settings.baseCurrency
}); });
const currentPositions = await portfolioCalculator.getCurrentPositions( const currentPositions = await portfolioCalculator.getSnapshot();
portfolioCalculator.getStartDate()
);
const positions = currentPositions.positions.filter( const positions = currentPositions.positions.filter(
(item) => !item.quantity.eq(0) (item) => !item.quantity.eq(0)
@ -1623,6 +1619,7 @@ export class PortfolioService {
filteredValueInBaseCurrency, filteredValueInBaseCurrency,
holdings, holdings,
impersonationId, impersonationId,
portfolioCalculator,
userCurrency, userCurrency,
userId userId
}: { }: {
@ -1631,6 +1628,7 @@ export class PortfolioService {
filteredValueInBaseCurrency: Big; filteredValueInBaseCurrency: Big;
holdings: PortfolioDetails['holdings']; holdings: PortfolioDetails['holdings'];
impersonationId: string; impersonationId: string;
portfolioCalculator: PortfolioCalculator;
userCurrency: string; userCurrency: string;
userId: string; userId: string;
}): Promise<PortfolioSummary> { }): Promise<PortfolioSummary> {
@ -1659,17 +1657,8 @@ export class PortfolioService {
} }
} }
const dividendInBaseCurrency = getSum( const dividendInBaseCurrency =
( await portfolioCalculator.getDividendInBaseCurrency();
await this.getDividends({
activities: activities.filter(({ type }) => {
return type === 'DIVIDEND';
})
})
).map(({ investment }) => {
return new Big(investment);
})
);
const emergencyFund = new Big( const emergencyFund = new Big(
Math.max( Math.max(

26
apps/api/src/helper/portfolio.helper.ts

@ -37,36 +37,48 @@ export function getInterval(
aDateRange: DateRange, aDateRange: DateRange,
portfolioStart = new Date(0) portfolioStart = new Date(0)
) { ) {
let endDate = endOfDay(new Date()); let endDate = endOfDay(new Date(Date.now()));
let startDate = portfolioStart; let startDate = portfolioStart;
switch (aDateRange) { switch (aDateRange) {
case '1d': case '1d':
startDate = max([startDate, subDays(resetHours(new Date()), 1)]); startDate = max([
startDate,
subDays(resetHours(new Date(Date.now())), 1)
]);
break; break;
case 'mtd': case 'mtd':
startDate = max([ startDate = max([
startDate, startDate,
subDays(startOfMonth(resetHours(new Date())), 1) subDays(startOfMonth(resetHours(new Date(Date.now()))), 1)
]); ]);
break; break;
case 'wtd': case 'wtd':
startDate = max([ startDate = max([
startDate, startDate,
subDays(startOfWeek(resetHours(new Date()), { weekStartsOn: 1 }), 1) subDays(
startOfWeek(resetHours(new Date(Date.now())), { weekStartsOn: 1 }),
1
)
]); ]);
break; break;
case 'ytd': case 'ytd':
startDate = max([ startDate = max([
startDate, startDate,
subDays(startOfYear(resetHours(new Date())), 1) subDays(startOfYear(resetHours(new Date(Date.now()))), 1)
]); ]);
break; break;
case '1y': case '1y':
startDate = max([startDate, subYears(resetHours(new Date()), 1)]); startDate = max([
startDate,
subYears(resetHours(new Date(Date.now())), 1)
]);
break; break;
case '5y': case '5y':
startDate = max([startDate, subYears(resetHours(new Date()), 5)]); startDate = max([
startDate,
subYears(resetHours(new Date(Date.now())), 5)
]);
break; break;
case 'max': case 'max':
break; break;

Loading…
Cancel
Save