diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index a65e30d53..126b04a07 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -198,22 +198,26 @@ export class OrderService { } public async getOrders({ + endDate, filters, includeDrafts = false, skip, sortColumn, sortDirection, + startDate, take = Number.MAX_SAFE_INTEGER, types, userCurrency, userId, withExcludedAccounts = false }: { + endDate?: Date; filters?: Filter[]; includeDrafts?: boolean; skip?: number; sortColumn?: string; sortDirection?: Prisma.SortOrder; + startDate?: Date; take?: number; types?: ActivityType[]; userCurrency: string; @@ -225,6 +229,18 @@ export class OrderService { ]; const where: Prisma.OrderWhereInput = { userId }; + if (endDate || startDate) { + where.AND = []; + + if (endDate) { + where.AND.push({ date: { lte: endDate } }); + } + + if (startDate) { + where.AND.push({ date: { gt: startDate } }); + } + } + const { ACCOUNT: filtersByAccount, ASSET_CLASS: filtersByAssetClass, diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index c2dffebeb..5f62b0969 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -236,8 +236,12 @@ export class PortfolioController { await this.impersonationService.validateImpersonationId(impersonationId); const userCurrency = this.request.user.Settings.settings.baseCurrency; + const { endDate, startDate } = this.portfolioService.getInterval(dateRange); + const { activities } = await this.orderService.getOrders({ + endDate, filters, + startDate, userCurrency, userId: impersonationUserId || this.request.user.id, types: ['DIVIDEND'] @@ -245,7 +249,6 @@ export class PortfolioController { let dividends = await this.portfolioService.getDividends({ activities, - dateRange, groupBy }); diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1db3fb85e..fca5e1e0d 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -87,7 +87,7 @@ import { endOfDay, endOfYear } from 'date-fns'; -import { isEmpty, last, uniq, uniqBy } from 'lodash'; +import { first, isEmpty, last, uniq, uniqBy } from 'lodash'; import { PortfolioCalculator } from './calculator/twr/portfolio-calculator'; import { @@ -224,11 +224,9 @@ export class PortfolioService { public async getDividends({ activities, - dateRange = 'max', groupBy }: { activities: Activity[]; - dateRange?: DateRange; groupBy?: GroupBy; }): Promise { let dividends = activities.map(({ date, valueInBaseCurrency }) => { @@ -242,14 +240,50 @@ export class PortfolioService { dividends = this.getDividendsByGroup({ dividends, groupBy }); } - const { startDate } = this.getInterval( - dateRange, - parseDate(dividends[0]?.date) - ); + return dividends; + } - return dividends.filter(({ date }) => { - return !isBefore(parseDate(date), startDate); - }); + public getInterval(aDateRange: DateRange, portfolioStart = new Date(0)) { + let endDate = endOfDay(new Date()); + let startDate = portfolioStart; + + switch (aDateRange) { + case '1d': + startDate = max([startDate, subDays(resetHours(new Date()), 1)]); + break; + case 'mtd': + startDate = max([ + startDate, + subDays(startOfMonth(resetHours(new Date())), 1) + ]); + break; + case 'wtd': + startDate = max([ + startDate, + subDays(startOfWeek(resetHours(new Date()), { weekStartsOn: 1 }), 1) + ]); + break; + case 'ytd': + startDate = max([ + startDate, + subDays(startOfYear(resetHours(new Date())), 1) + ]); + break; + case '1y': + startDate = max([startDate, subYears(resetHours(new Date()), 1)]); + break; + case '5y': + startDate = max([startDate, subYears(resetHours(new Date()), 5)]); + break; + case 'max': + break; + default: + // '2024', '2023', '2022', etc. + endDate = endOfYear(new Date(aDateRange)); + startDate = max([startDate, new Date(aDateRange)]); + } + + return { endDate, startDate }; } public async getInvestments({ @@ -963,7 +997,10 @@ export class PortfolioService { const userId = await this.getUserId(impersonationId, this.request.user.id); const user = await this.userService.user({ id: userId }); + const { endDate, startDate } = this.getInterval(dateRange); + const { activities } = await this.orderService.getOrders({ + endDate, filters, userId, types: ['BUY', 'SELL'], @@ -984,12 +1021,10 @@ export class PortfolioService { exchangeRateDataService: this.exchangeRateDataService }); - const { startDate } = this.getInterval( - dateRange, - portfolioCalculator.getStartDate() + const currentPositions = await portfolioCalculator.getCurrentPositions( + startDate, + endDate ); - const currentPositions = - await portfolioCalculator.getCurrentPositions(startDate); let positions = currentPositions.positions.filter(({ quantity }) => { return !quantity.eq(0); @@ -1136,7 +1171,10 @@ export class PortfolioService { ) ); + const { endDate, startDate } = this.getInterval(dateRange); + const { activities } = await this.orderService.getOrders({ + endDate, filters, userCurrency, userId, @@ -1172,16 +1210,6 @@ export class PortfolioService { exchangeRateDataService: this.exchangeRateDataService }); - const portfolioStart = min( - [ - parseDate(accountBalanceItems[0]?.date), - portfolioCalculator.getStartDate() - ].filter((date) => { - return isValid(date); - }) - ); - - const { startDate } = this.getInterval(dateRange, portfolioStart); const { currentValueInBaseCurrency, errors, @@ -1195,7 +1223,7 @@ export class PortfolioService { netPerformancePercentageWithCurrencyEffect, netPerformanceWithCurrencyEffect, totalInvestment - } = await portfolioCalculator.getCurrentPositions(startDate); // TODO: Provide endDate + } = await portfolioCalculator.getCurrentPositions(startDate, endDate); let currentNetPerformance = netPerformance; @@ -1620,49 +1648,6 @@ export class PortfolioService { }; } - private getInterval(aDateRange: DateRange, portfolioStart: Date) { - let endDate = endOfDay(new Date()); - let startDate = portfolioStart; - - switch (aDateRange) { - case '1d': - startDate = max([startDate, subDays(resetHours(new Date()), 1)]); - break; - case 'mtd': - startDate = max([ - startDate, - subDays(startOfMonth(resetHours(new Date())), 1) - ]); - break; - case 'wtd': - startDate = max([ - startDate, - subDays(startOfWeek(resetHours(new Date()), { weekStartsOn: 1 }), 1) - ]); - break; - case 'ytd': - startDate = max([ - startDate, - subDays(startOfYear(resetHours(new Date())), 1) - ]); - break; - case '1y': - startDate = max([startDate, subYears(resetHours(new Date()), 1)]); - break; - case '5y': - startDate = max([startDate, subYears(resetHours(new Date()), 5)]); - break; - case 'max': - break; - default: - // '2024', '2023', '2022', etc. - endDate = endOfYear(new Date(aDateRange)); - startDate = max([startDate, new Date(aDateRange)]); - } - - return { endDate, startDate }; - } - private getStreaks({ investments, savingsRate diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index db92b6bcb..d260b3aaf 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -41,7 +41,7 @@ export class UpdateUserSettingDto { 'mtd', 'wtd', 'ytd', - ...eachYearOfInterval({ end: new Date(), start: new Date(1900) }).map( + ...eachYearOfInterval({ end: new Date(), start: new Date(0) }).map( (date) => { return format(date, 'yyyy'); } diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index 56de9d438..429eaae6f 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -6,7 +6,6 @@ import { } from '@ghostfolio/common/chart-helper'; import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config'; import { - DATE_FORMAT, getBackgroundColor, getDateFormatString, getLocale, @@ -39,16 +38,8 @@ import { } from 'chart.js'; import 'chartjs-adapter-date-fns'; import annotationPlugin from 'chartjs-plugin-annotation'; -import { - addDays, - format, - isAfter, - isValid, - min, - parseISO, - subDays -} from 'date-fns'; -import { first, last } from 'lodash'; +import { isAfter, isValid, min, subDays } from 'date-fns'; +import { first } from 'lodash'; @Component({ selector: 'gf-investment-chart', @@ -112,46 +103,6 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy { Object.assign({}, item) ); - if (!this.groupBy && this.investments?.length > 0) { - let date: string; - - if (this.range === 'max') { - // Extend chart by 5% of days in market (before) - date = format( - subDays( - parseISO(this.investments[0].date), - this.daysInMarket * 0.05 || 90 - ), - DATE_FORMAT - ); - this.investments.unshift({ - date, - investment: 0 - }); - this.values.unshift({ - date, - value: 0 - }); - } - - // Extend chart by 5% of days in market (after) - date = format( - addDays( - parseDate(last(this.investments).date), - this.daysInMarket * 0.05 || 90 - ), - DATE_FORMAT - ); - this.investments.push({ - date, - investment: last(this.investments).investment - }); - this.values.push({ - date, - value: last(this.values).value - }); - } - const chartData: ChartData<'bar' | 'line'> = { labels: this.historicalDataItems.map(({ date }) => { return parseDate(date); @@ -303,7 +254,6 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy { display: false }, min: scaleXMin, - suggestedMax: new Date().toISOString(), type: 'time', time: { tooltipFormat: getDateFormatString(this.locale),