diff --git a/CHANGELOG.md b/CHANGELOG.md index 349cbefc0..304478425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Extended the asset profile data in the _Financial Modeling Prep_ service -- Extended the search by `isin` in the _Financial Modeling Prep_ service +- Extended the _Financial Modeling Prep_ service - Switched to _ESLint_’s flat config format +- Upgraded `chart.js` from version `4.2.0` to `4.4.7` +- Upgraded `chartjs-chart-treemap` from version `2.3.1` to `3.1.0` +- Upgraded `chartjs-plugin-annotation` from version `2.1.2` to `3.1.0` - Upgraded `eslint` dependencies - Upgraded `nestjs` from version `10.1.3` to `10.4.15` +- Upgraded `uuid` from version `11.0.2` to `11.0.5` ## 2.134.0 - 2025-01-15 diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index af8a9e9a2..386bcc8d4 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -11,6 +11,7 @@ import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; import { DataProviderInfo, @@ -27,7 +28,14 @@ import { } from '@prisma/client'; import { isISIN } from 'class-validator'; import { countries } from 'countries-list'; -import { format, isAfter, isBefore, isSameDay } from 'date-fns'; +import { + addDays, + format, + isAfter, + isBefore, + isSameDay, + parseISO +} from 'date-fns'; @Injectable() export class FinancialModelingPrepService implements DataProviderInterface { @@ -58,7 +66,10 @@ export class FinancialModelingPrepService implements DataProviderInterface { }; try { - if (this.cryptocurrencyService.isCryptocurrency(symbol)) { + if ( + symbol.endsWith(DEFAULT_CURRENCY) && + this.cryptocurrencyService.isCryptocurrency(symbol) + ) { const [quote] = await fetch( `${this.URL}/quote/${symbol}?apikey=${this.apiKey}`, { @@ -208,8 +219,54 @@ export class FinancialModelingPrepService implements DataProviderInterface { }; } - public async getDividends({}: GetDividendsParams) { - return {}; + public async getDividends({ + from, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), + symbol, + to + }: GetDividendsParams) { + if (isSameDay(from, to)) { + to = addDays(to, 1); + } + + try { + const response: { + [date: string]: IDataProviderHistoricalResponse; + } = {}; + + const { historical } = await fetch( + `${this.URL}/historical-price-full/stock_dividend/${symbol}?apikey=${this.apiKey}`, + { + signal: AbortSignal.timeout(requestTimeout) + } + ).then((res) => res.json()); + + historical + .filter(({ date }) => { + return ( + (isSameDay(parseISO(date), from) || + isAfter(parseISO(date), from)) && + isBefore(parseISO(date), to) + ); + }) + .forEach(({ adjDividend, date }) => { + response[date] = { + marketPrice: adjDividend + }; + }); + + return response; + } catch (error) { + Logger.error( + `Could not get dividends for ${symbol} (${this.getName()}) from ${format( + from, + DATE_FORMAT + )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}`, + 'FinancialModelingPrepService' + ); + + return {}; + } } public async getHistorical({ @@ -234,14 +291,14 @@ export class FinancialModelingPrepService implements DataProviderInterface { [symbol]: {} }; - for (const { close, date } of historical) { + for (const { adjClose, date } of historical) { if ( (isSameDay(parseDate(date), from) || isAfter(parseDate(date), from)) && isBefore(parseDate(date), to) ) { result[symbol][date] = { - marketPrice: close + marketPrice: adjClose }; } } @@ -375,7 +432,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { return `https://financialmodelingprep.com/api/v${version}`; } - public parseAssetClass(profile: any): { + private parseAssetClass(profile: any): { assetClass: AssetClass; assetSubClass: AssetSubClass; } { diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index 7f914870b..6319c3cd7 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -77,7 +77,7 @@ export class GfPortfolioProportionChartComponent @ViewChild('chartCanvas') chartCanvas: ElementRef; - public chart: Chart<'pie'>; + public chart: Chart<'doughnut'>; public isLoading = true; private readonly OTHER_KEY = 'OTHER'; @@ -257,7 +257,7 @@ export class GfPortfolioProportionChartComponent }); }); - const datasets: ChartConfiguration['data']['datasets'] = [ + const datasets: ChartConfiguration<'doughnut'>['data']['datasets'] = [ { backgroundColor: chartDataSorted.map(([, item]) => { return item.color; @@ -295,7 +295,7 @@ export class GfPortfolioProportionChartComponent datasets[1].data[1] = Number.MAX_SAFE_INTEGER; } - const data: ChartConfiguration['data'] = { + const data: ChartConfiguration<'doughnut'>['data'] = { datasets, labels }; @@ -308,7 +308,7 @@ export class GfPortfolioProportionChartComponent ) as unknown; this.chart.update(); } else { - this.chart = new Chart(this.chartCanvas.nativeElement, { + this.chart = new Chart<'doughnut'>(this.chartCanvas.nativeElement, { data, options: { animation: false, diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index 11ee026f5..4c3167c9e 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -196,7 +196,7 @@ export class GfTreemapChartComponent min: Math.min(...negativeNetPerformancePercents) }; - const data: ChartConfiguration['data'] = { + const data: ChartConfiguration<'treemap'>['data'] = { datasets: [ { backgroundColor: (ctx) => { diff --git a/package-lock.json b/package-lock.json index 68a6906d9..0190bfeaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,10 +50,10 @@ "bull": "4.16.4", "cache-manager": "5.7.6", "cache-manager-redis-yet": "5.1.4", - "chart.js": "4.2.0", + "chart.js": "4.4.7", "chartjs-adapter-date-fns": "3.0.0", - "chartjs-chart-treemap": "2.3.1", - "chartjs-plugin-annotation": "2.1.2", + "chartjs-chart-treemap": "3.1.0", + "chartjs-plugin-annotation": "3.1.0", "chartjs-plugin-datalabels": "2.2.0", "cheerio": "1.0.0", "class-transformer": "0.5.1", @@ -88,7 +88,7 @@ "stripe": "17.3.0", "svgmap": "2.6.0", "twitter-api-v2": "1.14.2", - "uuid": "11.0.2", + "uuid": "11.0.5", "yahoo-finance2": "2.11.3", "zone.js": "0.15.0" }, @@ -13880,15 +13880,15 @@ "license": "MIT" }, "node_modules/chart.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.0.tgz", - "integrity": "sha512-wbtcV+QKeH0F7gQZaCJEIpsNriFheacouJQTVIjITi3eQA8bTlIBoknz0+dgV79aeKLNMAX+nDslIVE/nJ3rzA==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.7.tgz", + "integrity": "sha512-pwkcKfdzTMAU/+jNosKhNL2bHtJc/sSmYgVbuGTEDhzkrhmyihmP7vUc/5ZK9WopidMDHNe3Wm7jOd/WhuHWuw==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" }, "engines": { - "pnpm": "^7.0.0" + "pnpm": ">=8" } }, "node_modules/chartjs-adapter-date-fns": { @@ -13902,21 +13902,21 @@ } }, "node_modules/chartjs-chart-treemap": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/chartjs-chart-treemap/-/chartjs-chart-treemap-2.3.1.tgz", - "integrity": "sha512-GW+iODLICIJhNZtHbTtaOjCwRIxmXcquXRKDFMsrkXyqyDeSN1aiVfzNNj6Xjy55soopqRA+YfHqjT2S2zF7lQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/chartjs-chart-treemap/-/chartjs-chart-treemap-3.1.0.tgz", + "integrity": "sha512-0LJxj4J9sCTHmrXCFlqtoBKMJDcS7VzFeRgNBRZRwU1QSpCXJKTNk5TysPEs5/YW0XYvZoN8u44RqqLf0pAzQw==", "license": "MIT", "peerDependencies": { "chart.js": ">=3.0.0" } }, "node_modules/chartjs-plugin-annotation": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-2.1.2.tgz", - "integrity": "sha512-kmEp2WtpogwnKKnDPO3iO3mVwvVGtmG5BkZVtAEZm5YzJ9CYxojjYEgk7OTrFbJ5vU098b84UeJRe8kRfNcq5g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-annotation/-/chartjs-plugin-annotation-3.1.0.tgz", + "integrity": "sha512-EkAed6/ycXD/7n0ShrlT1T2Hm3acnbFhgkIEJLa0X+M6S16x0zwj1Fv4suv/2bwayCT3jGPdAtI9uLcAMToaQQ==", "license": "MIT", "peerDependencies": { - "chart.js": ">=3.7.0" + "chart.js": ">=4.0.0" } }, "node_modules/chartjs-plugin-datalabels": { @@ -31181,9 +31181,9 @@ } }, "node_modules/uuid": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", - "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/package.json b/package.json index 7bc964055..c75b630c8 100644 --- a/package.json +++ b/package.json @@ -96,10 +96,10 @@ "bull": "4.16.4", "cache-manager": "5.7.6", "cache-manager-redis-yet": "5.1.4", - "chart.js": "4.2.0", + "chart.js": "4.4.7", "chartjs-adapter-date-fns": "3.0.0", - "chartjs-chart-treemap": "2.3.1", - "chartjs-plugin-annotation": "2.1.2", + "chartjs-chart-treemap": "3.1.0", + "chartjs-plugin-annotation": "3.1.0", "chartjs-plugin-datalabels": "2.2.0", "cheerio": "1.0.0", "class-transformer": "0.5.1", @@ -134,7 +134,7 @@ "stripe": "17.3.0", "svgmap": "2.6.0", "twitter-api-v2": "1.14.2", - "uuid": "11.0.2", + "uuid": "11.0.5", "yahoo-finance2": "2.11.3", "zone.js": "0.15.0" },