From 76890e63fafb7795b5bc8f8e0933dcceeccd67bb Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:03:45 +0200 Subject: [PATCH 1/5] Bugfix/fix all time high in benchmarks (#3527) * Fix all time high * Update changelog --- CHANGELOG.md | 6 ++++++ apps/api/src/app/benchmark/benchmark.service.ts | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 514f89393..c52c8589c 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 + +- Fixed an issue with the all time high in the benchmarks of the markets overview + ## 2.91.0 - 2024-06-26 ### Added diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index f4f2d7848..6008746d1 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -161,7 +161,10 @@ export class BenchmarkService { performances: { allTimeHigh: { date: allTimeHigh?.date, - performancePercent: performancePercentFromAllTimeHigh + performancePercent: + performancePercentFromAllTimeHigh >= 0 + ? 0 + : performancePercentFromAllTimeHigh } }, symbol: benchmarkAssetProfiles[index].symbol, @@ -419,7 +422,7 @@ export class BenchmarkService { private getMarketCondition( aPerformanceInPercent: number ): Benchmark['marketCondition'] { - if (aPerformanceInPercent === 0) { + if (aPerformanceInPercent >= 0) { return 'ALL_TIME_HIGH'; } else if (aPerformanceInPercent <= -0.2) { return 'BEAR_MARKET'; From 4d3dff3e5bbec7e9a84060f231100ddf42d1967d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:53:08 +0200 Subject: [PATCH 2/5] Feature/extend personal finance tools 20240629 (#3528) * Add Anlage.App * Add Portfoloo * Add SharesMaster * Add Merlin * Add Holistic * Add AlphaTrackr * Add Segmio --- libs/common/src/lib/personal-finance-tools.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/libs/common/src/lib/personal-finance-tools.ts b/libs/common/src/lib/personal-finance-tools.ts index 6b61d77d8..8a20ca15c 100644 --- a/libs/common/src/lib/personal-finance-tools.ts +++ b/libs/common/src/lib/personal-finance-tools.ts @@ -18,6 +18,13 @@ export const personalFinanceTools: Product[] = [ origin: `United States`, slogan: 'Investment Software Suite' }, + { + founded: 2016, + key: 'alphatrackr', + languages: ['English'], + name: 'AlphaTrackr', + slogan: 'Investment Portfolio Tracking Tool' + }, { founded: 2017, hasSelfHostingAbility: false, @@ -26,6 +33,17 @@ export const personalFinanceTools: Product[] = [ origin: `Switzerland`, slogan: 'Simplicity for Complex Wealth' }, + { + founded: 2018, + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'anlage.app', + languages: ['English'], + name: 'Anlage.App', + origin: `Austria`, + pricingPerYear: '$120', + slogan: 'Analyze and track your portfolio.' + }, { founded: 2022, hasFreePlan: true, @@ -190,6 +208,16 @@ export const personalFinanceTools: Product[] = [ origin: `Germany`, slogan: 'Volle Kontrolle über deine Investitionen' }, + { + hasFreePlan: true, + hasSelfHostingAbility: false, + key: 'holistic-capital', + languages: ['Deutsch'], + name: 'Holistic', + origin: `Germany`, + slogan: 'Die All-in-One Lösung für dein Vermögen.', + useAnonymously: true + }, { hasFreePlan: true, hasSelfHostingAbility: false, @@ -264,6 +292,17 @@ export const personalFinanceTools: Product[] = [ region: `United States`, slogan: 'Your financial future, in your control' }, + { + hasFreePlan: false, + hasSelfHostingAbility: false, + key: 'merlincrypto', + languages: ['English'], + name: 'Merlin', + origin: `United States`, + pricingPerYear: '$204', + region: 'Canada, United States', + slogan: 'The smartest way to track your crypto' + }, { founded: 2019, hasFreePlan: false, @@ -331,6 +370,14 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$360', slogan: 'Tools for Better Investors' }, + { + hasFreePlan: true, + key: 'portfoloo', + name: 'Portfoloo', + note: 'Portfoloo has discontinued', + slogan: + 'Free Stock Portfolio Tracker with unlimited portfolio and stocks for DIY investors' + }, { founded: 2021, hasFreePlan: true, @@ -370,6 +417,13 @@ export const personalFinanceTools: Product[] = [ pricingPerYear: '$239', slogan: 'Stock Market Analysis & Tools for Investors' }, + { + founded: 2022, + key: 'segmio', + name: 'Segmio', + origin: `Romania`, + slogan: 'Wealth Management and Net Worth Tracking' + }, { founded: 2007, hasFreePlan: true, @@ -381,6 +435,13 @@ export const personalFinanceTools: Product[] = [ region: `Global`, slogan: 'Stock Portfolio Tracker' }, + { + hasFreePlan: true, + key: 'sharesmaster', + name: 'SharesMaster', + note: 'SharesMaster has discontinued', + slogan: 'Free Stock Portfolio Tracker' + }, { hasFreePlan: true, hasSelfHostingAbility: false, From 8386fec98a552fa90eb138d0dd8999be9d5b1aba Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:53:25 +0200 Subject: [PATCH 3/5] Feature/automatic deletion of unused asset profiles (#3525) * Automatic deletion of unused asset profiles * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/app/order/order.controller.ts | 1 - apps/api/src/app/order/order.service.ts | 27 ++++++++++++++++++---- apps/api/src/app/user/user.module.ts | 2 ++ apps/api/src/app/user/user.service.ts | 6 +++-- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52c8589c..8bd423470 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 +### Changed + +- Added support for automatic deletion of unused asset profiles when deleting activities + ### Fixed - Fixed an issue with the all time high in the benchmarks of the markets overview diff --git a/apps/api/src/app/order/order.controller.ts b/apps/api/src/app/order/order.controller.ts index f3709abae..f9190d1eb 100644 --- a/apps/api/src/app/order/order.controller.ts +++ b/apps/api/src/app/order/order.controller.ts @@ -66,7 +66,6 @@ export class OrderController { return this.orderService.deleteOrders({ filters, - userCurrency: this.request.user.Settings.settings.baseCurrency, userId: this.request.user.id }); } diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 7a4dd5a4a..a1fa4db6c 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -184,7 +184,15 @@ export class OrderService { where }); - if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(order.type)) { + const [symbolProfile] = + await this.symbolProfileService.getSymbolProfilesByIds([ + order.symbolProfileId + ]); + + if ( + ['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(order.type) || + symbolProfile.activitiesCount === 0 + ) { await this.symbolProfileService.deleteById(order.symbolProfileId); } @@ -200,18 +208,16 @@ export class OrderService { public async deleteOrders({ filters, - userCurrency, userId }: { filters?: Filter[]; - userCurrency: string; userId: string; }): Promise { const { activities } = await this.getOrders({ filters, userId, - userCurrency, includeDrafts: true, + userCurrency: undefined, withExcludedAccounts: true }); @@ -225,6 +231,19 @@ export class OrderService { } }); + const symbolProfiles = + await this.symbolProfileService.getSymbolProfilesByIds( + activities.map(({ symbolProfileId }) => { + return symbolProfileId; + }) + ); + + for (const { activitiesCount, id } of symbolProfiles) { + if (activitiesCount === 0) { + await this.symbolProfileService.deleteById(id); + } + } + this.eventEmitter.emit( PortfolioChangedEvent.getName(), new PortfolioChangedEvent({ userId }) diff --git a/apps/api/src/app/user/user.module.ts b/apps/api/src/app/user/user.module.ts index 35f78dc5f..063cfef82 100644 --- a/apps/api/src/app/user/user.module.ts +++ b/apps/api/src/app/user/user.module.ts @@ -1,3 +1,4 @@ +import { OrderModule } from '@ghostfolio/api/app/order/order.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; @@ -19,6 +20,7 @@ import { UserService } from './user.service'; secret: process.env.JWT_SECRET_KEY, signOptions: { expiresIn: '30 days' } }), + OrderModule, PrismaModule, PropertyModule, SubscriptionModule, diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 51bd4c4db..ce0ec5524 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -1,3 +1,4 @@ +import { OrderService } from '@ghostfolio/api/app/order/order.service'; import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service'; import { environment } from '@ghostfolio/api/environments/environment'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; @@ -40,6 +41,7 @@ export class UserService { public constructor( private readonly configurationService: ConfigurationService, private readonly eventEmitter: EventEmitter2, + private readonly orderService: OrderService, private readonly prismaService: PrismaService, private readonly propertyService: PropertyService, private readonly subscriptionService: SubscriptionService, @@ -398,8 +400,8 @@ export class UserService { } catch {} try { - await this.prismaService.order.deleteMany({ - where: { userId: where.id } + await this.orderService.deleteOrders({ + userId: where.id }); } catch {} From f08b0b570b6651a9e2787ee845957f65d0d36410 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Jun 2024 16:30:40 +0200 Subject: [PATCH 4/5] Feature/support derived currencies in currency validation (#3529) * Support derived currencies in currency validation * Update changelog --- CHANGELOG.md | 1 + .../api/src/app/account/create-account.dto.ts | 5 ++- .../api/src/app/account/update-account.dto.ts | 5 ++- .../src/app/admin/update-asset-profile.dto.ts | 5 ++- apps/api/src/app/order/create-order.dto.ts | 6 +-- apps/api/src/app/order/update-order.dto.ts | 6 +-- .../src/app/user/update-user-setting.dto.ts | 4 +- apps/api/src/validators/is-currency-code.ts | 44 +++++++++++++++++++ test/import/ok-derived-currency.json | 30 +++++++++++++ 9 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 apps/api/src/validators/is-currency-code.ts create mode 100644 test/import/ok-derived-currency.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bd423470..0ba81e462 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 ### Changed +- Added support for derived currencies in the currency validation - Added support for automatic deletion of unused asset profiles when deleting activities ### Fixed diff --git a/apps/api/src/app/account/create-account.dto.ts b/apps/api/src/app/account/create-account.dto.ts index b719c2619..f3c88316f 100644 --- a/apps/api/src/app/account/create-account.dto.ts +++ b/apps/api/src/app/account/create-account.dto.ts @@ -1,7 +1,8 @@ +import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; + import { Transform, TransformFnParams } from 'class-transformer'; import { IsBoolean, - IsISO4217CurrencyCode, IsNumber, IsOptional, IsString, @@ -20,7 +21,7 @@ export class CreateAccountDto { ) comment?: string; - @IsISO4217CurrencyCode() + @IsCurrencyCode() currency: string; @IsOptional() diff --git a/apps/api/src/app/account/update-account.dto.ts b/apps/api/src/app/account/update-account.dto.ts index 1b84a9bf6..6b87af71b 100644 --- a/apps/api/src/app/account/update-account.dto.ts +++ b/apps/api/src/app/account/update-account.dto.ts @@ -1,7 +1,8 @@ +import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; + import { Transform, TransformFnParams } from 'class-transformer'; import { IsBoolean, - IsISO4217CurrencyCode, IsNumber, IsOptional, IsString, @@ -20,7 +21,7 @@ export class UpdateAccountDto { ) comment?: string; - @IsISO4217CurrencyCode() + @IsCurrencyCode() currency: string; @IsString() diff --git a/apps/api/src/app/admin/update-asset-profile.dto.ts b/apps/api/src/app/admin/update-asset-profile.dto.ts index e3de3cab1..8c9ae220b 100644 --- a/apps/api/src/app/admin/update-asset-profile.dto.ts +++ b/apps/api/src/app/admin/update-asset-profile.dto.ts @@ -1,8 +1,9 @@ +import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; + import { AssetClass, AssetSubClass, Prisma } from '@prisma/client'; import { IsArray, IsEnum, - IsISO4217CurrencyCode, IsObject, IsOptional, IsString, @@ -26,7 +27,7 @@ export class UpdateAssetProfileDto { @IsOptional() countries?: Prisma.InputJsonArray; - @IsISO4217CurrencyCode() + @IsCurrencyCode() @IsOptional() currency?: string; diff --git a/apps/api/src/app/order/create-order.dto.ts b/apps/api/src/app/order/create-order.dto.ts index 72aba3a3b..6f52e7032 100644 --- a/apps/api/src/app/order/create-order.dto.ts +++ b/apps/api/src/app/order/create-order.dto.ts @@ -1,3 +1,4 @@ +import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; import { @@ -12,7 +13,6 @@ import { IsArray, IsBoolean, IsEnum, - IsISO4217CurrencyCode, IsISO8601, IsNumber, IsOptional, @@ -42,10 +42,10 @@ export class CreateOrderDto { ) comment?: string; - @IsISO4217CurrencyCode() + @IsCurrencyCode() currency: string; - @IsISO4217CurrencyCode() + @IsCurrencyCode() @IsOptional() customCurrency?: string; diff --git a/apps/api/src/app/order/update-order.dto.ts b/apps/api/src/app/order/update-order.dto.ts index 2fd33b743..eabd1f418 100644 --- a/apps/api/src/app/order/update-order.dto.ts +++ b/apps/api/src/app/order/update-order.dto.ts @@ -1,3 +1,4 @@ +import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970'; import { @@ -11,7 +12,6 @@ import { Transform, TransformFnParams } from 'class-transformer'; import { IsArray, IsEnum, - IsISO4217CurrencyCode, IsISO8601, IsNumber, IsOptional, @@ -41,10 +41,10 @@ export class UpdateOrderDto { ) comment?: string; - @IsISO4217CurrencyCode() + @IsCurrencyCode() currency: string; - @IsISO4217CurrencyCode() + @IsCurrencyCode() @IsOptional() customCurrency?: string; 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 d260b3aaf..1fc02ff4d 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -1,3 +1,4 @@ +import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import type { ColorScheme, DateRange, @@ -7,7 +8,6 @@ import type { import { IsArray, IsBoolean, - IsISO4217CurrencyCode, IsISO8601, IsIn, IsNumber, @@ -21,7 +21,7 @@ export class UpdateUserSettingDto { @IsOptional() annualInterestRate?: number; - @IsISO4217CurrencyCode() + @IsCurrencyCode() @IsOptional() baseCurrency?: string; diff --git a/apps/api/src/validators/is-currency-code.ts b/apps/api/src/validators/is-currency-code.ts new file mode 100644 index 000000000..8e8530552 --- /dev/null +++ b/apps/api/src/validators/is-currency-code.ts @@ -0,0 +1,44 @@ +import { DERIVED_CURRENCIES } from '@ghostfolio/common/config'; + +import { + registerDecorator, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, + ValidationArguments +} from 'class-validator'; +import { isISO4217CurrencyCode } from 'class-validator'; + +export function IsCurrencyCode(validationOptions?: ValidationOptions) { + return function (object: Object, propertyName: string) { + registerDecorator({ + propertyName, + constraints: [], + options: validationOptions, + target: object.constructor, + validator: IsExtendedCurrencyConstraint + }); + }; +} + +@ValidatorConstraint({ async: false }) +export class IsExtendedCurrencyConstraint + implements ValidatorConstraintInterface +{ + public defaultMessage(args: ValidationArguments) { + return '$value must be a valid ISO4217 currency code'; + } + + public validate(currency: any) { + // Return true if currency is a standard ISO 4217 code or a derived currency + return ( + isISO4217CurrencyCode(currency) || + [ + ...DERIVED_CURRENCIES.map((derivedCurrency) => { + return derivedCurrency.currency; + }), + 'USX' + ].includes(currency) + ); + } +} diff --git a/test/import/ok-derived-currency.json b/test/import/ok-derived-currency.json new file mode 100644 index 000000000..b43be395d --- /dev/null +++ b/test/import/ok-derived-currency.json @@ -0,0 +1,30 @@ +{ + "meta": { + "date": "2024-06-28T00:00:00.000Z", + "version": "dev" + }, + "accounts": [ + { + "balance": 2000, + "currency": "USD", + "id": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", + "isExcluded": false, + "name": "My Online Trading Account", + "platformId": null + } + ], + "activities": [ + { + "accountId": "b2d3fe1d-d6a8-41a3-be39-07ef5e9480f0", + "comment": null, + "fee": 0, + "quantity": 5, + "type": "BUY", + "unitPrice": 10875.00, + "currency": "ZAc", + "dataSource": "YAHOO", + "date": "2024-06-27T22:00:00.000Z", + "symbol": "JSE.JO" + } + ] +} From dcec3accf076d1c6c4a5f6a722e1213c21eb9230 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 29 Jun 2024 16:53:35 +0200 Subject: [PATCH 5/5] Feature/improve caching of benchmarks (#3530) * Improve caching * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/benchmark/benchmark.service.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba81e462..a720c17bc 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 - Added support for derived currencies in the currency validation - Added support for automatic deletion of unused asset profiles when deleting activities +- Improved the caching of the benchmarks in the markets overview (only cache if needed) ### Fixed diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index 6008746d1..27d91fd7d 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -135,7 +135,7 @@ export class BenchmarkService { Promise.all(promisesAllTimeHighs), Promise.all(promisesBenchmarkTrends) ]); - let storeInCache = true; + let storeInCache = useCache; benchmarks = allTimeHighs.map((allTimeHigh, index) => { const { marketPrice } =