From 2ad91e5796581882bdf9d8090b8414a24d16be31 Mon Sep 17 00:00:00 2001 From: ceroma <678940+ceroma@users.noreply.github.com> Date: Wed, 2 Oct 2024 06:59:57 -0300 Subject: [PATCH 01/30] Feature/optimize portfolio calculations with smarter date interval selection (#3829) * Optimize portfolio calculations with smarter date interval selection * Update changelog --- CHANGELOG.md | 1 + .../calculator/twr/portfolio-calculator.ts | 42 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78862da6d..92f82935a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Optimized the portfolio calculations with smarter date interval selection - Improved the language localization for German (`de`) ## 2.111.0 - 2024-09-28 diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index 3b64cd185..f5e301cba 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -16,13 +16,14 @@ import { addDays, addMilliseconds, differenceInDays, - eachDayOfInterval, format, isBefore } from 'date-fns'; import { cloneDeep, first, last, sortBy } from 'lodash'; export class TWRPortfolioCalculator extends PortfolioCalculator { + private chartDatesDescending: string[]; + protected calculateOverallPerformance( positions: TimelinePosition[] ): PortfolioSnapshot { @@ -820,31 +821,35 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { startDate = start; } + const endDateString = format(endDate, DATE_FORMAT); + const startDateString = format(startDate, DATE_FORMAT); + const currentValuesAtDateRangeStartWithCurrencyEffect = - currentValuesWithCurrencyEffect[format(startDate, DATE_FORMAT)] ?? - new Big(0); + currentValuesWithCurrencyEffect[startDateString] ?? new Big(0); const investmentValuesAccumulatedAtStartDateWithCurrencyEffect = - investmentValuesAccumulatedWithCurrencyEffect[ - format(startDate, DATE_FORMAT) - ] ?? new Big(0); + investmentValuesAccumulatedWithCurrencyEffect[startDateString] ?? + new Big(0); const grossPerformanceAtDateRangeStartWithCurrencyEffect = currentValuesAtDateRangeStartWithCurrencyEffect.minus( investmentValuesAccumulatedAtStartDateWithCurrencyEffect ); - const dates = eachDayOfInterval({ - end: endDate, - start: startDate - }).map((date) => { - return format(date, DATE_FORMAT); - }); - let average = new Big(0); let dayCount = 0; - for (const date of dates) { + if (!this.chartDatesDescending) { + this.chartDatesDescending = Object.keys(chartDateMap).sort().reverse(); + } + + for (const date of this.chartDatesDescending) { + if (date > endDateString) { + continue; + } else if (date < startDateString) { + break; + } + if ( investmentValuesAccumulatedWithCurrencyEffect[date] instanceof Big && investmentValuesAccumulatedWithCurrencyEffect[date].gt(0) @@ -864,17 +869,14 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { } netPerformanceWithCurrencyEffectMap[dateRange] = - netPerformanceValuesWithCurrencyEffect[ - format(endDate, DATE_FORMAT) - ]?.minus( + netPerformanceValuesWithCurrencyEffect[endDateString]?.minus( // If the date range is 'max', take 0 as a start value. Otherwise, // the value of the end of the day of the start date is taken which // differs from the buying price. dateRange === 'max' ? new Big(0) - : (netPerformanceValuesWithCurrencyEffect[ - format(startDate, DATE_FORMAT) - ] ?? new Big(0)) + : (netPerformanceValuesWithCurrencyEffect[startDateString] ?? + new Big(0)) ) ?? new Big(0); netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0) From 7f6b8145d7435c8a08a4abe5f8fa74720d443696 Mon Sep 17 00:00:00 2001 From: Prakhar Sharma <87238338+Prakhar29Sharma@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:57:55 +0530 Subject: [PATCH 02/30] Feature/set up output for click in holdings table component (#3851) * Set up @Output for click in holdings table component --- .../home-holdings/home-holdings.component.ts | 2 +- .../components/home-holdings/home-holdings.html | 3 ++- .../lib/holdings-table/holdings-table.component.ts | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/client/src/app/components/home-holdings/home-holdings.component.ts b/apps/client/src/app/components/home-holdings/home-holdings.component.ts index 4f7de03b5..c76638d33 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.component.ts +++ b/apps/client/src/app/components/home-holdings/home-holdings.component.ts @@ -111,7 +111,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit { this.initialize(); } - public onSymbolClicked({ dataSource, symbol }: AssetProfileIdentifier) { + public onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) { if (dataSource && symbol) { this.router.navigate([], { queryParams: { dataSource, symbol, holdingDetailDialog: true } diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index bd9e57bb2..01afe31a2 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -40,7 +40,7 @@ cursor="pointer" [dateRange]="user?.settings?.dateRange" [holdings]="holdings" - (treemapChartClicked)="onSymbolClicked($event)" + (treemapChartClicked)="onHoldingClicked($event)" /> }
@@ -50,6 +50,7 @@ [hasPermissionToCreateActivity]="hasPermissionToCreateOrder" [holdings]="holdings" [locale]="user?.settings?.locale" + (holdingClicked)="onHoldingClicked($event)" /> @if (hasPermissionToCreateOrder && holdings?.length > 0) {
diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 39a9baf5c..53e17e5b7 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -14,10 +14,12 @@ import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, + EventEmitter, Input, OnChanges, OnDestroy, OnInit, + Output, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; @@ -25,7 +27,6 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; -import { Router, RouterModule } from '@angular/router'; import { AssetSubClass } from '@prisma/client'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject, Subscription } from 'rxjs'; @@ -44,8 +45,7 @@ import { Subject, Subscription } from 'rxjs'; MatPaginatorModule, MatSortModule, MatTableModule, - NgxSkeletonLoaderModule, - RouterModule + NgxSkeletonLoaderModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-holdings-table', @@ -63,6 +63,8 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit { @Input() locale = getLocale(); @Input() pageSize = Number.MAX_SAFE_INTEGER; + @Output() holdingClicked = new EventEmitter(); + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; @@ -75,7 +77,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit { private unsubscribeSubject = new Subject(); - public constructor(private router: Router) {} + public constructor() {} public ngOnInit() {} @@ -107,9 +109,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit { public onOpenHoldingDialog({ dataSource, symbol }: AssetProfileIdentifier) { if (this.hasPermissionToOpenDetails) { - this.router.navigate([], { - queryParams: { dataSource, symbol, holdingDetailDialog: true } - }); + this.holdingClicked.emit({ dataSource, symbol }); } } From 8364f7f703d773c84ad6555e87017383fc1168c2 Mon Sep 17 00:00:00 2001 From: Dmytro Werner <68282006+Yordaniss@users.noreply.github.com> Date: Wed, 2 Oct 2024 21:15:10 +0200 Subject: [PATCH 03/30] Feature/set up output for click in activities table component (#3856) * Set up @output for click in activities table component --- .../activities/activities-page.component.ts | 12 +++++++++++- .../portfolio/activities/activities-page.html | 1 + .../activities-table.component.ts | 18 ++++-------------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index a7638c561..427637c2c 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -7,7 +7,7 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config'; import { downloadAsFile } from '@ghostfolio/common/helper'; -import { User } from '@ghostfolio/common/interfaces'; +import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; @@ -138,6 +138,16 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit { this.fetchActivities(); } + public onClickActivity({ dataSource, symbol }: AssetProfileIdentifier) { + this.router.navigate([], { + queryParams: { + dataSource, + symbol, + holdingDetailDialog: true + } + }); + } + public onCloneActivity(aActivity: Activity) { this.openCreateActivityDialog(aActivity); } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.html b/apps/client/src/app/pages/portfolio/activities/activities-page.html index 9edb400ab..c06f7dd75 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.html +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.html @@ -21,6 +21,7 @@ [sortDirection]="sortDirection" [totalItems]="totalItems" (activitiesDeleted)="onDeleteActivities()" + (activityClicked)="onClickActivity($event)" (activityDeleted)="onDeleteActivity($event)" (activityToClone)="onCloneActivity($event)" (activityToUpdate)="onUpdateActivity($event)" diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index 482305bb7..4f8fff904 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -42,7 +42,6 @@ import { } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { Router, RouterModule } from '@angular/router'; import { isUUID } from 'class-validator'; import { endOfToday, isAfter } from 'date-fns'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @@ -64,8 +63,7 @@ import { Subject, Subscription, takeUntil } from 'rxjs'; MatSortModule, MatTableModule, MatTooltipModule, - NgxSkeletonLoaderModule, - RouterModule + NgxSkeletonLoaderModule ], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-activities-table', @@ -95,6 +93,7 @@ export class GfActivitiesTableComponent @Input() totalItems = Number.MAX_SAFE_INTEGER; @Output() activitiesDeleted = new EventEmitter(); + @Output() activityClicked = new EventEmitter(); @Output() activityDeleted = new EventEmitter(); @Output() activityToClone = new EventEmitter(); @Output() activityToUpdate = new EventEmitter(); @@ -122,10 +121,7 @@ export class GfActivitiesTableComponent private unsubscribeSubject = new Subject(); - public constructor( - private notificationService: NotificationService, - private router: Router - ) {} + public constructor(private notificationService: NotificationService) {} public ngOnInit() { if (this.showCheckbox) { @@ -203,7 +199,7 @@ export class GfActivitiesTableComponent activity.isDraft === false && ['BUY', 'DIVIDEND', 'SELL'].includes(activity.type) ) { - this.onOpenPositionDialog({ + this.activityClicked.emit({ dataSource: activity.SymbolProfile.dataSource, symbol: activity.SymbolProfile.symbol }); @@ -268,12 +264,6 @@ export class GfActivitiesTableComponent }); } - public onOpenPositionDialog({ dataSource, symbol }: AssetProfileIdentifier) { - this.router.navigate([], { - queryParams: { dataSource, symbol, holdingDetailDialog: true } - }); - } - public onUpdateActivity(aActivity: OrderWithAccount) { this.activityToUpdate.emit(aActivity); } From 85a7838a96257916df2f4b4e774fd8693d032f7e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 3 Oct 2024 20:14:06 +0200 Subject: [PATCH 04/30] Feature/show message if no results have been found in activity dialog (#3854) * Show message if no results have been found in activity dialog * Update changelog --------- Signed-off-by: Dominik Willner Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/client/src/locales/messages.ca.xlf | 8 ++++++++ apps/client/src/locales/messages.de.xlf | 8 ++++++++ apps/client/src/locales/messages.es.xlf | 8 ++++++++ apps/client/src/locales/messages.fr.xlf | 8 ++++++++ apps/client/src/locales/messages.it.xlf | 8 ++++++++ apps/client/src/locales/messages.nl.xlf | 8 ++++++++ apps/client/src/locales/messages.pl.xlf | 8 ++++++++ apps/client/src/locales/messages.pt.xlf | 8 ++++++++ apps/client/src/locales/messages.tr.xlf | 8 ++++++++ apps/client/src/locales/messages.xlf | 7 +++++++ apps/client/src/locales/messages.zh.xlf | 8 ++++++++ .../symbol-autocomplete.component.html | 6 ++++++ .../symbol-autocomplete/symbol-autocomplete.component.ts | 6 +++--- 14 files changed, 97 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92f82935a..bb824f43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a message to the search asset component if no results have been found in the create or update activity dialog - Added support to customize the rule thresholds in the _X-ray_ section (experimental) ### Changed diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 597b88260..4be556c77 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 6929db1ef..dabb2bb66 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Ups! Es konnten leider keine Assets gefunden werden. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index b5d7f41f9..1cdc11f5f 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -7259,6 +7259,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index e880df353..7e37f0d8c 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index a2c1f0cb2..3ca902aed 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -7259,6 +7259,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index e686b6559..c768cf1b9 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index a585e4b9d..4e4e51635 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index e0ff5f7e7..b28898470 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 575dcf12b..1a47fecd9 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -7258,6 +7258,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 735554838..5cb3a8894 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -6565,6 +6565,13 @@ 49 + + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index f4e75b333..2c7c1e47c 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -7259,6 +7259,14 @@ 280 + + Oops! Could not find any assets. + Oops! Could not find any assets. + + libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html + 16 + + diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html index 2a2f98d7d..cac15c346 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html @@ -31,6 +31,12 @@ } + } @empty { + @if (control.value?.length > 1) { + Oops! Could not find any assets. + + } } } diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index 8e07ed674..da97aac05 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -117,17 +117,17 @@ export class GfSymbolAutocompleteComponent this.control.valueChanges .pipe( - debounceTime(400), - distinctUntilChanged(), filter((query) => { return isString(query) && query.length > 1; }), - takeUntil(this.unsubscribeSubject), tap(() => { this.isLoading = true; this.changeDetectorRef.markForCheck(); }), + debounceTime(400), + distinctUntilChanged(), + takeUntil(this.unsubscribeSubject), switchMap((query: string) => { return this.dataService.fetchSymbols({ query, From 413076141c655f1545f59159818c9704fd4df556 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:18:26 +0200 Subject: [PATCH 05/30] Bugfix/fix calculation of allocations by market (#3853) * Fix calculation of allocations by market (unknown) * Update changelog --- CHANGELOG.md | 4 +++ .../src/app/portfolio/portfolio.service.ts | 30 +++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb824f43b..9ba9ee7a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Optimized the portfolio calculations with smarter date interval selection - Improved the language localization for German (`de`) +### Fixed + +- Fixed an issue in the calculation of allocations by market (_Unknown_) + ## 2.111.0 - 2024-09-28 ### Added diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index c2c867f61..fc06545a5 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -470,8 +470,7 @@ export class PortfolioService { if (withMarkets) { ({ markets, marketsAdvanced } = this.getMarkets({ - assetProfile, - valueInBaseCurrency + assetProfile })); } @@ -1433,11 +1432,9 @@ export class PortfolioService { } private getMarkets({ - assetProfile, - valueInBaseCurrency + assetProfile }: { assetProfile: EnhancedSymbolProfile; - valueInBaseCurrency: Big; }) { const markets = { [UNKNOWN_KEY]: 0, @@ -1499,16 +1496,23 @@ export class PortfolioService { .toNumber(); } } - } else { - markets[UNKNOWN_KEY] = new Big(markets[UNKNOWN_KEY]) - .plus(valueInBaseCurrency) - .toNumber(); - - marketsAdvanced[UNKNOWN_KEY] = new Big(marketsAdvanced[UNKNOWN_KEY]) - .plus(valueInBaseCurrency) - .toNumber(); } + markets[UNKNOWN_KEY] = new Big(1) + .minus(markets.developedMarkets) + .minus(markets.emergingMarkets) + .minus(markets.otherMarkets) + .toNumber(); + + marketsAdvanced[UNKNOWN_KEY] = new Big(1) + .minus(marketsAdvanced.asiaPacific) + .minus(marketsAdvanced.emergingMarkets) + .minus(marketsAdvanced.europe) + .minus(marketsAdvanced.japan) + .minus(marketsAdvanced.northAmerica) + .minus(marketsAdvanced.otherMarkets) + .toNumber(); + return { markets, marketsAdvanced }; } From 9b8f552ee2950859bbff482e312670ccbc8074e8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:35:34 +0200 Subject: [PATCH 06/30] Feature/improve language localization for de 20241003 (#3863) * Update translations --- apps/client/src/locales/messages.ca.xlf | 16 ++++++++-------- apps/client/src/locales/messages.de.xlf | 16 ++++++++-------- apps/client/src/locales/messages.es.xlf | 16 ++++++++-------- apps/client/src/locales/messages.fr.xlf | 16 ++++++++-------- apps/client/src/locales/messages.it.xlf | 16 ++++++++-------- apps/client/src/locales/messages.nl.xlf | 16 ++++++++-------- apps/client/src/locales/messages.pl.xlf | 16 ++++++++-------- apps/client/src/locales/messages.pt.xlf | 16 ++++++++-------- apps/client/src/locales/messages.tr.xlf | 16 ++++++++-------- apps/client/src/locales/messages.xlf | 16 ++++++++-------- apps/client/src/locales/messages.zh.xlf | 16 ++++++++-------- .../symbol-autocomplete.component.html | 4 ++-- 12 files changed, 90 insertions(+), 90 deletions(-) diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 4be556c77..06f8a5647 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -1471,7 +1471,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -2635,7 +2635,7 @@ Gestionar Activitats apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -5979,7 +5979,7 @@ Do you really want to delete these activities? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -5987,7 +5987,7 @@ Do you really want to delete this activity? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index dabb2bb66..62302249d 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -554,7 +554,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1058,7 +1058,7 @@ Aktivitäten verwalten apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -2550,7 +2550,7 @@ Möchtest du diese Aktivität wirklich löschen? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -3914,7 +3914,7 @@ Möchtest du diese Aktivitäten wirklich löschen? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Schliessen apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Ups! Es konnten leider keine Assets gefunden werden. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 1cdc11f5f..1e897f596 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -555,7 +555,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1059,7 +1059,7 @@ Gestión de las operaciones apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -2551,7 +2551,7 @@ ¿Estás seguro de eliminar esta operación? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -3915,7 +3915,7 @@ Do you really want to delete these activities? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -7056,7 +7056,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7064,7 +7064,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7072,7 +7072,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7264,7 +7264,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index 7e37f0d8c..aa366fcc4 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -614,7 +614,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1378,7 +1378,7 @@ Gérer les Activités apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -3070,7 +3070,7 @@ Voulez-vous vraiment supprimer cette activité ? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -3914,7 +3914,7 @@ Voulez-vous vraiment supprimer toutes vos activités ? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 3ca902aed..a9d6df210 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -555,7 +555,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1059,7 +1059,7 @@ Gestione delle attività apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -2551,7 +2551,7 @@ Vuoi davvero eliminare questa attività? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -3915,7 +3915,7 @@ Vuoi davvero eliminare tutte le tue attività? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -7056,7 +7056,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7064,7 +7064,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7072,7 +7072,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7264,7 +7264,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index c768cf1b9..441455ef8 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -554,7 +554,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1058,7 +1058,7 @@ Activiteiten beheren apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -2550,7 +2550,7 @@ Wil je deze activiteit echt verwijderen? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -3914,7 +3914,7 @@ Wil je echt al je activiteiten verwijderen? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 4e4e51635..e68b07f45 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -1363,7 +1363,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -2283,7 +2283,7 @@ Zarządzaj Aktywnościami apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -4151,7 +4151,7 @@ Do you really want to delete these activities? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -5503,7 +5503,7 @@ Czy na pewno chcesz usunąć tę działalność? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index b28898470..8bb514d06 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -614,7 +614,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1242,7 +1242,7 @@ Gerir Atividades apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -2942,7 +2942,7 @@ Deseja realmente eliminar esta atividade? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -3914,7 +3914,7 @@ Deseja mesmo eliminar estas atividades? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 1a47fecd9..e5292e342 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -1327,7 +1327,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -2135,7 +2135,7 @@ İşlemleri Yönet apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -3663,7 +3663,7 @@ Tüm işlemlerinizi silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -5207,7 +5207,7 @@ TBu işlemi silmeyi gerçekten istiyor musunuz? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -7055,7 +7055,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7063,7 +7063,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7071,7 +7071,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7263,7 +7263,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index 5cb3a8894..849b1053e 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -1312,7 +1312,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -2151,7 +2151,7 @@ Manage Activities apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -3825,7 +3825,7 @@ Do you really want to delete these activities? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -5064,7 +5064,7 @@ Do you really want to delete this activity? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -6359,7 +6359,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -6380,7 +6380,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -6408,7 +6408,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -6569,7 +6569,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 2c7c1e47c..22aa783d6 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -1372,7 +1372,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 21 + 35 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -2300,7 +2300,7 @@ 管理活动 apps/client/src/app/components/home-holdings/home-holdings.html - 61 + 62 @@ -4168,7 +4168,7 @@ 您真的要删除所有活动吗? libs/ui/src/lib/activities-table/activities-table.component.ts - 223 + 219 @@ -5552,7 +5552,7 @@ 您确实要删除此活动吗? libs/ui/src/lib/activities-table/activities-table.component.ts - 233 + 229 @@ -7056,7 +7056,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 5 + 9 @@ -7064,7 +7064,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 22 @@ -7072,7 +7072,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 15 + 33 @@ -7264,7 +7264,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 16 + 37 diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html index cac15c346..d055a618a 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html @@ -34,8 +34,8 @@ } @empty { @if (control.value?.length > 1) { Oops! Could not find any assets. - + >Oops! Could not find any assets. } } } From dd28f38e60b8ab9b2f846c8ba6144dc468849bc7 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 3 Oct 2024 20:39:56 +0200 Subject: [PATCH 07/30] Bugfix/fix eslint configuration (#3852) * Fix eslint configuration * Update changelog --------- Signed-off-by: Dominik Willner --- .eslintrc.json | 82 +++++++------------ CHANGELOG.md | 1 + .../src/app/pages/landing/landing-page.html | 2 +- .../lib/carousel/carousel-item.directive.ts | 4 +- .../fire-calculator.service.ts | 2 +- .../holdings-table.component.ts | 4 +- .../src/lib/shared/abstract-mat-form-field.ts | 1 + .../top-holdings/top-holdings.component.ts | 6 +- 8 files changed, 39 insertions(+), 63 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1b7628ebf..fc591aab6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,7 +7,7 @@ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": { "@nx/enforce-module-boundaries": [ - "error", + "warn", { "enforceBuildableLibDependency": true, "allow": [], @@ -18,30 +18,24 @@ } ] } - ] + ], + "@typescript-eslint/no-extra-semi": "error", + "no-extra-semi": "off" } }, { "files": ["*.ts", "*.tsx"], - "extends": ["plugin:@nx/typescript"], - "rules": { - "@typescript-eslint/no-extra-semi": "error", - "no-extra-semi": "off" - } + "extends": ["plugin:@nx/typescript"] }, { "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nx/javascript"], - "rules": { - "@typescript-eslint/no-extra-semi": "error", - "no-extra-semi": "off" - } + "extends": ["plugin:@nx/javascript"] }, { "files": ["*.ts"], "plugins": ["eslint-plugin-import", "@typescript-eslint"], "rules": { - "@typescript-eslint/consistent-type-definitions": "error", + "@typescript-eslint/consistent-type-definitions": "warn", "@typescript-eslint/dot-notation": "off", "@typescript-eslint/explicit-member-accessibility": [ "off", @@ -49,76 +43,62 @@ "accessibility": "explicit" } ], - "@typescript-eslint/member-ordering": "error", + "@typescript-eslint/member-ordering": "warn", "@typescript-eslint/naming-convention": "off", "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-empty-interface": "error", + "@typescript-eslint/no-empty-interface": "warn", "@typescript-eslint/no-inferrable-types": [ - "error", + "warn", { "ignoreParameters": true } ], "@typescript-eslint/no-misused-new": "error", - "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-shadow": [ - "error", + "warn", { "hoist": "all" } ], - "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/prefer-function-type": "error", + "@typescript-eslint/no-unused-expressions": "warn", + "@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/unified-signatures": "error", + "@typescript-eslint/no-loss-of-precision": "warn", + "@typescript-eslint/no-var-requires": "warn", + "@typescript-eslint/ban-ts-comment": "warn", + "@typescript-eslint/ban-types": "warn", "arrow-body-style": "off", "constructor-super": "error", "eqeqeq": ["error", "smart"], - "guard-for-in": "error", + "guard-for-in": "warn", "id-blacklist": "off", "id-match": "off", "import/no-deprecated": "warn", "no-bitwise": "error", "no-caller": "error", - "no-console": [ - "error", - { - "allow": [ - "log", - "warn", - "dir", - "timeLog", - "assert", - "clear", - "count", - "countReset", - "group", - "groupEnd", - "table", - "dirxml", - "error", - "groupCollapsed", - "Console", - "profile", - "profileEnd", - "timeStamp", - "context" - ] - } - ], "no-debugger": "error", "no-empty": "off", "no-eval": "error", "no-fallthrough": "error", "no-new-wrappers": "error", "no-restricted-imports": ["error", "rxjs/Rx"], - "no-throw-literal": "error", + "no-throw-literal": "warn", "no-undef-init": "error", "no-underscore-dangle": "off", "no-var": "error", - "prefer-const": "error", - "radix": "error" + "prefer-const": "warn", + "radix": "error", + "no-unsafe-optional-chaining": "warn", + "no-extra-boolean-cast": "warn", + "no-empty-pattern": "warn", + "no-useless-catch": "warn", + "no-unsafe-finally": "warn", + "no-prototype-builtins": "warn", + "no-async-promise-executor": "warn", + "no-constant-condition": "warn" } } ], - "extends": [null, "plugin:storybook/recommended"] + "extends": ["plugin:storybook/recommended"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba9ee7a1..3f943048a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed an issue in the calculation of allocations by market (_Unknown_) +- Fixed the `eslint` configuration ## 2.111.0 - 2024-09-28 diff --git a/apps/client/src/app/pages/landing/landing-page.html b/apps/client/src/app/pages/landing/landing-page.html index f726a6020..7ce938227 100644 --- a/apps/client/src/app/pages/landing/landing-page.html +++ b/apps/client/src/app/pages/landing/landing-page.html @@ -331,7 +331,7 @@
@for (testimonial of testimonials; track testimonial) { -
+
) {} } diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.service.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.service.ts index 20247ef90..8a5e3afb9 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.service.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.service.ts @@ -52,7 +52,7 @@ export class FireCalculatorService { r: number; totalAmount: number; }) { - if (r == 0) { + if (r === 0) { // No compound interest return (totalAmount - P) / PMT; } else if (totalAmount <= P) { diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 53e17e5b7..1f44cf405 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -53,7 +53,7 @@ import { Subject, Subscription } from 'rxjs'; styleUrls: ['./holdings-table.component.scss'], templateUrl: './holdings-table.component.html' }) -export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit { +export class GfHoldingsTableComponent implements OnChanges, OnDestroy { @Input() baseCurrency: string; @Input() deviceType: string; @Input() hasPermissionToCreateActivity: boolean; @@ -79,8 +79,6 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit { public constructor() {} - public ngOnInit() {} - public ngOnChanges() { this.displayedColumns = ['icon', 'nameWithSymbol', 'dateOfFirstActivity']; diff --git a/libs/ui/src/lib/shared/abstract-mat-form-field.ts b/libs/ui/src/lib/shared/abstract-mat-form-field.ts index 460b6969d..05491b8ab 100644 --- a/libs/ui/src/lib/shared/abstract-mat-form-field.ts +++ b/libs/ui/src/lib/shared/abstract-mat-form-field.ts @@ -16,6 +16,7 @@ import { Subject } from 'rxjs'; @Component({ template: '' }) +// eslint-disable-next-line @angular-eslint/component-class-suffix export abstract class AbstractMatFormField implements ControlValueAccessor, DoCheck, MatFormFieldControl, OnDestroy { diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts index 6f7695687..38ef3524d 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts @@ -38,7 +38,7 @@ import { Subject } from 'rxjs'; styleUrls: ['./top-holdings.component.scss'], templateUrl: './top-holdings.component.html' }) -export class GfTopHoldingsComponent implements OnChanges, OnDestroy, OnInit { +export class GfTopHoldingsComponent implements OnChanges, OnDestroy { @Input() baseCurrency: string; @Input() locale = getLocale(); @Input() pageSize = Number.MAX_SAFE_INTEGER; @@ -57,10 +57,6 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy, OnInit { private unsubscribeSubject = new Subject(); - public constructor() {} - - public ngOnInit() {} - public ngOnChanges() { this.isLoading = true; From c4742d3a53631ff8acf6241ee186a1474bb369c1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 3 Oct 2024 20:43:04 +0200 Subject: [PATCH 08/30] Release 2.112.0 (#3864) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f943048a..88d89667b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.112.0 - 2024-10-03 ### Added diff --git a/package-lock.json b/package-lock.json index b663482d4..b848e8188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.110.0", + "version": "2.112.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.110.0", + "version": "2.112.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index de40eb8fe..584407f1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.111.0", + "version": "2.112.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 252fb3fe11465476b6a3c3a3e05aa4903db7f465 Mon Sep 17 00:00:00 2001 From: dandevaud <50833091+dandevaud@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:49:03 +0200 Subject: [PATCH 09/30] Bugfix/Handle exception in historical market data gathering of derived currencies (#3858) * Handle exception in historical market data gathering of derived currencies * Update changelog --------- Co-authored-by: Dan --- CHANGELOG.md | 6 ++++++ .../services/data-provider/data-provider.service.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88d89667b..00021fbfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +- Handled an exception in the historical market data gathering of derived currencies + ## 2.112.0 - 2024-10-03 ### Added diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index e5eda2d7e..bd20541af 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -666,9 +666,13 @@ export class DataProviderService { } = {}; for (const date in rootData) { - data[date] = { - marketPrice: new Big(factor).mul(rootData[date].marketPrice).toNumber() - }; + if (isNumber(rootData[date].marketPrice)) { + data[date] = { + marketPrice: new Big(factor) + .mul(rootData[date].marketPrice) + .toNumber() + }; + } } return data; From bcc8cb1e5b7276c1f8af07d4abd8fea16406effa Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:44:07 +0200 Subject: [PATCH 10/30] Feature/modernize rules implementation (#3869) * Modernize rules implementation * Include markets in details --- .../src/app/portfolio/portfolio.controller.ts | 2 - .../src/app/portfolio/portfolio.service.ts | 172 +++++++++++------- apps/api/src/models/rule.ts | 19 +- .../base-currency-current-investment.ts | 19 +- .../current-investment.ts | 17 +- .../rule-settings-dialog.component.ts | 2 - .../interfaces/portfolio-details.interface.ts | 7 + 7 files changed, 143 insertions(+), 95 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 9f5635cf5..7c11e4767 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -1,4 +1,3 @@ -import { AccessService } from '@ghostfolio/api/app/access/access.service'; import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; @@ -61,7 +60,6 @@ import { UpdateHoldingTagsDto } from './update-holding-tags.dto'; @Controller('portfolio') export class PortfolioController { public constructor( - private readonly accessService: AccessService, private readonly apiService: ApiService, private readonly configurationService: ConfigurationService, private readonly impersonationService: ImpersonationService, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index fc06545a5..6879b2030 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -45,6 +45,7 @@ import type { AccountWithValue, DateRange, GroupBy, + Market, RequestWithUser, UserWithSettings } from '@ghostfolio/common/types'; @@ -581,6 +582,17 @@ export class PortfolioService { }; } + let markets: { + [key in Market]: { + name: string; + value: number; + }; + }; + + if (withMarkets) { + markets = this.getAggregatedMarkets(holdings); + } + let summary: PortfolioSummary; if (withSummary) { @@ -602,6 +614,7 @@ export class PortfolioService { accounts, hasErrors, holdings, + markets, platforms, summary }; @@ -1148,74 +1161,49 @@ export class PortfolioService { public async getReport(impersonationId: string): Promise { const userId = await this.getUserId(impersonationId, this.request.user.id); - const user = await this.userService.user({ id: userId }); - const userCurrency = this.getUserCurrency(user); - - const { activities } = - await this.orderService.getOrdersForPortfolioCalculator({ - userCurrency, - userId - }); + const userSettings = this.request.user.Settings.settings; - const portfolioCalculator = this.calculatorFactory.createCalculator({ - activities, + const { accounts, holdings, summary } = await this.getDetails({ + impersonationId, userId, - calculationType: PerformanceCalculationType.TWR, - currency: this.request.user.Settings.settings.baseCurrency - }); - - let { totalFeesWithCurrencyEffect, positions, totalInvestment } = - await portfolioCalculator.getSnapshot(); - - positions = positions.filter((item) => !item.quantity.eq(0)); - - const portfolioItemsNow: { [symbol: string]: TimelinePosition } = {}; - - for (const position of positions) { - portfolioItemsNow[position.symbol] = position; - } - - const { accounts } = await this.getValueOfAccountsAndPlatforms({ - activities, - portfolioItemsNow, - userCurrency, - userId + withMarkets: true, + withSummary: true }); - const userSettings = this.request.user.Settings.settings; - return { rules: { - accountClusterRisk: isEmpty(activities) - ? undefined - : await this.rulesService.evaluate( - [ - new AccountClusterRiskCurrentInvestment( - this.exchangeRateDataService, - accounts - ), - new AccountClusterRiskSingleAccount( - this.exchangeRateDataService, - accounts - ) - ], - userSettings - ), - currencyClusterRisk: isEmpty(activities) - ? undefined - : await this.rulesService.evaluate( - [ - new CurrencyClusterRiskBaseCurrencyCurrentInvestment( - this.exchangeRateDataService, - positions - ), - new CurrencyClusterRiskCurrentInvestment( - this.exchangeRateDataService, - positions - ) - ], - userSettings - ), + accountClusterRisk: + summary.ordersCount > 0 + ? await this.rulesService.evaluate( + [ + new AccountClusterRiskCurrentInvestment( + this.exchangeRateDataService, + accounts + ), + new AccountClusterRiskSingleAccount( + this.exchangeRateDataService, + accounts + ) + ], + userSettings + ) + : undefined, + currencyClusterRisk: + summary.ordersCount > 0 + ? await this.rulesService.evaluate( + [ + new CurrencyClusterRiskBaseCurrencyCurrentInvestment( + this.exchangeRateDataService, + Object.values(holdings) + ), + new CurrencyClusterRiskCurrentInvestment( + this.exchangeRateDataService, + Object.values(holdings) + ) + ], + userSettings + ) + : undefined, emergencyFund: await this.rulesService.evaluate( [ new EmergencyFundSetup( @@ -1229,8 +1217,8 @@ export class PortfolioService { [ new FeeRatioInitialInvestment( this.exchangeRateDataService, - totalInvestment.toNumber(), - totalFeesWithCurrencyEffect.toNumber() + summary.committedFunds, + summary.fees ) ], userSettings @@ -1257,6 +1245,62 @@ export class PortfolioService { await this.orderService.assignTags({ dataSource, symbol, tags, userId }); } + private getAggregatedMarkets(holdings: { + [symbol: string]: PortfolioPosition; + }): { + [key in Market]: { name: string; value: number }; + } { + const markets = { + [UNKNOWN_KEY]: { + name: UNKNOWN_KEY, + value: 0 + }, + developedMarkets: { + name: 'developedMarkets', + value: 0 + }, + emergingMarkets: { + name: 'emergingMarkets', + value: 0 + }, + otherMarkets: { + name: 'otherMarkets', + value: 0 + } + }; + + for (const [symbol, position] of Object.entries(holdings)) { + const value = position.valueInBaseCurrency; + + if (position.assetClass !== AssetClass.LIQUIDITY) { + if (position.countries.length > 0) { + markets.developedMarkets.value += + position.markets.developedMarkets * value; + markets.emergingMarkets.value += + position.markets.emergingMarkets * value; + markets.otherMarkets.value += position.markets.otherMarkets * value; + } else { + markets[UNKNOWN_KEY].value += value; + } + } + } + + const marketsTotal = + markets.developedMarkets.value + + markets.emergingMarkets.value + + markets.otherMarkets.value + + markets[UNKNOWN_KEY].value; + + markets.developedMarkets.value = + markets.developedMarkets.value / marketsTotal; + markets.emergingMarkets.value = + markets.emergingMarkets.value / marketsTotal; + markets.otherMarkets.value = markets.otherMarkets.value / marketsTotal; + markets[UNKNOWN_KEY].value = markets[UNKNOWN_KEY].value / marketsTotal; + + return markets; + } + private async getCashPositions({ cashDetails, userCurrency, diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts index 8397f3e46..a1e0d9bee 100644 --- a/apps/api/src/models/rule.ts +++ b/apps/api/src/models/rule.ts @@ -1,8 +1,9 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { groupBy } from '@ghostfolio/common/helper'; -import { UserSettings } from '@ghostfolio/common/interfaces'; -import { TimelinePosition } from '@ghostfolio/common/models'; +import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; + +import { Big } from 'big.js'; import { EvaluationResult } from './interfaces/evaluation-result.interface'; import { RuleInterface } from './interfaces/rule.interface'; @@ -33,24 +34,26 @@ export abstract class Rule implements RuleInterface { return this.name; } - public groupCurrentPositionsByAttribute( - positions: TimelinePosition[], - attribute: keyof TimelinePosition, + public groupCurrentHoldingsByAttribute( + holdings: PortfolioPosition[], + attribute: keyof PortfolioPosition, baseCurrency: string ) { - return Array.from(groupBy(attribute, positions).entries()).map( + return Array.from(groupBy(attribute, holdings).entries()).map( ([attributeValue, objs]) => ({ groupKey: attributeValue, investment: objs.reduce( (previousValue, currentValue) => - previousValue + currentValue.investment.toNumber(), + previousValue + currentValue.investment, 0 ), value: objs.reduce( (previousValue, currentValue) => previousValue + this.exchangeRateDataService.toCurrency( - currentValue.quantity.mul(currentValue.marketPrice).toNumber(), + new Big(currentValue.quantity) + .mul(currentValue.marketPrice) + .toNumber(), currentValue.currency, baseCurrency ), diff --git a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts index e3050efcc..39ee8b88d 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts @@ -1,35 +1,34 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; -import { TimelinePosition } from '@ghostfolio/common/models'; +import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { - private positions: TimelinePosition[]; + private holdings: PortfolioPosition[]; public constructor( protected exchangeRateDataService: ExchangeRateDataService, - positions: TimelinePosition[] + holdings: PortfolioPosition[] ) { super(exchangeRateDataService, { key: CurrencyClusterRiskBaseCurrencyCurrentInvestment.name, name: 'Investment: Base Currency' }); - this.positions = positions; + this.holdings = holdings; } public evaluate(ruleSettings: Settings) { - const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute( - this.positions, + const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute( + this.holdings, 'currency', ruleSettings.baseCurrency ); - let maxItem = positionsGroupedByCurrency[0]; + let maxItem = holdingsGroupedByCurrency[0]; let totalValue = 0; - positionsGroupedByCurrency.forEach((groupItem) => { + holdingsGroupedByCurrency.forEach((groupItem) => { // Calculate total value totalValue += groupItem.value; @@ -39,7 +38,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { + const baseCurrencyItem = holdingsGroupedByCurrency.find((item) => { return item.groupKey === ruleSettings.baseCurrency; }); diff --git a/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts index fadf47ba5..bdb36c78a 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/current-investment.ts @@ -1,35 +1,34 @@ import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { UserSettings } from '@ghostfolio/common/interfaces'; -import { TimelinePosition } from '@ghostfolio/common/models'; +import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces'; export class CurrencyClusterRiskCurrentInvestment extends Rule { - private positions: TimelinePosition[]; + private holdings: PortfolioPosition[]; public constructor( protected exchangeRateDataService: ExchangeRateDataService, - positions: TimelinePosition[] + holdings: PortfolioPosition[] ) { super(exchangeRateDataService, { key: CurrencyClusterRiskCurrentInvestment.name, name: 'Investment' }); - this.positions = positions; + this.holdings = holdings; } public evaluate(ruleSettings: Settings) { - const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute( - this.positions, + const holdingsGroupedByCurrency = this.groupCurrentHoldingsByAttribute( + this.holdings, 'currency', ruleSettings.baseCurrency ); - let maxItem = positionsGroupedByCurrency[0]; + let maxItem = holdingsGroupedByCurrency[0]; let totalValue = 0; - positionsGroupedByCurrency.forEach((groupItem) => { + holdingsGroupedByCurrency.forEach((groupItem) => { // Calculate total value totalValue += groupItem.value; diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts index 4fb68e780..265d3c941 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.component.ts @@ -35,8 +35,6 @@ export class GfRuleSettingsDialogComponent { @Inject(MAT_DIALOG_DATA) public data: IRuleSettingsDialogParams, public dialogRef: MatDialogRef ) { - console.log(this.data.rule); - this.settings = this.data.rule.settings; } } diff --git a/libs/common/src/lib/interfaces/portfolio-details.interface.ts b/libs/common/src/lib/interfaces/portfolio-details.interface.ts index 611ed8056..3c2833071 100644 --- a/libs/common/src/lib/interfaces/portfolio-details.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-details.interface.ts @@ -2,6 +2,7 @@ import { PortfolioPosition, PortfolioSummary } from '@ghostfolio/common/interfaces'; +import { Market } from '@ghostfolio/common/types'; export interface PortfolioDetails { accounts: { @@ -14,6 +15,12 @@ export interface PortfolioDetails { }; }; holdings: { [symbol: string]: PortfolioPosition }; + markets?: { + [key in Market]: { + name: string; + value: number; + }; + }; platforms: { [id: string]: { balance: number; From f3e2091ff4384915f7a3b3774066d086fff3ebf1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 5 Oct 2024 09:58:09 +0200 Subject: [PATCH 11/30] Feature/refactor markets calculation in details of portfolio service (#3871) * Refactor markets calculation --- .../src/app/portfolio/portfolio.service.ts | 151 ++++++++++++++---- .../interfaces/portfolio-details.interface.ts | 14 +- 2 files changed, 127 insertions(+), 38 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 6879b2030..e79853e5c 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -45,7 +45,6 @@ import type { AccountWithValue, DateRange, GroupBy, - Market, RequestWithUser, UserWithSettings } from '@ghostfolio/common/types'; @@ -582,15 +581,11 @@ export class PortfolioService { }; } - let markets: { - [key in Market]: { - name: string; - value: number; - }; - }; + let markets: PortfolioDetails['markets']; + let marketsAdvanced: PortfolioDetails['marketsAdvanced']; if (withMarkets) { - markets = this.getAggregatedMarkets(holdings); + ({ markets, marketsAdvanced } = this.getAggregatedMarkets(holdings)); } let summary: PortfolioSummary; @@ -615,6 +610,7 @@ export class PortfolioService { hasErrors, holdings, markets, + marketsAdvanced, platforms, summary }; @@ -1248,24 +1244,67 @@ export class PortfolioService { private getAggregatedMarkets(holdings: { [symbol: string]: PortfolioPosition; }): { - [key in Market]: { name: string; value: number }; + markets: PortfolioDetails['markets']; + marketsAdvanced: PortfolioDetails['marketsAdvanced']; } { - const markets = { + const markets: PortfolioDetails['markets'] = { [UNKNOWN_KEY]: { - name: UNKNOWN_KEY, - value: 0 + id: UNKNOWN_KEY, + valueInBaseCurrency: 0, + valueInPercentage: 0 }, developedMarkets: { - name: 'developedMarkets', - value: 0 + id: 'developedMarkets', + valueInBaseCurrency: 0, + valueInPercentage: 0 }, emergingMarkets: { - name: 'emergingMarkets', - value: 0 + id: 'emergingMarkets', + valueInBaseCurrency: 0, + valueInPercentage: 0 }, otherMarkets: { - name: 'otherMarkets', - value: 0 + id: 'otherMarkets', + valueInBaseCurrency: 0, + valueInPercentage: 0 + } + }; + + const marketsAdvanced: PortfolioDetails['marketsAdvanced'] = { + [UNKNOWN_KEY]: { + id: UNKNOWN_KEY, + valueInBaseCurrency: 0, + valueInPercentage: 0 + }, + asiaPacific: { + id: 'asiaPacific', + valueInBaseCurrency: 0, + valueInPercentage: 0 + }, + emergingMarkets: { + id: 'emergingMarkets', + valueInBaseCurrency: 0, + valueInPercentage: 0 + }, + europe: { + id: 'europe', + valueInBaseCurrency: 0, + valueInPercentage: 0 + }, + japan: { + id: 'japan', + valueInBaseCurrency: 0, + valueInPercentage: 0 + }, + northAmerica: { + id: 'northAmerica', + valueInBaseCurrency: 0, + valueInPercentage: 0 + }, + otherMarkets: { + id: 'otherMarkets', + valueInBaseCurrency: 0, + valueInPercentage: 0 } }; @@ -1274,31 +1313,73 @@ export class PortfolioService { if (position.assetClass !== AssetClass.LIQUIDITY) { if (position.countries.length > 0) { - markets.developedMarkets.value += + markets.developedMarkets.valueInBaseCurrency += position.markets.developedMarkets * value; - markets.emergingMarkets.value += + markets.emergingMarkets.valueInBaseCurrency += position.markets.emergingMarkets * value; - markets.otherMarkets.value += position.markets.otherMarkets * value; + markets.otherMarkets.valueInBaseCurrency += + position.markets.otherMarkets * value; + + marketsAdvanced.asiaPacific.valueInBaseCurrency += + position.marketsAdvanced.asiaPacific * value; + marketsAdvanced.emergingMarkets.valueInBaseCurrency += + position.marketsAdvanced.emergingMarkets * value; + marketsAdvanced.europe.valueInBaseCurrency += + position.marketsAdvanced.europe * value; + marketsAdvanced.japan.valueInBaseCurrency += + position.marketsAdvanced.japan * value; + marketsAdvanced.northAmerica.valueInBaseCurrency += + position.marketsAdvanced.northAmerica * value; + marketsAdvanced.otherMarkets.valueInBaseCurrency += + position.marketsAdvanced.otherMarkets * value; } else { - markets[UNKNOWN_KEY].value += value; + markets[UNKNOWN_KEY].valueInBaseCurrency += value; + marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency += value; } } } const marketsTotal = - markets.developedMarkets.value + - markets.emergingMarkets.value + - markets.otherMarkets.value + - markets[UNKNOWN_KEY].value; - - markets.developedMarkets.value = - markets.developedMarkets.value / marketsTotal; - markets.emergingMarkets.value = - markets.emergingMarkets.value / marketsTotal; - markets.otherMarkets.value = markets.otherMarkets.value / marketsTotal; - markets[UNKNOWN_KEY].value = markets[UNKNOWN_KEY].value / marketsTotal; - - return markets; + markets.developedMarkets.valueInBaseCurrency + + markets.emergingMarkets.valueInBaseCurrency + + markets.otherMarkets.valueInBaseCurrency + + markets[UNKNOWN_KEY].valueInBaseCurrency; + + markets.developedMarkets.valueInPercentage = + markets.developedMarkets.valueInBaseCurrency / marketsTotal; + markets.emergingMarkets.valueInPercentage = + markets.emergingMarkets.valueInBaseCurrency / marketsTotal; + markets.otherMarkets.valueInPercentage = + markets.otherMarkets.valueInBaseCurrency / marketsTotal; + markets[UNKNOWN_KEY].valueInPercentage = + markets[UNKNOWN_KEY].valueInBaseCurrency / marketsTotal; + + const marketsAdvancedTotal = + marketsAdvanced.asiaPacific.valueInBaseCurrency + + marketsAdvanced.emergingMarkets.valueInBaseCurrency + + marketsAdvanced.europe.valueInBaseCurrency + + marketsAdvanced.japan.valueInBaseCurrency + + marketsAdvanced.northAmerica.valueInBaseCurrency + + marketsAdvanced.otherMarkets.valueInBaseCurrency + + marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency; + + marketsAdvanced.asiaPacific.valueInPercentage = + marketsAdvanced.asiaPacific.valueInBaseCurrency / marketsAdvancedTotal; + marketsAdvanced.emergingMarkets.valueInPercentage = + marketsAdvanced.emergingMarkets.valueInBaseCurrency / + marketsAdvancedTotal; + marketsAdvanced.europe.valueInPercentage = + marketsAdvanced.europe.valueInBaseCurrency / marketsAdvancedTotal; + marketsAdvanced.japan.valueInPercentage = + marketsAdvanced.japan.valueInBaseCurrency / marketsAdvancedTotal; + marketsAdvanced.northAmerica.valueInPercentage = + marketsAdvanced.northAmerica.valueInBaseCurrency / marketsAdvancedTotal; + marketsAdvanced.otherMarkets.valueInPercentage = + marketsAdvanced.otherMarkets.valueInBaseCurrency / marketsAdvancedTotal; + marketsAdvanced[UNKNOWN_KEY].valueInPercentage = + marketsAdvanced[UNKNOWN_KEY].valueInBaseCurrency / marketsAdvancedTotal; + + return { markets, marketsAdvanced }; } private async getCashPositions({ diff --git a/libs/common/src/lib/interfaces/portfolio-details.interface.ts b/libs/common/src/lib/interfaces/portfolio-details.interface.ts index 3c2833071..2e9426936 100644 --- a/libs/common/src/lib/interfaces/portfolio-details.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-details.interface.ts @@ -2,7 +2,7 @@ import { PortfolioPosition, PortfolioSummary } from '@ghostfolio/common/interfaces'; -import { Market } from '@ghostfolio/common/types'; +import { Market, MarketAdvanced } from '@ghostfolio/common/types'; export interface PortfolioDetails { accounts: { @@ -17,8 +17,16 @@ export interface PortfolioDetails { holdings: { [symbol: string]: PortfolioPosition }; markets?: { [key in Market]: { - name: string; - value: number; + id: Market; + valueInBaseCurrency: number; + valueInPercentage: number; + }; + }; + marketsAdvanced?: { + [key in MarketAdvanced]: { + id: MarketAdvanced; + valueInBaseCurrency: number; + valueInPercentage: number; }; }; platforms: { From fd26253e7afc7849f5652920a225d7709f3ba113 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 5 Oct 2024 10:45:39 +0200 Subject: [PATCH 12/30] Feature/set up husky and pre-commit hook for linting and format check (#3867) * Set up husky and pre-commit hook for linting and format check * Update changelog --------- Signed-off-by: Dominik Willner --- .husky/pre-commit | 6 ++++++ CHANGELOG.md | 4 ++++ DEVELOPMENT.md | 1 - git-hooks/pre-commit | 26 -------------------------- package-lock.json | 16 ++++++++++++++++ package.json | 4 +++- 6 files changed, 29 insertions(+), 28 deletions(-) create mode 100644 .husky/pre-commit delete mode 100755 git-hooks/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..4bf77522a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +# Run linting and stop the commit process if any errors are found +# --quiet suppresses warnings (temporary until all warnings are fixed) +npm run lint --quiet || exit 1 + +# Check formatting on modified and uncommitted files, stop the commit if issues are found +npm run format:check --uncommitted || exit 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 00021fbfc..f2464c350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Set up a git-hook via `husky` to lint and format the changes before a commit + ### Fixed - Handled an exception in the historical market data gathering of derived currencies diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 0c76a2924..b009679ac 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -14,7 +14,6 @@ 1. Run `npm install` 1. Run `docker compose --env-file ./.env -f docker/docker-compose.dev.yml up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io) 1. Run `npm run database:setup` to initialize the database schema -1. Run `git config core.hooksPath ./git-hooks/` to setup git hooks 1. Start the [server](#start-server) and the [client](#start-client) 1. Open https://localhost:4200/en in your browser 1. Create a new user via _Get Started_ (this first user will get the role `ADMIN`) diff --git a/git-hooks/pre-commit b/git-hooks/pre-commit deleted file mode 100755 index b2b86eac1..000000000 --- a/git-hooks/pre-commit +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# Will check if "npm run format" is run before executing. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. - -echo "Running npm run format" - -# Run the command and loop over its output -FILES_TO_STAGE="" -i=0 -while IFS= read -r line; do - # Process each line here - ((i++)) - if [ $i -le 2 ]; then - continue - fi - if [[ $line == Done* ]]; then - break - fi - FILES_TO_STAGE="$FILES_TO_STAGE $line" - -done < <(npm run format) -git add $FILES_TO_STAGE -echo "Files formatted. Committing..." diff --git a/package-lock.json b/package-lock.json index b848e8188..5edf7d510 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,6 +143,7 @@ "eslint-plugin-cypress": "2.15.1", "eslint-plugin-import": "2.29.1", "eslint-plugin-storybook": "0.6.15", + "husky": "9.1.6", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", @@ -20653,6 +20654,21 @@ "node": ">=8.12.0" } }, + "node_modules/husky": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/hyperdyperid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", diff --git a/package.json b/package.json index 584407f1d..06fd92a8c 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,11 @@ "format:check": "nx format:check", "format:write": "nx format:write", "help": "nx help", - "lint": "nx lint", + "lint": "nx run-many --target=lint --all", "ng": "nx", "nx": "nx", "postinstall": "prisma generate", + "prepare": "husky", "prisma": "prisma", "replace-placeholders-in-build": "node ./replace.build.js", "start": "node dist/apps/api/main", @@ -188,6 +189,7 @@ "eslint-plugin-cypress": "2.15.1", "eslint-plugin-import": "2.29.1", "eslint-plugin-storybook": "0.6.15", + "husky": "9.1.6", "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", From e2ae43bf28d8aa1ac69f7eadd2522d82c1e169d3 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 5 Oct 2024 17:42:07 +0200 Subject: [PATCH 13/30] Feature/set up recommendedTypeChecked rule in eslint configuration (#3876) * Set up recommendedTypeChecked rule in eslint configuration * Update changelog --------- Signed-off-by: Dominik Willner --- .eslintrc.json | 31 +++++++++++++++++++++++++------ CHANGELOG.md | 1 + apps/client-e2e/.eslintrc.json | 20 ++++++++++++++++++++ apps/ui-e2e/.eslintrc.json | 3 +++ libs/ui/.eslintrc.json | 7 +++++-- 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 apps/client-e2e/.eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json index fc591aab6..59b7e8b89 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -34,6 +34,7 @@ { "files": ["*.ts"], "plugins": ["eslint-plugin-import", "@typescript-eslint"], + "extends": ["plugin:@typescript-eslint/recommended-type-checked"], "rules": { "@typescript-eslint/consistent-type-definitions": "warn", "@typescript-eslint/dot-notation": "off", @@ -53,7 +54,6 @@ "ignoreParameters": true } ], - "@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-shadow": [ "warn", @@ -61,12 +61,10 @@ "hoist": "all" } ], - "@typescript-eslint/no-unused-expressions": "warn", "@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/unified-signatures": "error", "@typescript-eslint/no-loss-of-precision": "warn", "@typescript-eslint/no-var-requires": "warn", - "@typescript-eslint/ban-ts-comment": "warn", "@typescript-eslint/ban-types": "warn", "arrow-body-style": "off", "constructor-super": "error", @@ -83,11 +81,9 @@ "no-fallthrough": "error", "no-new-wrappers": "error", "no-restricted-imports": ["error", "rxjs/Rx"], - "no-throw-literal": "warn", "no-undef-init": "error", "no-underscore-dangle": "off", "no-var": "error", - "prefer-const": "warn", "radix": "error", "no-unsafe-optional-chaining": "warn", "no-extra-boolean-cast": "warn", @@ -96,7 +92,30 @@ "no-unsafe-finally": "warn", "no-prototype-builtins": "warn", "no-async-promise-executor": "warn", - "no-constant-condition": "warn" + "no-constant-condition": "warn", + + // The following rules are part of @typescript-eslint/recommended-type-checked + // and can be remove once solved + "@typescript-eslint/await-thenable": "warn", + "@typescript-eslint/ban-ts-comment": "warn", + "@typescript-eslint/no-base-to-string": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-floating-promises": "warn", + "@typescript-eslint/no-misused-promises": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-unnecessary-type-assertion": "warn", + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/no-unsafe-enum-comparison": "warn", + "@typescript-eslint/no-unused-expressions": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/require-await": "warn", + "@typescript-eslint/restrict-template-expressions": "warn", + "@typescript-eslint/unbound-method": "warn", + "prefer-const": "warn" } } ], diff --git a/CHANGELOG.md b/CHANGELOG.md index f2464c350..1d940fcde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Set up a git-hook via `husky` to lint and format the changes before a commit +- Added the `typescript-eslint/recommended-type-checked` rule to the `eslint` configuration ### Fixed diff --git a/apps/client-e2e/.eslintrc.json b/apps/client-e2e/.eslintrc.json new file mode 100644 index 000000000..dbedf6bd4 --- /dev/null +++ b/apps/client-e2e/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["apps/client-e2e/tsconfig.*?.json"] + }, + "rules": {} + }, + { + "files": ["src/plugins/index.js"], + "rules": { + "@typescript-eslint/no-var-requires": "off", + "no-undef": "off" + } + } + ] +} diff --git a/apps/ui-e2e/.eslintrc.json b/apps/ui-e2e/.eslintrc.json index 4c5989b23..e1ff28793 100644 --- a/apps/ui-e2e/.eslintrc.json +++ b/apps/ui-e2e/.eslintrc.json @@ -4,6 +4,9 @@ "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["apps/ui-e2e/tsconfig.json"] + }, "rules": {} }, { diff --git a/libs/ui/.eslintrc.json b/libs/ui/.eslintrc.json index 2d3d3dcaf..6b88b020e 100644 --- a/libs/ui/.eslintrc.json +++ b/libs/ui/.eslintrc.json @@ -1,9 +1,12 @@ { "extends": ["../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], + "ignorePatterns": ["!**/*", "**/*.stories.ts"], "overrides": [ { - "files": ["*.ts"], + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "parserOptions": { + "project": ["libs/ui/tsconfig.*?.json"] + }, "extends": [ "plugin:@nx/angular", "plugin:@angular-eslint/template/process-inline-templates" From e715ce14e53816c50bc8534ec607a9606150c9df Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:52:01 +0200 Subject: [PATCH 14/30] Feature/switch typescript-eslint no-unused-expressions rule to error (#3872) * Switch @typescript-eslint/no-unused-expressions to error (remove from eslint configuration) --- .eslintrc.json | 1 - .../lib/activities-table/activities-table.component.ts | 10 +++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 59b7e8b89..4078da0e8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -107,7 +107,6 @@ "@typescript-eslint/no-unsafe-argument": "warn", "@typescript-eslint/no-unsafe-assignment": "warn", "@typescript-eslint/no-unsafe-enum-comparison": "warn", - "@typescript-eslint/no-unused-expressions": "warn", "@typescript-eslint/no-unsafe-member-access": "warn", "@typescript-eslint/no-unsafe-return": "warn", "@typescript-eslint/no-unused-vars": "warn", diff --git a/libs/ui/src/lib/activities-table/activities-table.component.ts b/libs/ui/src/lib/activities-table/activities-table.component.ts index 4f8fff904..67f81adce 100644 --- a/libs/ui/src/lib/activities-table/activities-table.component.ts +++ b/libs/ui/src/lib/activities-table/activities-table.component.ts @@ -269,9 +269,13 @@ export class GfActivitiesTableComponent } public toggleAllRows() { - this.areAllRowsSelected() - ? this.selectedRows.clear() - : this.dataSource.data.forEach((row) => this.selectedRows.select(row)); + if (this.areAllRowsSelected()) { + this.selectedRows.clear(); + } else { + this.dataSource.data.forEach((row) => { + this.selectedRows.select(row); + }); + } this.selectedActivities.emit(this.selectedRows.selected); } From ecd75b5d8ab971942278b02b75a69569e3b3197f Mon Sep 17 00:00:00 2001 From: ceroma <678940+ceroma@users.noreply.github.com> Date: Sat, 5 Oct 2024 13:08:59 -0300 Subject: [PATCH 15/30] Feature/optimize portfolio snapshot computation by reusing date intervals (#3855) * Optimize portfolio snapshot computation by reusing date intervals * Update changelog --- CHANGELOG.md | 4 ++ .../calculator/twr/portfolio-calculator.ts | 53 ++++++++++--------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d940fcde..f55f594e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set up a git-hook via `husky` to lint and format the changes before a commit - Added the `typescript-eslint/recommended-type-checked` rule to the `eslint` configuration +### Changed + +- Optimized the portfolio calculations by reusing date intervals + ### Fixed - Handled an exception in the historical market data gathering of derived currencies diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index f5e301cba..b26b6fac1 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -22,7 +22,7 @@ import { import { cloneDeep, first, last, sortBy } from 'lodash'; export class TWRPortfolioCalculator extends PortfolioCalculator { - private chartDatesDescending: string[]; + private chartDates: string[]; protected calculateOverallPerformance( positions: TimelinePosition[] @@ -228,11 +228,11 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { const dateOfFirstTransaction = new Date(first(orders).date); - const unitPriceAtStartDate = - marketSymbolMap[format(start, DATE_FORMAT)]?.[symbol]; + const endDateString = format(end, DATE_FORMAT); + const startDateString = format(start, DATE_FORMAT); - const unitPriceAtEndDate = - marketSymbolMap[format(end, DATE_FORMAT)]?.[symbol]; + const unitPriceAtStartDate = marketSymbolMap[startDateString]?.[symbol]; + const unitPriceAtEndDate = marketSymbolMap[endDateString]?.[symbol]; if ( !unitPriceAtEndDate || @@ -278,7 +278,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { // Add a synthetic order at the start and the end date orders.push({ - date: format(start, DATE_FORMAT), + date: startDateString, fee: new Big(0), feeInBaseCurrency: new Big(0), itemType: 'start', @@ -292,7 +292,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { }); orders.push({ - date: format(end, DATE_FORMAT), + date: endDateString, fee: new Big(0), feeInBaseCurrency: new Big(0), itemType: 'end', @@ -305,7 +305,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { unitPrice: unitPriceAtEndDate }); - let day = start; let lastUnitPrice: Big; const ordersByDate: { [date: string]: PortfolioOrderItem[] } = {}; @@ -315,15 +314,23 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { ordersByDate[order.date].push(order); } - while (isBefore(day, end)) { - const dateString = format(day, DATE_FORMAT); + if (!this.chartDates) { + this.chartDates = Object.keys(chartDateMap).sort(); + } + + for (const dateString of this.chartDates) { + if (dateString < startDateString) { + continue; + } else if (dateString > endDateString) { + break; + } if (ordersByDate[dateString]?.length > 0) { for (let order of ordersByDate[dateString]) { order.unitPriceFromMarketData = marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice; } - } else if (chartDateMap[dateString]) { + } else { orders.push({ date: dateString, fee: new Big(0), @@ -343,8 +350,6 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { const lastOrder = last(orders); lastUnitPrice = lastOrder.unitPriceFromMarketData ?? lastOrder.unitPrice; - - day = addDays(day, 1); } // Sort orders so that the start and end placeholder order are at the correct @@ -821,14 +826,14 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { startDate = start; } - const endDateString = format(endDate, DATE_FORMAT); - const startDateString = format(startDate, DATE_FORMAT); + const rangeEndDateString = format(endDate, DATE_FORMAT); + const rangeStartDateString = format(startDate, DATE_FORMAT); const currentValuesAtDateRangeStartWithCurrencyEffect = - currentValuesWithCurrencyEffect[startDateString] ?? new Big(0); + currentValuesWithCurrencyEffect[rangeStartDateString] ?? new Big(0); const investmentValuesAccumulatedAtStartDateWithCurrencyEffect = - investmentValuesAccumulatedWithCurrencyEffect[startDateString] ?? + investmentValuesAccumulatedWithCurrencyEffect[rangeStartDateString] ?? new Big(0); const grossPerformanceAtDateRangeStartWithCurrencyEffect = @@ -839,14 +844,12 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { let average = new Big(0); let dayCount = 0; - if (!this.chartDatesDescending) { - this.chartDatesDescending = Object.keys(chartDateMap).sort().reverse(); - } + for (let i = this.chartDates.length - 1; i >= 0; i -= 1) { + const date = this.chartDates[i]; - for (const date of this.chartDatesDescending) { - if (date > endDateString) { + if (date > rangeEndDateString) { continue; - } else if (date < startDateString) { + } else if (date < rangeStartDateString) { break; } @@ -869,13 +872,13 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { } netPerformanceWithCurrencyEffectMap[dateRange] = - netPerformanceValuesWithCurrencyEffect[endDateString]?.minus( + netPerformanceValuesWithCurrencyEffect[rangeEndDateString]?.minus( // If the date range is 'max', take 0 as a start value. Otherwise, // the value of the end of the day of the start date is taken which // differs from the buying price. dateRange === 'max' ? new Big(0) - : (netPerformanceValuesWithCurrencyEffect[startDateString] ?? + : (netPerformanceValuesWithCurrencyEffect[rangeStartDateString] ?? new Big(0)) ) ?? new Big(0); From b0c2d3cddf23c946ea4936344d24a083976342c1 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sat, 5 Oct 2024 20:53:33 +0200 Subject: [PATCH 16/30] Feature/add lint step to build-code.yml workflow (#3879) * add lint step to build-code.yml workflow Signed-off-by: Dominik Willner --- .github/workflows/build-code.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-code.yml b/.github/workflows/build-code.yml index 5c072d760..18240ebc4 100644 --- a/.github/workflows/build-code.yml +++ b/.github/workflows/build-code.yml @@ -29,6 +29,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Check code style + run: npm run lint + - name: Check formatting run: npm run format:check From 8cbefbc1f4ea02253d6494c7fb0fb6660ae9cf8f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 5 Oct 2024 21:11:51 +0200 Subject: [PATCH 17/30] Feature/move prisma to devDependencies (#3880) --- package-lock.json | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5edf7d510..8db40b134 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,7 +84,6 @@ "passport": "0.7.0", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.1", - "prisma": "5.20.0", "reflect-metadata": "0.1.13", "rxjs": "7.5.6", "stripe": "15.11.0", @@ -150,6 +149,7 @@ "nx": "19.5.6", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", + "prisma": "5.20.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "7.0.1", @@ -9668,12 +9668,14 @@ "version": "5.20.0", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.20.0.tgz", "integrity": "sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.20.0.tgz", "integrity": "sha512-DtqkP+hcZvPEbj8t8dK5df2b7d3B8GNauKqaddRRqQBBlgkbdhJkxhoJTrOowlS3vaRt2iMCkU0+CSNn0KhqAQ==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -9687,12 +9689,14 @@ "version": "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284.tgz", "integrity": "sha512-Lg8AS5lpi0auZe2Mn4gjuCg081UZf88k3cn0RCwHgR+6cyHHpttPZBElJTHf83ZGsRNAmVCZCfUGA57WB4u4JA==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.20.0.tgz", "integrity": "sha512-JVcaPXC940wOGpCOwuqQRTz6I9SaBK0c1BAyC1pcz9xBi+dzFgUu3G/p9GV1FhFs9OKpfSpIhQfUJE9y00zhqw==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "5.20.0", @@ -9704,6 +9708,7 @@ "version": "5.20.0", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.20.0.tgz", "integrity": "sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@prisma/debug": "5.20.0" @@ -28839,6 +28844,7 @@ "version": "5.20.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.20.0.tgz", "integrity": "sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig==", + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 06fd92a8c..59f66ba50 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,6 @@ "passport": "0.7.0", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.1", - "prisma": "5.20.0", "reflect-metadata": "0.1.13", "rxjs": "7.5.6", "stripe": "15.11.0", @@ -196,6 +195,7 @@ "nx": "19.5.6", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", + "prisma": "5.20.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "7.0.1", From b50a1fc63de2c02e4f7e2ba6dda564ef53e2faeb Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:27:42 +0200 Subject: [PATCH 18/30] Feature/reuse markets calculation in public portfolio endpoint (#3882) * Reuse markets calculation in public portfolio endpoint * Update changelog --- CHANGELOG.md | 1 + .../app/endpoints/public/public.controller.ts | 7 ++- .../app/pages/public/public-page.component.ts | 46 +------------------ .../src/app/pages/public/public-page.html | 10 ++-- .../public-portfolio-response.interface.ts | 9 +++- 5 files changed, 22 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55f594e4..7e991d77e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Optimized the portfolio calculations by reusing date intervals +- Refactored the calculation of the allocations by market on the public page ### Fixed diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index b0f7bb2c3..9399f97bf 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -57,7 +57,7 @@ export class PublicController { } const [ - { holdings }, + { holdings, markets }, { performance: performance1d }, { performance: performanceMax }, { performance: performanceYtd } @@ -76,8 +76,13 @@ export class PublicController { }) ]); + Object.values(markets).forEach((market) => { + delete market.valueInBaseCurrency; + }); + const publicPortfolioResponse: PublicPortfolioResponse = { hasDetails, + markets, alias: access.alias, holdings: {}, performance: { diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index 56d6ebdb7..3dbce23ec 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -32,7 +32,7 @@ export class PublicPageComponent implements OnInit { public deviceType: string; public holdings: PublicPortfolioResponse['holdings'][string][]; public markets: { - [key in Market]: { name: string; value: number }; + [key in Market]: { id: Market; valueInPercentage: number }; }; public positions: { [symbol: string]: Pick & { @@ -102,24 +102,7 @@ export class PublicPageComponent implements OnInit { } }; this.holdings = []; - this.markets = { - [UNKNOWN_KEY]: { - name: UNKNOWN_KEY, - value: 0 - }, - developedMarkets: { - name: 'developedMarkets', - value: 0 - }, - emergingMarkets: { - name: 'emergingMarkets', - value: 0 - }, - otherMarkets: { - name: 'otherMarkets', - value: 0 - } - }; + this.markets = this.publicPortfolioDetails.markets; this.positions = {}; this.sectors = { [UNKNOWN_KEY]: { @@ -150,13 +133,6 @@ export class PublicPageComponent implements OnInit { // Prepare analysis data by continents, countries, holdings and sectors except for liquidity if (position.countries.length > 0) { - this.markets.developedMarkets.value += - position.markets.developedMarkets * position.valueInBaseCurrency; - this.markets.emergingMarkets.value += - position.markets.emergingMarkets * position.valueInBaseCurrency; - this.markets.otherMarkets.value += - position.markets.otherMarkets * position.valueInBaseCurrency; - for (const country of position.countries) { const { code, continent, name, weight } = country; @@ -192,9 +168,6 @@ export class PublicPageComponent implements OnInit { this.countries[UNKNOWN_KEY].value += this.publicPortfolioDetails.holdings[symbol].valueInBaseCurrency; - - this.markets[UNKNOWN_KEY].value += - this.publicPortfolioDetails.holdings[symbol].valueInBaseCurrency; } if (position.sectors.length > 0) { @@ -227,21 +200,6 @@ export class PublicPageComponent implements OnInit { : position.valueInPercentage }; } - - const marketsTotal = - this.markets.developedMarkets.value + - this.markets.emergingMarkets.value + - this.markets.otherMarkets.value + - this.markets[UNKNOWN_KEY].value; - - this.markets.developedMarkets.value = - this.markets.developedMarkets.value / marketsTotal; - this.markets.emergingMarkets.value = - this.markets.emergingMarkets.value / marketsTotal; - this.markets.otherMarkets.value = - this.markets.otherMarkets.value / marketsTotal; - this.markets[UNKNOWN_KEY].value = - this.markets[UNKNOWN_KEY].value / marketsTotal; } public ngOnDestroy() { diff --git a/apps/client/src/app/pages/public/public-page.html b/apps/client/src/app/pages/public/public-page.html index 369ea50f5..ac1628a3a 100644 --- a/apps/client/src/app/pages/public/public-page.html +++ b/apps/client/src/app/pages/public/public-page.html @@ -156,7 +156,7 @@ i18n size="large" [isPercent]="true" - [value]="markets?.developedMarkets?.value" + [value]="markets?.developedMarkets?.valueInPercentage" >Developed Markets
@@ -165,7 +165,7 @@ i18n size="large" [isPercent]="true" - [value]="markets?.emergingMarkets?.value" + [value]="markets?.emergingMarkets?.valueInPercentage" >Emerging Markets
@@ -174,17 +174,17 @@ i18n size="large" [isPercent]="true" - [value]="markets?.otherMarkets?.value" + [value]="markets?.otherMarkets?.valueInPercentage" >Other Markets
- @if (markets?.[UNKNOWN_KEY]?.value > 0) { + @if (markets?.[UNKNOWN_KEY]?.valueInPercentage > 0) {
No data available
diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts index ce623a058..dc6e57587 100644 --- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts @@ -1,4 +1,5 @@ -import { PortfolioPosition } from '../portfolio-position.interface'; +import { PortfolioDetails, PortfolioPosition } from '..'; +import { Market } from '../../types'; export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { alias?: string; @@ -22,6 +23,12 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { | 'valueInPercentage' >; }; + markets: { + [key in Market]: Pick< + PortfolioDetails['markets'][key], + 'id' | 'valueInPercentage' + >; + }; } interface PublicPortfolioResponseV1 { From f48ce4e1aed114def80c2d8c2fd0b52f991c0a87 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:03:11 +0200 Subject: [PATCH 19/30] Feature/reuse markets calculation in portfolio details endpoint (#3883) * Reuse markets calculation in portfolio details endpoint * Update changelog --- CHANGELOG.md | 1 + .../src/app/portfolio/portfolio.controller.ts | 31 +++++++++- .../allocations/allocations-page.component.ts | 59 +------------------ .../allocations/allocations-page.html | 10 ++-- .../interfaces/portfolio-details.interface.ts | 2 +- 5 files changed, 39 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e991d77e..cd7de4e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Optimized the portfolio calculations by reusing date intervals +- Refactored the calculation of the allocations by market on the allocations page - Refactored the calculation of the allocations by market on the public page ### Fixed diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 7c11e4767..aa3d23644 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -13,7 +13,10 @@ import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; -import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; +import { + HEADER_KEY_IMPERSONATION, + UNKNOWN_KEY +} from '@ghostfolio/common/config'; import { PortfolioDetails, PortfolioDividends, @@ -95,7 +98,7 @@ export class PortfolioController { filterByTags }); - const { accounts, hasErrors, holdings, platforms, summary } = + const { accounts, hasErrors, holdings, markets, platforms, summary } = await this.portfolioService.getDetails({ dateRange, filters, @@ -162,6 +165,10 @@ export class PortfolioController { }) || isRestrictedView(this.request.user) ) { + Object.values(markets).forEach((market) => { + delete market.valueInBaseCurrency; + }); + portfolioSummary = nullifyValuesInObject(summary, [ 'cash', 'committedFunds', @@ -214,6 +221,26 @@ export class PortfolioController { hasError, holdings, platforms, + markets: hasDetails + ? markets + : { + [UNKNOWN_KEY]: { + id: UNKNOWN_KEY, + valueInPercentage: 1 + }, + developedMarkets: { + id: 'developedMarkets', + valueInPercentage: 0 + }, + emergingMarkets: { + id: 'emergingMarkets', + valueInPercentage: 0 + }, + otherMarkets: { + id: 'otherMarkets', + valueInPercentage: 0 + } + }, summary: portfolioSummary }; } diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index 89b08ad95..d7502cbfa 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -47,7 +47,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { public hasImpersonationId: boolean; public isLoading = false; public markets: { - [key in Market]: { name: string; value: number }; + [key in Market]: { id: Market; valueInPercentage: number }; }; public marketsAdvanced: { [key in MarketAdvanced]: { @@ -219,24 +219,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { value: 0 } }; - this.markets = { - [UNKNOWN_KEY]: { - name: UNKNOWN_KEY, - value: 0 - }, - developedMarkets: { - name: 'developedMarkets', - value: 0 - }, - emergingMarkets: { - name: 'emergingMarkets', - value: 0 - }, - otherMarkets: { - name: 'otherMarkets', - value: 0 - } - }; this.marketsAdvanced = { [UNKNOWN_KEY]: { id: UNKNOWN_KEY, @@ -318,6 +300,8 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { }; } + this.markets = this.portfolioDetails.markets; + for (const [symbol, position] of Object.entries( this.portfolioDetails.holdings )) { @@ -348,22 +332,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { // Prepare analysis data by continents, countries, holdings and sectors except for liquidity if (position.countries.length > 0) { - this.markets.developedMarkets.value += - position.markets.developedMarkets * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.markets.emergingMarkets.value += - position.markets.emergingMarkets * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.markets.otherMarkets.value += - position.markets.otherMarkets * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.marketsAdvanced.asiaPacific.value += position.marketsAdvanced.asiaPacific * (isNumber(position.valueInBaseCurrency) @@ -440,12 +408,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency : this.portfolioDetails.holdings[symbol].valueInPercentage; - this.markets[UNKNOWN_KEY].value += isNumber( - position.valueInBaseCurrency - ) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage; - this.marketsAdvanced[UNKNOWN_KEY].value += isNumber( position.valueInBaseCurrency ) @@ -538,21 +500,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { }; } - const marketsTotal = - this.markets.developedMarkets.value + - this.markets.emergingMarkets.value + - this.markets.otherMarkets.value + - this.markets[UNKNOWN_KEY].value; - - this.markets.developedMarkets.value = - this.markets.developedMarkets.value / marketsTotal; - this.markets.emergingMarkets.value = - this.markets.emergingMarkets.value / marketsTotal; - this.markets.otherMarkets.value = - this.markets.otherMarkets.value / marketsTotal; - this.markets[UNKNOWN_KEY].value = - this.markets[UNKNOWN_KEY].value / marketsTotal; - this.topHoldings = Object.values(this.topHoldingsMap) .map(({ name, value }) => { if (this.hasImpersonationId || this.user.settings.isRestrictedView) { diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html index 9b855592d..3431501f5 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html @@ -218,7 +218,7 @@ i18n size="large" [isPercent]="true" - [value]="markets?.developedMarkets?.value" + [value]="markets?.developedMarkets?.valueInPercentage" >Developed Markets
@@ -227,7 +227,7 @@ i18n size="large" [isPercent]="true" - [value]="markets?.emergingMarkets?.value" + [value]="markets?.emergingMarkets?.valueInPercentage" >Emerging Markets
@@ -236,17 +236,17 @@ i18n size="large" [isPercent]="true" - [value]="markets?.otherMarkets?.value" + [value]="markets?.otherMarkets?.valueInPercentage" >Other Markets
- @if (markets?.[UNKNOWN_KEY]?.value > 0) { + @if (markets?.[UNKNOWN_KEY]?.valueInPercentage > 0) {
No data available
diff --git a/libs/common/src/lib/interfaces/portfolio-details.interface.ts b/libs/common/src/lib/interfaces/portfolio-details.interface.ts index 2e9426936..711ae424f 100644 --- a/libs/common/src/lib/interfaces/portfolio-details.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-details.interface.ts @@ -18,7 +18,7 @@ export interface PortfolioDetails { markets?: { [key in Market]: { id: Market; - valueInBaseCurrency: number; + valueInBaseCurrency?: number; valueInPercentage: number; }; }; From f0f67cdacb3f3cf8354e7dfeed544de165c6b48e Mon Sep 17 00:00:00 2001 From: dw-0 Date: Sun, 6 Oct 2024 16:57:56 +0200 Subject: [PATCH 20/30] Feature/set up stylisticTypeChecked rule in eslint configuration (#3878) * Set up stylisticTypeChecked rule in eslint configuration * Update changelog --------- Signed-off-by: Dominik Willner --- .eslintrc.json | 49 ++++++++++++++++++++++++++++++++++----- CHANGELOG.md | 1 + apps/client/tsconfig.json | 2 ++ libs/ui/tsconfig.json | 6 ++--- tsconfig.base.json | 15 +++++++++++- 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 4078da0e8..c2e6b8079 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -34,9 +34,11 @@ { "files": ["*.ts"], "plugins": ["eslint-plugin-import", "@typescript-eslint"], - "extends": ["plugin:@typescript-eslint/recommended-type-checked"], + "extends": [ + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked" + ], "rules": { - "@typescript-eslint/consistent-type-definitions": "warn", "@typescript-eslint/dot-notation": "off", "@typescript-eslint/explicit-member-accessibility": [ "off", @@ -45,8 +47,33 @@ } ], "@typescript-eslint/member-ordering": "warn", - "@typescript-eslint/naming-convention": "off", - "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/naming-convention": [ + "off", + { + "selector": "default", + "format": ["camelCase"], + "leadingUnderscore": "allow", + "trailingUnderscore": "allow" + }, + { + "selector": ["variable", "classProperty", "typeProperty"], + "format": ["camelCase", "UPPER_CASE"], + "leadingUnderscore": "allow", + "trailingUnderscore": "allow" + }, + { + "selector": "objectLiteralProperty", + "format": null + }, + { + "selector": "enumMember", + "format": ["camelCase", "UPPER_CASE", "PascalCase"] + }, + { + "selector": "typeLike", + "format": ["PascalCase"] + } + ], "@typescript-eslint/no-empty-interface": "warn", "@typescript-eslint/no-inferrable-types": [ "warn", @@ -61,7 +88,6 @@ "hoist": "all" } ], - "@typescript-eslint/prefer-function-type": "warn", "@typescript-eslint/unified-signatures": "error", "@typescript-eslint/no-loss-of-precision": "warn", "@typescript-eslint/no-var-requires": "warn", @@ -114,7 +140,18 @@ "@typescript-eslint/require-await": "warn", "@typescript-eslint/restrict-template-expressions": "warn", "@typescript-eslint/unbound-method": "warn", - "prefer-const": "warn" + "prefer-const": "warn", + + // The following rules are part of @typescript-eslint/stylistic-type-checked + // and can be remove once solved + "@typescript-eslint/consistent-type-definitions": "warn", + "@typescript-eslint/prefer-function-type": "warn", + "@typescript-eslint/no-empty-function": "warn", + "@typescript-eslint/prefer-nullish-coalescing": "warn", // TODO: Requires strictNullChecks: true + "@typescript-eslint/consistent-type-assertions": "warn", + "@typescript-eslint/prefer-optional-chain": "warn", + "@typescript-eslint/consistent-indexed-object-style": "warn", + "@typescript-eslint/consistent-generic-constructors": "warn" } } ], diff --git a/CHANGELOG.md b/CHANGELOG.md index cd7de4e76..2cbea2781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set up a git-hook via `husky` to lint and format the changes before a commit - Added the `typescript-eslint/recommended-type-checked` rule to the `eslint` configuration +- Added the `typescript-eslint/stylistic-type-checked` rule to the `eslint` configuration ### Changed diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json index fa5cdf2a3..1dd30aad1 100644 --- a/apps/client/tsconfig.json +++ b/apps/client/tsconfig.json @@ -15,6 +15,8 @@ ], "angularCompilerOptions": { "strictInjectionParameters": true, + // TODO: Enable stricter rules for this project + "strictInputAccessModifiers": false, "strictTemplates": false }, "compilerOptions": { diff --git a/libs/ui/tsconfig.json b/libs/ui/tsconfig.json index 693f120c7..3ec4903b5 100644 --- a/libs/ui/tsconfig.json +++ b/libs/ui/tsconfig.json @@ -14,11 +14,11 @@ } ], "compilerOptions": { - "forceConsistentCasingInFileNames": true, + "target": "es2020", + // TODO: Remove once solved in tsconfig.base.json "strict": false, "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true, - "target": "es2020" + "noFallthroughCasesInSwitch": true }, "angularCompilerOptions": { "strictInjectionParameters": true, diff --git a/tsconfig.base.json b/tsconfig.base.json index b67024e12..e977a9596 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -20,7 +20,20 @@ "@ghostfolio/client/*": ["apps/client/src/app/*"], "@ghostfolio/common/*": ["libs/common/src/lib/*"], "@ghostfolio/ui/*": ["libs/ui/src/lib/*"] - } + }, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "strict": false, + "strictNullChecks": false, + "strictPropertyInitialization": false, + "noImplicitReturns": false, + "noImplicitAny": false, + "noImplicitThis": false, + "noImplicitOverride": false, + "noPropertyAccessFromIndexSignature": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "allowUnreachableCode": true }, "exclude": ["node_modules", "tmp"] } From bce18f7261e29e4834fb9128ca993a7c75e6fd5b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:05:06 +0200 Subject: [PATCH 21/30] Feature/reuse advanced markets calculation in portfolio details endpoint (#3884) --- .../src/app/portfolio/portfolio.controller.ts | 60 ++++++++++++++++--- .../allocations/allocations-page.component.ts | 40 +++---------- .../interfaces/portfolio-details.interface.ts | 2 +- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index aa3d23644..ff0c31060 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -98,15 +98,22 @@ export class PortfolioController { filterByTags }); - const { accounts, hasErrors, holdings, markets, platforms, summary } = - await this.portfolioService.getDetails({ - dateRange, - filters, - impersonationId, - withMarkets, - userId: this.request.user.id, - withSummary: true - }); + const { + accounts, + hasErrors, + holdings, + markets, + marketsAdvanced, + platforms, + summary + } = await this.portfolioService.getDetails({ + dateRange, + filters, + impersonationId, + withMarkets, + userId: this.request.user.id, + withSummary: true + }); if (hasErrors || hasNotDefinedValuesInObject(holdings)) { hasError = true; @@ -168,6 +175,9 @@ export class PortfolioController { Object.values(markets).forEach((market) => { delete market.valueInBaseCurrency; }); + Object.values(marketsAdvanced).forEach((market) => { + delete market.valueInBaseCurrency; + }); portfolioSummary = nullifyValuesInObject(summary, [ 'cash', @@ -241,6 +251,38 @@ export class PortfolioController { valueInPercentage: 0 } }, + marketsAdvanced: hasDetails + ? marketsAdvanced + : { + [UNKNOWN_KEY]: { + id: UNKNOWN_KEY, + valueInPercentage: 0 + }, + asiaPacific: { + id: 'asiaPacific', + valueInPercentage: 0 + }, + emergingMarkets: { + id: 'emergingMarkets', + valueInPercentage: 0 + }, + europe: { + id: 'europe', + valueInPercentage: 0 + }, + japan: { + id: 'japan', + valueInPercentage: 0 + }, + northAmerica: { + id: 'northAmerica', + valueInPercentage: 0 + }, + otherMarkets: { + id: 'otherMarkets', + valueInPercentage: 0 + } + }, summary: portfolioSummary }; } diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index d7502cbfa..9b0384d39 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -302,6 +302,14 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { this.markets = this.portfolioDetails.markets; + Object.values(this.portfolioDetails.marketsAdvanced).forEach( + ({ id, valueInBaseCurrency, valueInPercentage }) => { + this.marketsAdvanced[id].value = isNumber(valueInBaseCurrency) + ? valueInBaseCurrency + : valueInPercentage; + } + ); + for (const [symbol, position] of Object.entries( this.portfolioDetails.holdings )) { @@ -332,32 +340,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { // Prepare analysis data by continents, countries, holdings and sectors except for liquidity if (position.countries.length > 0) { - this.marketsAdvanced.asiaPacific.value += - position.marketsAdvanced.asiaPacific * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.marketsAdvanced.emergingMarkets.value += - position.marketsAdvanced.emergingMarkets * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.marketsAdvanced.europe.value += - position.marketsAdvanced.europe * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.marketsAdvanced.japan.value += - position.marketsAdvanced.japan * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - this.marketsAdvanced.northAmerica.value += - position.marketsAdvanced.northAmerica * - (isNumber(position.valueInBaseCurrency) - ? position.valueInBaseCurrency - : position.valueInPercentage); - for (const country of position.countries) { const { code, continent, name, weight } = country; @@ -407,12 +389,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { ) ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency : this.portfolioDetails.holdings[symbol].valueInPercentage; - - this.marketsAdvanced[UNKNOWN_KEY].value += isNumber( - position.valueInBaseCurrency - ) - ? this.portfolioDetails.holdings[symbol].valueInBaseCurrency - : this.portfolioDetails.holdings[symbol].valueInPercentage; } if (position.holdings.length > 0) { diff --git a/libs/common/src/lib/interfaces/portfolio-details.interface.ts b/libs/common/src/lib/interfaces/portfolio-details.interface.ts index 711ae424f..e455f73ca 100644 --- a/libs/common/src/lib/interfaces/portfolio-details.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-details.interface.ts @@ -25,7 +25,7 @@ export interface PortfolioDetails { marketsAdvanced?: { [key in MarketAdvanced]: { id: MarketAdvanced; - valueInBaseCurrency: number; + valueInBaseCurrency?: number; valueInPercentage: number; }; }; From adf9a71cbd8549d4f6085363fbc079e68247eff3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:08:10 +0200 Subject: [PATCH 22/30] Release 2.113.0 (#3886) --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cbea2781..54cad4be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.113.0 - 2024-10-06 ### Added diff --git a/package.json b/package.json index 59f66ba50..894b0d192 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.112.0", + "version": "2.113.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 29d3b762a8d6e66abe473adf5a239f3e8a71e421 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Oct 2024 18:49:42 +0200 Subject: [PATCH 23/30] Feature/switch typescript-eslint no-unused-vars to error (#3887) * Switch @typescript-eslint/no-unused-vars to error --- .eslintrc.json | 1 - .../api/src/app/account/account.controller.ts | 15 +++---- apps/api/src/app/account/account.service.ts | 9 +---- .../app/admin/update-bulk-market-data.dto.ts | 2 +- apps/api/src/app/auth/google.strategy.ts | 3 +- .../calculator/mwr/portfolio-calculator.ts | 16 ++------ .../twr/portfolio-calculator.spec.ts | 39 ------------------- .../calculator/twr/portfolio-calculator.ts | 8 +--- .../portfolio/current-rate.service.spec.ts | 2 +- .../src/app/portfolio/portfolio.service.ts | 5 +-- .../redis-cache/redis-cache.service.mock.ts | 4 +- .../src/app/user/update-user-setting.dto.ts | 1 - .../emergency-fund/emergency-fund-setup.ts | 2 +- .../alpha-vantage/alpha-vantage.service.ts | 2 +- .../coingecko/coingecko.service.ts | 2 +- .../yahoo-finance/yahoo-finance.service.ts | 1 - .../eod-historical-data.service.ts | 4 +- .../financial-modeling-prep.service.ts | 2 +- .../google-sheets/google-sheets.service.ts | 2 +- .../data-provider/manual/manual.service.ts | 10 ++--- .../rapid-api/rapid-api.service.ts | 2 +- .../yahoo-finance/yahoo-finance.service.ts | 2 +- .../exchange-rate-data.service.mock.ts | 7 +--- .../portfolio-snapshot.service.mock.ts | 2 - apps/api/src/validators/is-currency-code.ts | 5 +-- .../src/app/adapter/custom-date-adapter.ts | 2 +- .../admin-market-data.service.ts | 2 +- .../home-overview/home-overview.component.ts | 1 - .../core/notification/notification.service.ts | 2 +- .../src/app/pages/faq/faq-page.component.ts | 2 +- .../src/lib/assistant/assistant.component.ts | 1 - .../holdings-table.component.ts | 1 - .../portfolio-proportion-chart.component.ts | 2 +- .../top-holdings/top-holdings.component.ts | 1 - 34 files changed, 38 insertions(+), 124 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c2e6b8079..a3b868692 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -135,7 +135,6 @@ "@typescript-eslint/no-unsafe-enum-comparison": "warn", "@typescript-eslint/no-unsafe-member-access": "warn", "@typescript-eslint/no-unsafe-return": "warn", - "@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unsafe-call": "warn", "@typescript-eslint/require-await": "warn", "@typescript-eslint/restrict-template-expressions": "warn", diff --git a/apps/api/src/app/account/account.controller.ts b/apps/api/src/app/account/account.controller.ts index 44e136793..c0f4dac6a 100644 --- a/apps/api/src/app/account/account.controller.ts +++ b/apps/api/src/app/account/account.controller.ts @@ -74,15 +74,12 @@ export class AccountController { ); } - return this.accountService.deleteAccount( - { - id_userId: { - id, - userId: this.request.user.id - } - }, - this.request.user.id - ); + return this.accountService.deleteAccount({ + id_userId: { + id, + userId: this.request.user.id + } + }); } @Get() diff --git a/apps/api/src/app/account/account.service.ts b/apps/api/src/app/account/account.service.ts index 37876dde0..096caba48 100644 --- a/apps/api/src/app/account/account.service.ts +++ b/apps/api/src/app/account/account.service.ts @@ -108,8 +108,7 @@ export class AccountService { } public async deleteAccount( - where: Prisma.AccountWhereUniqueInput, - aUserId: string + where: Prisma.AccountWhereUniqueInput ): Promise { const account = await this.prismaService.account.delete({ where @@ -170,11 +169,7 @@ export class AccountService { where.isExcluded = false; } - const { - ACCOUNT: filtersByAccount, - ASSET_CLASS: filtersByAssetClass, - TAG: filtersByTag - } = groupBy(filters, ({ type }) => { + const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => { return type; }); diff --git a/apps/api/src/app/admin/update-bulk-market-data.dto.ts b/apps/api/src/app/admin/update-bulk-market-data.dto.ts index 5177263a6..da0da1272 100644 --- a/apps/api/src/app/admin/update-bulk-market-data.dto.ts +++ b/apps/api/src/app/admin/update-bulk-market-data.dto.ts @@ -1,5 +1,5 @@ import { Type } from 'class-transformer'; -import { ArrayNotEmpty, IsArray, isNotEmptyObject } from 'class-validator'; +import { ArrayNotEmpty, IsArray } from 'class-validator'; import { UpdateMarketDataDto } from './update-market-data.dto'; diff --git a/apps/api/src/app/auth/google.strategy.ts b/apps/api/src/app/auth/google.strategy.ts index 4d024603b..ea6772680 100644 --- a/apps/api/src/app/auth/google.strategy.ts +++ b/apps/api/src/app/auth/google.strategy.ts @@ -29,8 +29,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { token: string, refreshToken: string, profile: Profile, - done: Function, - done2: Function + done: Function ) { try { const jwt = await this.authService.validateOAuthLogin({ diff --git a/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts index eb49b7cdb..e54f63422 100644 --- a/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/mwr/portfolio-calculator.ts @@ -3,24 +3,14 @@ import { AssetProfileIdentifier, SymbolMetrics } from '@ghostfolio/common/interfaces'; -import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models'; +import { PortfolioSnapshot } from '@ghostfolio/common/models'; export class MWRPortfolioCalculator extends PortfolioCalculator { - protected calculateOverallPerformance( - positions: TimelinePosition[] - ): PortfolioSnapshot { + protected calculateOverallPerformance(): PortfolioSnapshot { throw new Error('Method not implemented.'); } - protected getSymbolMetrics({ - dataSource, - end, - exchangeRates, - marketSymbolMap, - start, - step = 1, - symbol - }: { + protected getSymbolMetrics({}: { end: Date; exchangeRates: { [dateString: string]: number }; marketSymbolMap: { diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts index d8431cd83..99bee2c21 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.spec.ts @@ -1,42 +1,3 @@ -import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory'; -import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; -import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; -import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service'; - describe('PortfolioCalculator', () => { - let configurationService: ConfigurationService; - let currentRateService: CurrentRateService; - let exchangeRateDataService: ExchangeRateDataService; - let portfolioCalculatorFactory: PortfolioCalculatorFactory; - let portfolioSnapshotService: PortfolioSnapshotService; - let redisCacheService: RedisCacheService; - - beforeEach(() => { - configurationService = new ConfigurationService(); - - currentRateService = new CurrentRateService(null, null, null, null); - - exchangeRateDataService = new ExchangeRateDataService( - null, - null, - null, - null - ); - - portfolioSnapshotService = new PortfolioSnapshotService(null); - - redisCacheService = new RedisCacheService(null, null); - - portfolioCalculatorFactory = new PortfolioCalculatorFactory( - configurationService, - currentRateService, - exchangeRateDataService, - portfolioSnapshotService, - redisCacheService - ); - }); - test.skip('Skip empty test', () => 1); }); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index b26b6fac1..b66bc0d53 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -12,13 +12,7 @@ import { DateRange } from '@ghostfolio/common/types'; import { Logger } from '@nestjs/common'; import { Big } from 'big.js'; -import { - addDays, - addMilliseconds, - differenceInDays, - format, - isBefore -} from 'date-fns'; +import { addMilliseconds, differenceInDays, format, isBefore } from 'date-fns'; import { cloneDeep, first, last, sortBy } from 'lodash'; export class TWRPortfolioCalculator extends PortfolioCalculator { diff --git a/apps/api/src/app/portfolio/current-rate.service.spec.ts b/apps/api/src/app/portfolio/current-rate.service.spec.ts index c86dde448..d0e61c8ce 100644 --- a/apps/api/src/app/portfolio/current-rate.service.spec.ts +++ b/apps/api/src/app/portfolio/current-rate.service.spec.ts @@ -79,7 +79,7 @@ jest.mock('@ghostfolio/api/services/property/property.service', () => { return { PropertyService: jest.fn().mockImplementation(() => { return { - getByKey: (key: string) => Promise.resolve({}) + getByKey: () => Promise.resolve({}) }; }) }; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index e79853e5c..89eeb666d 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1053,8 +1053,7 @@ export class PortfolioService { dateRange = 'max', filters, impersonationId, - userId, - withExcludedAccounts = false + userId }: { dateRange?: DateRange; filters?: Filter[]; @@ -1308,7 +1307,7 @@ export class PortfolioService { } }; - for (const [symbol, position] of Object.entries(holdings)) { + for (const [, position] of Object.entries(holdings)) { const value = position.valueInBaseCurrency; if (position.assetClass !== AssetClass.LIQUIDITY) { diff --git a/apps/api/src/app/redis-cache/redis-cache.service.mock.ts b/apps/api/src/app/redis-cache/redis-cache.service.mock.ts index 2779308bd..feb669ab0 100644 --- a/apps/api/src/app/redis-cache/redis-cache.service.mock.ts +++ b/apps/api/src/app/redis-cache/redis-cache.service.mock.ts @@ -1,7 +1,5 @@ import { Filter } from '@ghostfolio/common/interfaces'; -import { Milliseconds } from 'cache-manager'; - export const RedisCacheServiceMock = { cache: new Map(), get: (key: string): Promise => { @@ -20,7 +18,7 @@ export const RedisCacheServiceMock = { return `portfolio-snapshot-${userId}${filtersHash > 0 ? `-${filtersHash}` : ''}`; }, - set: (key: string, value: string, ttl?: Milliseconds): Promise => { + set: (key: string, value: string): Promise => { RedisCacheServiceMock.cache.set(key, value); return Promise.resolve(value); 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 b4d3edb77..6ea6d7427 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -1,5 +1,4 @@ import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; -import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; import type { ColorScheme, DateRange, diff --git a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts index c59701438..819b8bd7b 100644 --- a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts +++ b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts @@ -18,7 +18,7 @@ export class EmergencyFundSetup extends Rule { this.emergencyFund = emergencyFund; } - public evaluate(ruleSettings: Settings) { + public evaluate() { if (!this.emergencyFund) { return { evaluation: 'No emergency fund has been set up', diff --git a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts index 4e93bc757..016584949 100644 --- a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts +++ b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts @@ -33,7 +33,7 @@ export class AlphaVantageService implements DataProviderInterface { }); } - public canHandle(symbol: string) { + public canHandle() { return !!this.configurationService.get('API_KEY_ALPHA_VANTAGE'); } diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index 067a6fbf9..d420c51fd 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -48,7 +48,7 @@ export class CoinGeckoService implements DataProviderInterface { } } - public canHandle(symbol: string) { + public canHandle() { return true; } diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts index 35fa9604a..1b1335b7e 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts @@ -83,7 +83,6 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { } public async enhance({ - requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), response, symbol }: { diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index 8b2a1828b..cc23344a3 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -43,7 +43,7 @@ export class EodHistoricalDataService implements DataProviderInterface { this.apiKey = this.configurationService.get('API_KEY_EOD_HISTORICAL_DATA'); } - public canHandle(symbol: string) { + public canHandle() { return true; } @@ -163,7 +163,7 @@ export class EodHistoricalDataService implements DataProviderInterface { ).json(); return response.reduce( - (result, { close, date }, index, array) => { + (result, { close, date }) => { if (isNumber(close)) { result[this.convertFromEodSymbol(symbol)][date] = { marketPrice: close 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 cf9c5ef9b..7d5b38479 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 @@ -33,7 +33,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { ); } - public canHandle(symbol: string) { + public canHandle() { return true; } diff --git a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts index c8ff87719..966069f22 100644 --- a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts +++ b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts @@ -29,7 +29,7 @@ export class GoogleSheetsService implements DataProviderInterface { private readonly symbolProfileService: SymbolProfileService ) {} - public canHandle(symbol: string) { + public canHandle() { return true; } diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 02e50a145..030ab8ea2 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -39,7 +39,7 @@ export class ManualService implements DataProviderInterface { private readonly symbolProfileService: SymbolProfileService ) {} - public canHandle(symbol: string) { + public canHandle() { return true; } @@ -86,12 +86,8 @@ export class ManualService implements DataProviderInterface { const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles( [{ symbol, dataSource: this.getName() }] ); - const { - defaultMarketPrice, - headers = {}, - selector, - url - } = symbolProfile?.scraperConfiguration ?? {}; + const { defaultMarketPrice, selector, url } = + symbolProfile?.scraperConfiguration ?? {}; if (defaultMarketPrice) { const historical: { diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts index 7e866552e..e47e96d88 100644 --- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts @@ -26,7 +26,7 @@ export class RapidApiService implements DataProviderInterface { private readonly configurationService: ConfigurationService ) {} - public canHandle(symbol: string) { + public canHandle() { return !!this.configurationService.get('API_KEY_RAPID_API'); } diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index a8f7d261e..2d67c646c 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -34,7 +34,7 @@ export class YahooFinanceService implements DataProviderInterface { private readonly yahooFinanceDataEnhancerService: YahooFinanceDataEnhancerService ) {} - public canHandle(symbol: string) { + public canHandle() { return true; } diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts index 59f5144d8..8f5d1c28a 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.mock.ts @@ -1,10 +1,5 @@ export const ExchangeRateDataServiceMock = { - getExchangeRatesByCurrency: ({ - currencies, - endDate, - startDate, - targetCurrency - }): Promise => { + getExchangeRatesByCurrency: ({ targetCurrency }): Promise => { if (targetCurrency === 'CHF') { return Promise.resolve({ CHFCHF: { diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts index 8d7526906..59fdc5855 100644 --- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts +++ b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts @@ -5,8 +5,6 @@ import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queu export const PortfolioSnapshotServiceMock = { addJobToQueue({ - data, - name, opts }: { data: IPortfolioSnapshotQueueJob; diff --git a/apps/api/src/validators/is-currency-code.ts b/apps/api/src/validators/is-currency-code.ts index 8e8530552..34a82c481 100644 --- a/apps/api/src/validators/is-currency-code.ts +++ b/apps/api/src/validators/is-currency-code.ts @@ -4,8 +4,7 @@ import { registerDecorator, ValidationOptions, ValidatorConstraint, - ValidatorConstraintInterface, - ValidationArguments + ValidatorConstraintInterface } from 'class-validator'; import { isISO4217CurrencyCode } from 'class-validator'; @@ -25,7 +24,7 @@ export function IsCurrencyCode(validationOptions?: ValidationOptions) { export class IsExtendedCurrencyConstraint implements ValidatorConstraintInterface { - public defaultMessage(args: ValidationArguments) { + public defaultMessage() { return '$value must be a valid ISO4217 currency code'; } diff --git a/apps/client/src/app/adapter/custom-date-adapter.ts b/apps/client/src/app/adapter/custom-date-adapter.ts index 724ea7211..a1326b823 100644 --- a/apps/client/src/app/adapter/custom-date-adapter.ts +++ b/apps/client/src/app/adapter/custom-date-adapter.ts @@ -15,7 +15,7 @@ export class CustomDateAdapter extends NativeDateAdapter { /** * Formats a date as a string */ - public format(aDate: Date, aParseFormat: string): string { + public format(aDate: Date): string { return format(aDate, getDateFormatString(this.locale)); } diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index b1d7154e9..9695dc2a2 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -12,7 +12,7 @@ import { } from '@ghostfolio/common/interfaces'; import { Injectable } from '@angular/core'; -import { EMPTY, catchError, finalize, forkJoin, takeUntil } from 'rxjs'; +import { EMPTY, catchError, finalize, forkJoin } from 'rxjs'; @Injectable() export class AdminMarketDataService { diff --git a/apps/client/src/app/components/home-overview/home-overview.component.ts b/apps/client/src/app/components/home-overview/home-overview.component.ts index a32041fce..cd6495a31 100644 --- a/apps/client/src/app/components/home-overview/home-overview.component.ts +++ b/apps/client/src/app/components/home-overview/home-overview.component.ts @@ -11,7 +11,6 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; -import { DateRange } from '@ghostfolio/common/types'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { DeviceDetectorService } from 'ngx-device-detector'; diff --git a/apps/client/src/app/core/notification/notification.service.ts b/apps/client/src/app/core/notification/notification.service.ts index 2e7d9de6c..189da67f5 100644 --- a/apps/client/src/app/core/notification/notification.service.ts +++ b/apps/client/src/app/core/notification/notification.service.ts @@ -33,7 +33,7 @@ export class NotificationService { title: aParams.title }); - return dialog.afterClosed().subscribe((result) => { + return dialog.afterClosed().subscribe(() => { if (isFunction(aParams.discardFn)) { aParams.discardFn(); } diff --git a/apps/client/src/app/pages/faq/faq-page.component.ts b/apps/client/src/app/pages/faq/faq-page.component.ts index 0bbe1c904..1e0d508ae 100644 --- a/apps/client/src/app/pages/faq/faq-page.component.ts +++ b/apps/client/src/app/pages/faq/faq-page.component.ts @@ -1,5 +1,5 @@ import { DataService } from '@ghostfolio/client/services/data.service'; -import { TabConfiguration, User } from '@ghostfolio/common/interfaces'; +import { TabConfiguration } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { Component, OnDestroy, OnInit } from '@angular/core'; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 1da5e682e..c60e93d88 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -36,7 +36,6 @@ import { MatMenuTrigger } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { RouterModule } from '@angular/router'; import { Account, AssetClass } from '@prisma/client'; -import { eachYearOfInterval, format } from 'date-fns'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs'; import { diff --git a/libs/ui/src/lib/holdings-table/holdings-table.component.ts b/libs/ui/src/lib/holdings-table/holdings-table.component.ts index 1f44cf405..257fa2013 100644 --- a/libs/ui/src/lib/holdings-table/holdings-table.component.ts +++ b/libs/ui/src/lib/holdings-table/holdings-table.component.ts @@ -18,7 +18,6 @@ import { Input, OnChanges, OnDestroy, - OnInit, Output, ViewChild } from '@angular/core'; 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 ad11c54f3..d3a28dffe 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 @@ -270,7 +270,7 @@ export class GfPortfolioProportionChartComponent } ]; - let labels = chartDataSorted.map(([symbol, { name }]) => { + let labels = chartDataSorted.map(([, { name }]) => { return name; }); diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts index 38ef3524d..9c4a22cfb 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts @@ -10,7 +10,6 @@ import { Input, OnChanges, OnDestroy, - OnInit, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; From 14b88edff6df4a5ea1dd27e66774f72195d91d60 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:07:07 +0200 Subject: [PATCH 24/30] Feature/extend MSFT buy with dividend test (#3891) * Extend test --- ...io-calculator-msft-buy-with-dividend.spec.ts | 17 +++++++++++++++++ .../app/portfolio/current-rate.service.mock.ts | 2 ++ 2 files changed, 19 insertions(+) diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts index 97b860400..ba1cbeb3c 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator-msft-buy-with-dividend.spec.ts @@ -155,10 +155,27 @@ describe('PortfolioCalculator', () => { dividendInBaseCurrency: new Big('0.62'), fee: new Big('19'), firstBuyDate: '2021-09-16', + grossPerformance: new Big('33.25'), + grossPerformancePercentage: new Big('0.11136043941322258691'), + grossPerformancePercentageWithCurrencyEffect: new Big( + '0.11136043941322258691' + ), + grossPerformanceWithCurrencyEffect: new Big('33.25'), investment: new Big('298.58'), investmentWithCurrencyEffect: new Big('298.58'), marketPrice: 331.83, marketPriceInBaseCurrency: 331.83, + netPerformance: new Big('14.25'), + netPerformancePercentage: new Big('0.04772590260566682296'), + netPerformancePercentageWithCurrencyEffectMap: { + max: new Big('0.04772590260566682296') + }, + netPerformanceWithCurrencyEffectMap: { + '1d': new Big('-5.39'), + '5y': new Big('14.25'), + max: new Big('14.25'), + wtd: new Big('-5.39') + }, quantity: new Big('1'), symbol: 'MSFT', tags: [], diff --git a/apps/api/src/app/portfolio/current-rate.service.mock.ts b/apps/api/src/app/portfolio/current-rate.service.mock.ts index 313b09d67..fab25ae2d 100644 --- a/apps/api/src/app/portfolio/current-rate.service.mock.ts +++ b/apps/api/src/app/portfolio/current-rate.service.mock.ts @@ -65,6 +65,8 @@ function mockGetValue(symbol: string, date: Date) { return { marketPrice: 89.12 }; } else if (isSameDay(parseDate('2021-11-16'), date)) { return { marketPrice: 339.51 }; + } else if (isSameDay(parseDate('2023-07-09'), date)) { + return { marketPrice: 337.22 }; } else if (isSameDay(parseDate('2023-07-10'), date)) { return { marketPrice: 331.83 }; } From 7e339972ad94bfb441dbd57f46c9cb77fa9b60ea Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:49:29 +0200 Subject: [PATCH 25/30] Feature/extend public api with health check endpoint (#3825) * Add health check endpoint documentation * Update changelog --- CHANGELOG.md | 6 ++++++ README.md | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54cad4be4..80b0236d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Extended the _Public API_ with the health check endpoint (experimental) + ## 2.113.0 - 2024-10-06 ### Added diff --git a/README.md b/README.md index 477554749..989cc37ea 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,20 @@ You can get the _Bearer Token_ via `POST http://localhost:3333/api/v1/auth/anony Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/` or `curl -s http://localhost:3333/api/v1/auth/anonymous/`. +### Health Check (experimental) + +#### Request + +`GET http://localhost:3333/api/v1/health` + +**Info:** No Bearer Token is required for health check + +#### Response + +##### Success + +`200 OK` + ### Import Activities #### Prerequisites From fc5ed887ff50fa2b3290589f101caca72bb86f2b Mon Sep 17 00:00:00 2001 From: dw-0 Date: Tue, 8 Oct 2024 15:30:56 +0200 Subject: [PATCH 26/30] Feature/Set prefer-const to error in eslint configuration (#3888) * Set prefer-const to error in eslint configuration * Update changelog --------- Signed-off-by: Dominik Willner --- .eslintrc.json | 1 - CHANGELOG.md | 4 ++ apps/api/src/app/admin/admin.service.ts | 4 +- .../src/app/benchmark/benchmark.service.ts | 2 +- apps/api/src/app/import/import.service.ts | 40 +++++++++---------- .../calculator/portfolio-calculator.ts | 18 ++++----- .../calculator/twr/portfolio-calculator.ts | 11 ++--- .../src/app/portfolio/portfolio.service.ts | 6 ++- apps/api/src/app/user/user.service.ts | 5 ++- .../openfigi/openfigi.service.ts | 2 +- .../data-provider/data-provider.service.ts | 6 ++- .../eod-historical-data.service.ts | 2 +- .../exchange-rate-data.service.ts | 12 +++--- .../account-detail-dialog.component.ts | 2 +- .../holding-detail-dialog.component.ts | 2 +- apps/client/src/app/services/data.service.ts | 2 +- libs/common/src/lib/helper.ts | 2 +- 17 files changed, 66 insertions(+), 55 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index a3b868692..d3f7edea9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -139,7 +139,6 @@ "@typescript-eslint/require-await": "warn", "@typescript-eslint/restrict-template-expressions": "warn", "@typescript-eslint/unbound-method": "warn", - "prefer-const": "warn", // The following rules are part of @typescript-eslint/stylistic-type-checked // and can be remove once solved diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b0236d8..0a43aa44c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the _Public API_ with the health check endpoint (experimental) +### Changed + +- Switched the `prefer-const` rule from `warn` to `error` in the `eslint` configuration + ## 2.113.0 - 2024-10-06 ### Added diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 6c3d7c141..860f19851 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -233,7 +233,7 @@ export class AdminService { const extendedPrismaClient = this.getExtendedPrismaClient(); try { - let [assetProfiles, count] = await Promise.all([ + const symbolProfileResult = await Promise.all([ extendedPrismaClient.symbolProfile.findMany({ orderBy, skip, @@ -264,6 +264,8 @@ export class AdminService { }), this.prismaService.symbolProfile.count({ where }) ]); + const assetProfiles = symbolProfileResult[0]; + let count = symbolProfileResult[1]; const lastMarketPrices = await this.prismaService.marketData.findMany({ distinct: ['dataSource', 'symbol'], diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index f57f5fa30..34f101a8e 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -234,7 +234,7 @@ export class BenchmarkService { return { marketData }; } - for (let marketDataItem of marketDataItems) { + for (const marketDataItem of marketDataItems) { const exchangeRate = exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[ format(marketDataItem.date, DATE_FORMAT) diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index c42cd300b..30415970d 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -266,21 +266,18 @@ export class ImportService { const activities: Activity[] = []; - for (let [ - index, - { - accountId, - comment, - currency, - date, - error, - fee, - quantity, - SymbolProfile, - type, - unitPrice - } - ] of activitiesExtendedWithErrors.entries()) { + for (const [index, activity] of activitiesExtendedWithErrors.entries()) { + const accountId = activity.accountId; + const comment = activity.comment; + const currency = activity.currency; + const date = activity.date; + const error = activity.error; + let fee = activity.fee; + const quantity = activity.quantity; + const SymbolProfile = activity.SymbolProfile; + const type = activity.type; + let unitPrice = activity.unitPrice; + const assetProfile = assetProfiles[ getAssetProfileIdentifier({ dataSource: SymbolProfile.dataSource, @@ -491,12 +488,13 @@ export class ImportService { userCurrency: string; userId: string; }): Promise[]> { - let { activities: existingActivities } = await this.orderService.getOrders({ - userCurrency, - userId, - includeDrafts: true, - withExcludedAccounts: true - }); + const { activities: existingActivities } = + await this.orderService.getOrders({ + userCurrency, + userId, + includeDrafts: true, + withExcludedAccounts: true + }); return activitiesDto.map( ({ diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts index ba3b60237..3d39b80b5 100644 --- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts @@ -217,7 +217,7 @@ export abstract class PortfolioCalculator { } } - let exchangeRatesByCurrency = + const exchangeRatesByCurrency = await this.exchangeRateDataService.getExchangeRatesByCurrency({ currencies: uniq(Object.values(currencies)), endDate: endOfDay(this.endDate), @@ -261,7 +261,7 @@ export abstract class PortfolioCalculator { const daysInMarket = differenceInDays(this.endDate, this.startDate); - let chartDateMap = this.getChartDateMap({ + const chartDateMap = this.getChartDateMap({ endDate: this.endDate, startDate: this.startDate, step: Math.round( @@ -701,9 +701,9 @@ export abstract class PortfolioCalculator { let netPerformanceAtStartDate: number; let netPerformanceWithCurrencyEffectAtStartDate: number; - let totalInvestmentValuesWithCurrencyEffect: number[] = []; + const totalInvestmentValuesWithCurrencyEffect: number[] = []; - for (let historicalDataItem of historicalData) { + for (const historicalDataItem of historicalData) { const date = resetHours(parseDate(historicalDataItem.date)); if (!isBefore(date, start) && !isAfter(date, end)) { @@ -832,13 +832,13 @@ export abstract class PortfolioCalculator { }): { [date: string]: true } { // Create a map of all relevant chart dates: // 1. Add transaction point dates - let chartDateMap = this.transactionPoints.reduce((result, { date }) => { + const chartDateMap = this.transactionPoints.reduce((result, { date }) => { result[date] = true; return result; }, {}); // 2. Add dates between transactions respecting the specified step size - for (let date of eachDayOfInterval( + for (const date of eachDayOfInterval( { end: endDate, start: startDate }, { step } )) { @@ -847,7 +847,7 @@ export abstract class PortfolioCalculator { if (step > 1) { // Reduce the step size of last 90 days - for (let date of eachDayOfInterval( + for (const date of eachDayOfInterval( { end: endDate, start: subDays(endDate, 90) }, { step: 3 } )) { @@ -855,7 +855,7 @@ export abstract class PortfolioCalculator { } // Reduce the step size of last 30 days - for (let date of eachDayOfInterval( + for (const date of eachDayOfInterval( { end: endDate, start: subDays(endDate, 30) }, { step: 1 } )) { @@ -867,7 +867,7 @@ export abstract class PortfolioCalculator { chartDateMap[format(endDate, DATE_FORMAT)] = true; // Make sure some key dates are present - for (let dateRange of ['1d', '1y', '5y', 'max', 'mtd', 'wtd', 'ytd']) { + for (const dateRange of ['1d', '1y', '5y', 'max', 'mtd', 'wtd', 'ytd']) { const { endDate: dateRangeEnd, startDate: dateRangeStart } = getIntervalFromDateRange(dateRange); diff --git a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts index b66bc0d53..950d9e4d3 100644 --- a/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/calculator/twr/portfolio-calculator.ts @@ -27,7 +27,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { let hasErrors = false; let netPerformance = new Big(0); let totalFeesWithCurrencyEffect = new Big(0); - let totalInterestWithCurrencyEffect = new Big(0); + const totalInterestWithCurrencyEffect = new Big(0); let totalInvestment = new Big(0); let totalInvestmentWithCurrencyEffect = new Big(0); let totalTimeWeightedInvestment = new Big(0); @@ -156,7 +156,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { [date: string]: Big; } = {}; - let totalAccountBalanceInBaseCurrency = new Big(0); + const totalAccountBalanceInBaseCurrency = new Big(0); let totalDividend = new Big(0); let totalDividendInBaseCurrency = new Big(0); let totalInterest = new Big(0); @@ -320,7 +320,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { } if (ordersByDate[dateString]?.length > 0) { - for (let order of ordersByDate[dateString]) { + for (const order of ordersByDate[dateString]) { order.unitPriceFromMarketData = marketSymbolMap[dateString]?.[symbol] ?? lastUnitPrice; } @@ -813,8 +813,9 @@ export class TWRPortfolioCalculator extends PortfolioCalculator { // return format(date, 'yyyy'); // }) ]) { - // TODO: getIntervalFromDateRange(dateRange, start) - let { endDate, startDate } = getIntervalFromDateRange(dateRange); + const dateInterval = getIntervalFromDateRange(dateRange); + const endDate = dateInterval.endDate; + let startDate = dateInterval.startDate; if (isBefore(startDate, start)) { startDate = start; diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 89eeb666d..bbc8f437e 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -943,7 +943,9 @@ export class PortfolioService { currency: this.request.user.Settings.settings.baseCurrency }); - let { hasErrors, positions } = await portfolioCalculator.getSnapshot(); + const portfolioSnapshot = await portfolioCalculator.getSnapshot(); + const hasErrors = portfolioSnapshot.hasErrors; + let positions = portfolioSnapshot.positions; positions = positions.filter(({ quantity }) => { return !quantity.eq(0); @@ -1984,7 +1986,7 @@ export class PortfolioService { SymbolProfile, type } of ordersByAccount) { - let currentValueOfSymbolInBaseCurrency = + const currentValueOfSymbolInBaseCurrency = getFactor(type) * quantity * (portfolioItemsNow[SymbolProfile.symbol]?.marketPriceInBaseCurrency ?? diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index a3dfc2fca..c04af5fbf 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -56,7 +56,7 @@ export class UserService { { Account, id, permissions, Settings, subscription }: UserWithSettings, aLocale = locale ): Promise { - let [access, firstActivity, tags] = await Promise.all([ + const accessesResult = await Promise.all([ this.prismaService.access.findMany({ include: { User: true @@ -72,6 +72,9 @@ export class UserService { }), this.tagService.getInUseByUser(id) ]); + const access = accessesResult[0]; + const firstActivity = accessesResult[1]; + let tags = accessesResult[2]; let systemMessage: SystemMessage; diff --git a/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts b/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts index 4d26b31fb..8d2d180e3 100644 --- a/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/openfigi/openfigi.service.ts @@ -43,7 +43,7 @@ export class OpenFigiDataEnhancerService implements DataEnhancerInterface { this.configurationService.get('API_KEY_OPEN_FIGI'); } - let abortController = new AbortController(); + const abortController = new AbortController(); setTimeout(() => { abortController.abort(); diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index bd20541af..385c7b078 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -458,7 +458,9 @@ export class DataProviderService { promises.push( promise.then(async (result) => { - for (let [symbol, dataProviderResponse] of Object.entries(result)) { + for (const [symbol, dataProviderResponse] of Object.entries( + result + )) { if ( [ ...DERIVED_CURRENCIES.map(({ currency }) => { @@ -577,7 +579,7 @@ export class DataProviderService { return { items: lookupItems }; } - let dataProviderServices = this.configurationService + const dataProviderServices = this.configurationService .get('DATA_SOURCES') .map((dataSource) => { return this.getDataProvider(DataSource[dataSource]); diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index cc23344a3..3a840340e 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -203,7 +203,7 @@ export class EodHistoricalDataService implements DataProviderInterface { requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), symbols }: GetQuotesParams): Promise<{ [symbol: string]: IDataProviderResponse }> { - let response: { [symbol: string]: IDataProviderResponse } = {}; + const response: { [symbol: string]: IDataProviderResponse } = {}; if (symbols.length <= 0) { return response; diff --git a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts index 31b2f885c..db95a3487 100644 --- a/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts +++ b/apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts @@ -63,11 +63,11 @@ export class ExchangeRateDataService { return {}; } - let exchangeRatesByCurrency: { + const exchangeRatesByCurrency: { [currency: string]: { [dateString: string]: number }; } = {}; - for (let currency of currencies) { + for (const currency of currencies) { exchangeRatesByCurrency[`${currency}${targetCurrency}`] = await this.getExchangeRates({ startDate, @@ -94,7 +94,7 @@ export class ExchangeRateDataService { !isBefore(date, startDate); date = subDays(resetHours(date), 1) ) { - let dateString = format(date, DATE_FORMAT); + const dateString = format(date, DATE_FORMAT); // Check if the exchange rate for the current date is missing if ( @@ -351,7 +351,7 @@ export class ExchangeRateDataService { startDate: Date; }) { const dates = eachDayOfInterval({ end: endDate, start: startDate }); - let factors: { [dateString: string]: number } = {}; + const factors: { [dateString: string]: number } = {}; if (currencyFrom === currencyTo) { for (const date of dates) { @@ -379,10 +379,10 @@ export class ExchangeRateDataService { } else { // Calculate indirectly via base currency - let marketPriceBaseCurrencyFromCurrency: { + const marketPriceBaseCurrencyFromCurrency: { [dateString: string]: number; } = {}; - let marketPriceBaseCurrencyToCurrency: { + const marketPriceBaseCurrencyToCurrency: { [dateString: string]: number; } = {}; diff --git a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts index d6791760b..d4bdad556 100644 --- a/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts +++ b/apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts @@ -121,7 +121,7 @@ export class AccountDetailDialog implements OnDestroy, OnInit { } public onExport() { - let activityIds = this.dataSource.data.map(({ id }) => { + const activityIds = this.dataSource.data.map(({ id }) => { return id; }); diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index 027c06bd6..b94ba79d5 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -463,7 +463,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { } public onExport() { - let activityIds = this.dataSource.data.map(({ id }) => { + const activityIds = this.dataSource.data.map(({ id }) => { return id; }); diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 4135335c6..b5affeb60 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -287,7 +287,7 @@ export class DataService { } public deleteActivities({ filters }) { - let params = this.buildFiltersAsQueryParams({ filters }); + const params = this.buildFiltersAsQueryParams({ filters }); return this.http.delete(`/api/v1/order`, { params }); } diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 689150b68..005d3d77e 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -135,7 +135,7 @@ export function extractNumberFromString({ // Remove non-numeric characters (excluding international formatting characters) const numericValue = value.replace(/[^\d.,'’\s]/g, ''); - let parser = new NumberParser(locale); + const parser = new NumberParser(locale); return parser.parse(numericValue); } catch { From 98957d282e378312b09c50f933b991a43d07d873 Mon Sep 17 00:00:00 2001 From: Matej Gerek Date: Wed, 9 Oct 2024 21:04:37 +0200 Subject: [PATCH 27/30] Feature/move tags from info to user service (#3859) * Move tags from info to user service * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/info/info.module.ts | 2 - apps/api/src/app/info/info.service.ts | 9 +---- apps/api/src/app/user/user.service.ts | 10 ++--- apps/api/src/services/tag/tag.service.ts | 40 ++++++++++++------- .../holding-detail-dialog.component.ts | 17 ++++---- ...ate-or-update-activity-dialog.component.ts | 16 ++++---- .../src/lib/interfaces/info-item.interface.ts | 3 +- .../src/lib/interfaces/user.interface.ts | 2 +- .../src/lib/assistant/assistant.component.ts | 27 +++++++++---- 10 files changed, 71 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a43aa44c..752fc2397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved the tags from the info to the user service - Switched the `prefer-const` rule from `warn` to `error` in the `eslint` configuration ## 2.113.0 - 2024-10-06 diff --git a/apps/api/src/app/info/info.module.ts b/apps/api/src/app/info/info.module.ts index 9b7854160..7903ac397 100644 --- a/apps/api/src/app/info/info.module.ts +++ b/apps/api/src/app/info/info.module.ts @@ -9,7 +9,6 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; -import { TagModule } from '@ghostfolio/api/services/tag/tag.module'; import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; @@ -33,7 +32,6 @@ import { InfoService } from './info.service'; PropertyModule, RedisCacheModule, SymbolProfileModule, - TagModule, TransformDataSourceInResponseModule, UserModule ], diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index ee225e769..bd291c511 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -5,7 +5,6 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { PropertyService } from '@ghostfolio/api/services/property/property.service'; -import { TagService } from '@ghostfolio/api/services/tag/tag.service'; import { DEFAULT_CURRENCY, PROPERTY_BETTER_UPTIME_MONITOR_ID, @@ -47,7 +46,6 @@ export class InfoService { private readonly platformService: PlatformService, private readonly propertyService: PropertyService, private readonly redisCacheService: RedisCacheService, - private readonly tagService: TagService, private readonly userService: UserService ) {} @@ -103,8 +101,7 @@ export class InfoService { isUserSignupEnabled, platforms, statistics, - subscriptions, - tags + subscriptions ] = await Promise.all([ this.benchmarkService.getBenchmarkAssetProfiles(), this.getDemoAuthToken(), @@ -113,8 +110,7 @@ export class InfoService { orderBy: { name: 'asc' } }), this.getStatistics(), - this.getSubscriptions(), - this.tagService.getPublic() + this.getSubscriptions() ]); if (isUserSignupEnabled) { @@ -130,7 +126,6 @@ export class InfoService { platforms, statistics, subscriptions, - tags, baseCurrency: DEFAULT_CURRENCY, currencies: this.exchangeRateDataService.getCurrencies() }; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index c04af5fbf..0f76b9540 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -56,7 +56,7 @@ export class UserService { { Account, id, permissions, Settings, subscription }: UserWithSettings, aLocale = locale ): Promise { - const accessesResult = await Promise.all([ + const userData = await Promise.all([ this.prismaService.access.findMany({ include: { User: true @@ -70,11 +70,11 @@ export class UserService { }, where: { userId: id } }), - this.tagService.getInUseByUser(id) + this.tagService.getTagsForUser(id) ]); - const access = accessesResult[0]; - const firstActivity = accessesResult[1]; - let tags = accessesResult[2]; + const access = userData[0]; + const firstActivity = userData[1]; + let tags = userData[2]; let systemMessage: SystemMessage; diff --git a/apps/api/src/services/tag/tag.service.ts b/apps/api/src/services/tag/tag.service.ts index 5426a0582..b16f22fbb 100644 --- a/apps/api/src/services/tag/tag.service.ts +++ b/apps/api/src/services/tag/tag.service.ts @@ -6,29 +6,39 @@ import { Injectable } from '@nestjs/common'; export class TagService { public constructor(private readonly prismaService: PrismaService) {} - public async getPublic() { - return this.prismaService.tag.findMany({ - orderBy: { - name: 'asc' + public async getTagsForUser(userId: string) { + const tags = await this.prismaService.tag.findMany({ + include: { + _count: { + select: { + orders: { + where: { + userId + } + } + } + } }, - where: { - userId: null - } - }); - } - - public async getInUseByUser(userId: string) { - return this.prismaService.tag.findMany({ orderBy: { name: 'asc' }, where: { - orders: { - some: { + OR: [ + { userId + }, + { + userId: null } - } + ] } }); + + return tags.map(({ _count, id, name, userId }) => ({ + id, + name, + userId, + isUsed: _count.orders > 0 + })); } } diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index b94ba79d5..792ec6f9c 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -147,8 +147,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { ) {} public ngOnInit() { - const { tags } = this.dataService.fetchInfo(); - this.activityForm = this.formBuilder.group({ tags: [] }); @@ -158,13 +156,6 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { { id: this.data.symbol, type: 'SYMBOL' } ]; - this.tagsAvailable = tags.map((tag) => { - return { - ...tag, - name: translate(tag.name) - }; - }); - this.activityForm .get('tags') .valueChanges.pipe(takeUntil(this.unsubscribeSubject)) @@ -434,6 +425,14 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; + this.tagsAvailable = + this.user?.tags?.map((tag) => { + return { + ...tag, + name: translate(tag.name) + }; + }) ?? []; + this.changeDetectorRef.markForCheck(); } }); diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 4690ab6a8..cb21c255d 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -76,17 +76,19 @@ export class CreateOrUpdateActivityDialog implements OnDestroy { this.locale = this.data.user?.settings?.locale; this.dateAdapter.setLocale(this.locale); - const { currencies, platforms, tags } = this.dataService.fetchInfo(); + const { currencies, platforms } = this.dataService.fetchInfo(); this.currencies = currencies; this.defaultDateFormat = getDateFormatString(this.locale); this.platforms = platforms; - this.tagsAvailable = tags.map((tag) => { - return { - ...tag, - name: translate(tag.name) - }; - }); + + this.tagsAvailable = + this.data.user?.tags?.map((tag) => { + return { + ...tag, + name: translate(tag.name) + }; + }) ?? []; Object.keys(Type).forEach((type) => { this.typesTranslationMap[Type[type]] = translate(Type[type]); diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index d279c74a4..1b3926331 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,6 +1,6 @@ import { SubscriptionOffer } from '@ghostfolio/common/types'; -import { Platform, SymbolProfile, Tag } from '@prisma/client'; +import { Platform, SymbolProfile } from '@prisma/client'; import { Statistics } from './statistics.interface'; import { Subscription } from './subscription.interface'; @@ -19,5 +19,4 @@ export interface InfoItem { statistics: Statistics; stripePublicKey?: string; subscriptions: { [offer in SubscriptionOffer]: Subscription }; - tags: Tag[]; } diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index 2891314a0..27cd1a610 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -23,5 +23,5 @@ export interface User { offer: SubscriptionOffer; type: SubscriptionType; }; - tags: Tag[]; + tags: (Tag & { isUsed: boolean })[]; } diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index c60e93d88..c93d04303 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -156,7 +156,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ) {} public ngOnInit() { - this.accounts = this.user?.accounts; this.assetClasses = Object.keys(AssetClass).map((assetClass) => { return { id: assetClass, @@ -164,13 +163,6 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { type: 'ASSET_CLASS' }; }); - this.tags = this.user?.tags.map(({ id, name }) => { - return { - id, - label: translate(name), - type: 'TAG' - }; - }); this.searchFormControl.valueChanges .pipe( @@ -212,6 +204,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { } public ngOnChanges() { + this.accounts = this.user?.accounts ?? []; + this.dateRangeOptions = [ { label: $localize`Today`, value: '1d' }, { @@ -279,6 +273,23 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { emitEvent: false } ); + + this.tags = + this.user?.tags + ?.filter(({ isUsed }) => { + return isUsed; + }) + .map(({ id, name }) => { + return { + id, + label: translate(name), + type: 'TAG' + }; + }) ?? []; + + if (this.tags.length === 0) { + this.filterForm.get('tag').disable({ emitEvent: false }); + } } public hasFilter(aFormValue: { [key: string]: string }) { From 73cd2f9cb7192564f8ff2bbd60b1f2737f614090 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 10 Oct 2024 20:27:33 +0200 Subject: [PATCH 28/30] Bugfix/fix exception in portfolio details endpoint (#3896) * Fix exception caused by markets and marketsAdvanced * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/app/endpoints/public/public.controller.ts | 2 +- apps/api/src/app/portfolio/portfolio.controller.ts | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 752fc2397..58dd3fdbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved the tags from the info to the user service - Switched the `prefer-const` rule from `warn` to `error` in the `eslint` configuration +### Fixed + +- Fixed an exception in the portfolio details endpoint caused by a calculation of the allocations by market + ## 2.113.0 - 2024-10-06 ### Added diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index 9399f97bf..7488e4201 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -76,7 +76,7 @@ export class PublicController { }) ]); - Object.values(markets).forEach((market) => { + Object.values(markets ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index ff0c31060..326dda151 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -172,10 +172,10 @@ export class PortfolioController { }) || isRestrictedView(this.request.user) ) { - Object.values(markets).forEach((market) => { + Object.values(markets ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); - Object.values(marketsAdvanced).forEach((market) => { + Object.values(marketsAdvanced ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); From 7252a54ae059889d5fbb3adb4d744fff9fa6c859 Mon Sep 17 00:00:00 2001 From: Uday R <82779467+uday-rana@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:06:17 -0400 Subject: [PATCH 29/30] Feature/Set up tooltip in treemap chart component (#3897) * Set up tooltip in treemap chart component * Update changelog --- CHANGELOG.md | 1 + .../home-holdings/home-holdings.html | 3 ++ .../treemap-chart/treemap-chart.component.ts | 48 ++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58dd3fdbc..0d6ba5d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added a tooltip to the chart of the holdings tab on the home page (experimental) - Extended the _Public API_ with the health check endpoint (experimental) ### Changed diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index 01afe31a2..f1c4e7e88 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -38,8 +38,11 @@ } 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 8915707fa..b278180ea 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -2,11 +2,13 @@ import { getAnnualizedPerformancePercent, getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; +import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; +import { getLocale } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, PortfolioPosition } from '@ghostfolio/common/interfaces'; -import { DateRange } from '@ghostfolio/common/types'; +import { ColorScheme, DateRange } from '@ghostfolio/common/types'; import { CommonModule } from '@angular/common'; import { @@ -25,7 +27,7 @@ import { DataSource } from '@prisma/client'; import { Big } from 'big.js'; import { ChartConfiguration } from 'chart.js'; import { LinearScale } from 'chart.js'; -import { Chart } from 'chart.js'; +import { Chart, Tooltip } from 'chart.js'; import { TreemapController, TreemapElement } from 'chartjs-chart-treemap'; import { differenceInDays, max } from 'date-fns'; import { orderBy } from 'lodash'; @@ -44,9 +46,12 @@ const { gray, green, red } = require('open-color'); export class GfTreemapChartComponent implements AfterViewInit, OnChanges, OnDestroy { + @Input() baseCurrency: string; + @Input() colorScheme: ColorScheme; @Input() cursor: string; @Input() dateRange: DateRange; @Input() holdings: PortfolioPosition[]; + @Input() locale = getLocale(); @Output() treemapChartClicked = new EventEmitter(); @@ -58,7 +63,7 @@ export class GfTreemapChartComponent public isLoading = true; public constructor() { - Chart.register(LinearScale, TreemapController, TreemapElement); + Chart.register(LinearScale, Tooltip, TreemapController, TreemapElement); } public ngAfterViewInit() { @@ -168,6 +173,9 @@ export class GfTreemapChartComponent if (this.chartCanvas) { if (this.chart) { this.chart.data = data; + this.chart.options.plugins.tooltip = ( + this.getTooltipPluginConfiguration() + ); this.chart.update(); } else { this.chart = new Chart(this.chartCanvas.nativeElement, { @@ -199,9 +207,7 @@ export class GfTreemapChartComponent } }, plugins: { - tooltip: { - enabled: false - } + tooltip: this.getTooltipPluginConfiguration() } }, type: 'treemap' @@ -211,4 +217,34 @@ export class GfTreemapChartComponent this.isLoading = false; } + + private getTooltipPluginConfiguration() { + return { + ...getTooltipOptions({ + colorScheme: this.colorScheme, + currency: this.baseCurrency, + locale: this.locale + }), + callbacks: { + label: (context) => { + if (context.raw._data.valueInBaseCurrency !== null) { + const value = context.raw._data.valueInBaseCurrency; + return `${value.toLocaleString(this.locale, { + maximumFractionDigits: 2, + minimumFractionDigits: 2 + })} ${this.baseCurrency}`; + } else { + const percentage = + context.raw._data.allocationInPercentage * 100; + return `${percentage.toFixed(2)}%`; + } + }, + title: () => { + return ''; + } + }, + xAlign: 'center', + yAlign: 'center' + }; + } } From f5c0d803a06ac36886657491fbf0fd9afb480425 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:09:58 +0200 Subject: [PATCH 30/30] Release 2.114.0 (#3905) --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d6ba5d26..e66630572 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 2.114.0 - 2024-10-10 ### Added diff --git a/package.json b/package.json index 894b0d192..e283d6a23 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.113.0", + "version": "2.114.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio",