Fix chart tooltip of benchmark comparator (#3167)
* Fix chart tooltip of benchmark comparator * Update changelog
This commit is contained in:
parent
0edebe30e1
commit
b41eb60348
@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Extended the export functionality by the user account’s currency
|
- Extended the export functionality by the user account’s currency
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed the chart tooltip of the benchmark comparator
|
||||||
|
|
||||||
## 2.67.0 - 2024-03-26
|
## 2.67.0 - 2024-03-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -13,7 +13,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
DATE_FORMAT,
|
DATE_FORMAT,
|
||||||
calculateBenchmarkTrend,
|
calculateBenchmarkTrend,
|
||||||
parseDate
|
parseDate,
|
||||||
|
resetHours
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import {
|
import {
|
||||||
Benchmark,
|
Benchmark,
|
||||||
@ -27,7 +28,13 @@ import { BenchmarkTrend } from '@ghostfolio/common/types';
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { SymbolProfile } from '@prisma/client';
|
import { SymbolProfile } from '@prisma/client';
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
import { format, isSameDay, subDays } from 'date-fns';
|
import {
|
||||||
|
differenceInDays,
|
||||||
|
eachDayOfInterval,
|
||||||
|
format,
|
||||||
|
isSameDay,
|
||||||
|
subDays
|
||||||
|
} from 'date-fns';
|
||||||
import { isNumber, last, uniqBy } from 'lodash';
|
import { isNumber, last, uniqBy } from 'lodash';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
|
||||||
@ -208,15 +215,28 @@ export class BenchmarkService {
|
|||||||
|
|
||||||
public async getMarketDataBySymbol({
|
public async getMarketDataBySymbol({
|
||||||
dataSource,
|
dataSource,
|
||||||
|
endDate = new Date(),
|
||||||
startDate,
|
startDate,
|
||||||
symbol,
|
symbol,
|
||||||
userCurrency
|
userCurrency
|
||||||
}: {
|
}: {
|
||||||
|
endDate?: Date;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
} & UniqueAsset): Promise<BenchmarkMarketDataDetails> {
|
} & UniqueAsset): Promise<BenchmarkMarketDataDetails> {
|
||||||
const marketData: { date: string; value: number }[] = [];
|
const marketData: { date: string; value: number }[] = [];
|
||||||
|
|
||||||
|
const days = differenceInDays(endDate, startDate) + 1;
|
||||||
|
const dates = eachDayOfInterval(
|
||||||
|
{
|
||||||
|
start: startDate,
|
||||||
|
end: endDate
|
||||||
|
},
|
||||||
|
{ step: Math.round(days / Math.min(days, MAX_CHART_ITEMS)) }
|
||||||
|
).map((date) => {
|
||||||
|
return resetHours(date);
|
||||||
|
});
|
||||||
|
|
||||||
const [currentSymbolItem, marketDataItems] = await Promise.all([
|
const [currentSymbolItem, marketDataItems] = await Promise.all([
|
||||||
this.symbolService.get({
|
this.symbolService.get({
|
||||||
dataGatheringItem: {
|
dataGatheringItem: {
|
||||||
@ -232,7 +252,7 @@ export class BenchmarkService {
|
|||||||
dataSource,
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
date: {
|
date: {
|
||||||
gte: startDate
|
in: dates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -266,17 +286,7 @@ export class BenchmarkService {
|
|||||||
return { marketData };
|
return { marketData };
|
||||||
}
|
}
|
||||||
|
|
||||||
const step = Math.round(
|
|
||||||
marketDataItems.length / Math.min(marketDataItems.length, MAX_CHART_ITEMS)
|
|
||||||
);
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
for (let marketDataItem of marketDataItems) {
|
for (let marketDataItem of marketDataItems) {
|
||||||
if (i % step !== 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const exchangeRate =
|
const exchangeRate =
|
||||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||||
format(marketDataItem.date, DATE_FORMAT)
|
format(marketDataItem.date, DATE_FORMAT)
|
||||||
@ -299,15 +309,15 @@ export class BenchmarkService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const includesToday = isSameDay(
|
const includesEndDate = isSameDay(
|
||||||
parseDate(last(marketData).date),
|
parseDate(last(marketData).date),
|
||||||
new Date()
|
endDate
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentSymbolItem?.marketPrice && !includesToday) {
|
if (currentSymbolItem?.marketPrice && !includesEndDate) {
|
||||||
const exchangeRate =
|
const exchangeRate =
|
||||||
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
|
||||||
format(new Date(), DATE_FORMAT)
|
format(endDate, DATE_FORMAT)
|
||||||
];
|
];
|
||||||
|
|
||||||
const exchangeRateFactor =
|
const exchangeRateFactor =
|
||||||
@ -316,7 +326,7 @@ export class BenchmarkService {
|
|||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
marketData.push({
|
marketData.push({
|
||||||
date: format(new Date(), DATE_FORMAT),
|
date: format(endDate, DATE_FORMAT),
|
||||||
value:
|
value:
|
||||||
this.calculateChangeInPercentage(
|
this.calculateChangeInPercentage(
|
||||||
marketPriceAtStartDate,
|
marketPriceAtStartDate,
|
||||||
|
@ -34,7 +34,7 @@ export class CurrentRateService {
|
|||||||
}: GetValuesParams): Promise<GetValuesObject> {
|
}: GetValuesParams): Promise<GetValuesObject> {
|
||||||
const dataProviderInfos: DataProviderInfo[] = [];
|
const dataProviderInfos: DataProviderInfo[] = [];
|
||||||
|
|
||||||
const includeToday =
|
const includesToday =
|
||||||
(!dateQuery.lt || isBefore(new Date(), dateQuery.lt)) &&
|
(!dateQuery.lt || isBefore(new Date(), dateQuery.lt)) &&
|
||||||
(!dateQuery.gte || isBefore(dateQuery.gte, new Date())) &&
|
(!dateQuery.gte || isBefore(dateQuery.gte, new Date())) &&
|
||||||
(!dateQuery.in || this.containsToday(dateQuery.in));
|
(!dateQuery.in || this.containsToday(dateQuery.in));
|
||||||
@ -43,7 +43,7 @@ export class CurrentRateService {
|
|||||||
const quoteErrors: ResponseError['errors'] = [];
|
const quoteErrors: ResponseError['errors'] = [];
|
||||||
const today = resetHours(new Date());
|
const today = resetHours(new Date());
|
||||||
|
|
||||||
if (includeToday) {
|
if (includesToday) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.dataProviderService
|
this.dataProviderService
|
||||||
.getQuotes({ items: dataGatheringItems, user: this.request?.user })
|
.getQuotes({ items: dataGatheringItems, user: this.request?.user })
|
||||||
|
@ -3,6 +3,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
|
|||||||
import { parseDate } from '@ghostfolio/common/helper';
|
import { parseDate } from '@ghostfolio/common/helper';
|
||||||
|
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
|
import { subDays } from 'date-fns';
|
||||||
|
|
||||||
import { CurrentRateServiceMock } from './current-rate.service.mock';
|
import { CurrentRateServiceMock } from './current-rate.service.mock';
|
||||||
import { PortfolioCalculator } from './portfolio-calculator';
|
import { PortfolioCalculator } from './portfolio-calculator';
|
||||||
@ -46,13 +47,12 @@ describe('PortfolioCalculator', () => {
|
|||||||
.spyOn(Date, 'now')
|
.spyOn(Date, 'now')
|
||||||
.mockImplementation(() => parseDate('2021-12-18').getTime());
|
.mockImplementation(() => parseDate('2021-12-18').getTime());
|
||||||
|
|
||||||
const chartData = await portfolioCalculator.getChartData({
|
const start = subDays(new Date(Date.now()), 10);
|
||||||
start: new Date()
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
const chartData = await portfolioCalculator.getChartData({ start });
|
||||||
new Date()
|
|
||||||
);
|
const currentPositions =
|
||||||
|
await portfolioCalculator.getCurrentPositions(start);
|
||||||
|
|
||||||
const investments = portfolioCalculator.getInvestments();
|
const investments = portfolioCalculator.getInvestments();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
addDays,
|
addDays,
|
||||||
addMilliseconds,
|
addMilliseconds,
|
||||||
differenceInDays,
|
differenceInDays,
|
||||||
|
eachDayOfInterval,
|
||||||
endOfDay,
|
endOfDay,
|
||||||
format,
|
format,
|
||||||
isBefore,
|
isBefore,
|
||||||
@ -199,29 +200,31 @@ export class PortfolioCalculator {
|
|||||||
}) ?? [];
|
}) ?? [];
|
||||||
|
|
||||||
const currencies: { [symbol: string]: string } = {};
|
const currencies: { [symbol: string]: string } = {};
|
||||||
const dates: Date[] = [];
|
|
||||||
const dataGatheringItems: IDataGatheringItem[] = [];
|
const dataGatheringItems: IDataGatheringItem[] = [];
|
||||||
const firstIndex = transactionPointsBeforeEndDate.length;
|
const firstIndex = transactionPointsBeforeEndDate.length;
|
||||||
|
|
||||||
let day = start;
|
let dates = eachDayOfInterval({ start, end }, { step }).map((date) => {
|
||||||
|
return resetHours(date);
|
||||||
|
});
|
||||||
|
|
||||||
while (isBefore(day, end)) {
|
const includesEndDate = isSameDay(last(dates), end);
|
||||||
dates.push(resetHours(day));
|
|
||||||
day = addDays(day, step);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isSameDay(last(dates), end)) {
|
if (!includesEndDate) {
|
||||||
dates.push(resetHours(end));
|
dates.push(resetHours(end));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionPointsBeforeEndDate.length > 0) {
|
if (transactionPointsBeforeEndDate.length > 0) {
|
||||||
for (const item of transactionPointsBeforeEndDate[firstIndex - 1].items) {
|
for (const {
|
||||||
|
currency,
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
} of transactionPointsBeforeEndDate[firstIndex - 1].items) {
|
||||||
dataGatheringItems.push({
|
dataGatheringItems.push({
|
||||||
dataSource: item.dataSource,
|
dataSource,
|
||||||
symbol: item.symbol
|
symbol
|
||||||
});
|
});
|
||||||
currencies[item.symbol] = item.currency;
|
currencies[symbol] = currency;
|
||||||
symbols[item.symbol] = true;
|
symbols[symbol] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1482,17 +1482,13 @@ export class PortfolioService {
|
|||||||
|
|
||||||
userId = await this.getUserId(impersonationId, userId);
|
userId = await this.getUserId(impersonationId, userId);
|
||||||
|
|
||||||
const endDate = new Date();
|
|
||||||
|
|
||||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||||
const startDate = this.getStartDate(dateRange, portfolioStart);
|
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||||
|
const endDate = new Date();
|
||||||
let step = 1;
|
const daysInMarket = differenceInDays(endDate, startDate) + 1;
|
||||||
|
const step = withDataDecimation
|
||||||
if (withDataDecimation) {
|
? Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS))
|
||||||
const daysInMarket = differenceInDays(new Date(), startDate);
|
: 1;
|
||||||
step = Math.round(daysInMarket / Math.min(daysInMarket, MAX_CHART_ITEMS));
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = await portfolioCalculator.getChartData({
|
const items = await portfolioCalculator.getChartData({
|
||||||
step,
|
step,
|
||||||
|
@ -98,6 +98,12 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initialize() {
|
private initialize() {
|
||||||
|
const benchmarkDataValues: { [date: string]: number } = {};
|
||||||
|
|
||||||
|
for (const { date, value } of this.benchmarkDataItems) {
|
||||||
|
benchmarkDataValues[date] = value;
|
||||||
|
}
|
||||||
|
|
||||||
const data: ChartData<'line'> = {
|
const data: ChartData<'line'> = {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
@ -113,8 +119,11 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
backgroundColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
backgroundColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
||||||
borderColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
borderColor: `rgb(${secondaryColorRgb.r}, ${secondaryColorRgb.g}, ${secondaryColorRgb.b})`,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
data: this.benchmarkDataItems.map(({ date, value }) => {
|
data: this.performanceDataItems.map(({ date }) => {
|
||||||
return { x: parseDate(date).getTime(), y: value };
|
return {
|
||||||
|
x: parseDate(date).getTime(),
|
||||||
|
y: benchmarkDataValues[date]
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
label: this.benchmark?.name ?? $localize`Benchmark`
|
label: this.benchmark?.name ?? $localize`Benchmark`
|
||||||
}
|
}
|
||||||
@ -228,7 +237,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
locale: this.locale,
|
locale: this.locale,
|
||||||
unit: '%'
|
unit: '%'
|
||||||
}),
|
}),
|
||||||
mode: 'x',
|
mode: 'index',
|
||||||
position: <unknown>'top',
|
position: <unknown>'top',
|
||||||
xAlign: 'center',
|
xAlign: 'center',
|
||||||
yAlign: 'bottom'
|
yAlign: 'bottom'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user