diff --git a/.env.dev b/.env.dev new file mode 100644 index 000000000..c4c8a0d35 --- /dev/null +++ b/.env.dev @@ -0,0 +1,25 @@ +COMPOSE_PROJECT_NAME=ghostfolio-development + +# CACHE +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +# POSTGRES +POSTGRES_DB=ghostfolio-db +POSTGRES_USER=user +POSTGRES_PASSWORD= + +# VARIOUS +ACCESS_TOKEN_SALT= +DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer +JWT_SECRET_KEY= + +# DEVELOPMENT + +# Nx 18 enables using plugins to infer targets by default +# This is disabled for existing workspaces to maintain compatibility +# For more info, see: https://nx.dev/concepts/inferred-tasks +NX_ADD_PLUGINS=false + +NX_NATIVE_COMMAND_RUNNER=false diff --git a/.env.example b/.env.example index 8df547e37..766894992 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -COMPOSE_PROJECT_NAME=ghostfolio-development +COMPOSE_PROJECT_NAME=ghostfolio # CACHE REDIS_HOST=localhost @@ -10,6 +10,7 @@ POSTGRES_DB=ghostfolio-db POSTGRES_USER=user POSTGRES_PASSWORD= +# VARIOUS ACCESS_TOKEN_SALT= DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer JWT_SECRET_KEY= diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ff0203d0..4717f7f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added a toggle to switch between active and closed holdings on the portfolio holdings page +- Added support to update the cash balance of an account when adding a fee activity +- Added support to update the cash balance of an account when adding an interest activity +- Extended the content of the _General_ section by the product roadmap on the Frequently Asked Questions (FAQ) page + +### Changed + +- Improved the usability of the platform management in the admin control panel +- Improved the usability of the tag management in the admin control panel + +### Fixed + +- Fixed an issue in the dividend calculation of the portfolio holdings +- Fixed the date conversion of the import of historical market data in the admin control panel ## 2.63.2 - 2024-03-12 diff --git a/README.md b/README.md index eb302936e..9602d88c9 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Ghostfolio is available for various home server systems, including [CasaOS](http - [Node.js](https://nodejs.org/en/download) (version 18+) - [Yarn](https://yarnpkg.com/en/docs/install) - Create a local copy of this Git repository (clone) -- Copy the file `.env.example` to `.env` and populate it with your data (`cp .env.example .env`) +- Copy the file `.env.dev` to `.env` and populate it with your data (`cp .env.dev .env`) ### Setup diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 1603ed530..3efb5fe95 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -148,7 +148,7 @@ export class OrderService { .plus(data.fee) .toNumber(); - if (data.type === 'BUY') { + if (['BUY', 'FEE'].includes(data.type)) { amount = new Big(amount).mul(-1).toNumber(); } diff --git a/apps/api/src/app/portfolio/portfolio-calculator.ts b/apps/api/src/app/portfolio/portfolio-calculator.ts index faf954bbd..91d91309f 100644 --- a/apps/api/src/app/portfolio/portfolio-calculator.ts +++ b/apps/api/src/app/portfolio/portfolio-calculator.ts @@ -602,8 +602,6 @@ export class PortfolioCalculator { ); const { - dividend, - dividendInBaseCurrency, grossPerformance, grossPerformancePercentage, grossPerformancePercentageWithCurrencyEffect, @@ -615,6 +613,8 @@ export class PortfolioCalculator { netPerformanceWithCurrencyEffect, timeWeightedInvestment, timeWeightedInvestmentWithCurrencyEffect, + totalDividend, + totalDividendInBaseCurrency, totalInvestment, totalInvestmentWithCurrencyEffect } = this.getSymbolMetrics({ @@ -629,8 +629,8 @@ export class PortfolioCalculator { hasAnySymbolMetricsErrors = hasAnySymbolMetricsErrors || hasErrors; positions.push({ - dividend, - dividendInBaseCurrency, + dividend: totalDividend, + dividendInBaseCurrency: totalDividendInBaseCurrency, timeWeightedInvestment, timeWeightedInvestmentWithCurrencyEffect, averagePrice: item.averagePrice, @@ -861,8 +861,6 @@ export class PortfolioCalculator { const currentExchangeRate = exchangeRates[format(new Date(), DATE_FORMAT)]; const currentValues: { [date: string]: Big } = {}; const currentValuesWithCurrencyEffect: { [date: string]: Big } = {}; - let dividend = new Big(0); - let dividendInBaseCurrency = new Big(0); let fees = new Big(0); let feesAtStartDate = new Big(0); let feesAtStartDateWithCurrencyEffect = new Big(0); @@ -892,6 +890,8 @@ export class PortfolioCalculator { [date: string]: Big; } = {}; + let totalDividend = new Big(0); + let totalDividendInBaseCurrency = new Big(0); let totalInvestment = new Big(0); let totalInvestmentFromBuyTransactions = new Big(0); let totalInvestmentFromBuyTransactionsWithCurrencyEffect = new Big(0); @@ -912,8 +912,6 @@ export class PortfolioCalculator { return { currentValues: {}, currentValuesWithCurrencyEffect: {}, - dividend: new Big(0), - dividendInBaseCurrency: new Big(0), grossPerformance: new Big(0), grossPerformancePercentage: new Big(0), grossPerformancePercentageWithCurrencyEffect: new Big(0), @@ -934,6 +932,8 @@ export class PortfolioCalculator { timeWeightedInvestmentValues: {}, timeWeightedInvestmentValuesWithCurrencyEffect: {}, timeWeightedInvestmentWithCurrencyEffect: new Big(0), + totalDividend: new Big(0), + totalDividendInBaseCurrency: new Big(0), totalInvestment: new Big(0), totalInvestmentWithCurrencyEffect: new Big(0) }; @@ -954,8 +954,6 @@ export class PortfolioCalculator { return { currentValues: {}, currentValuesWithCurrencyEffect: {}, - dividend: new Big(0), - dividendInBaseCurrency: new Big(0), grossPerformance: new Big(0), grossPerformancePercentage: new Big(0), grossPerformancePercentageWithCurrencyEffect: new Big(0), @@ -976,6 +974,8 @@ export class PortfolioCalculator { timeWeightedInvestmentValues: {}, timeWeightedInvestmentValuesWithCurrencyEffect: {}, timeWeightedInvestmentWithCurrencyEffect: new Big(0), + totalDividend: new Big(0), + totalDividendInBaseCurrency: new Big(0), totalInvestment: new Big(0), totalInvestmentWithCurrencyEffect: new Big(0) }; @@ -1219,8 +1219,10 @@ export class PortfolioCalculator { totalUnits = totalUnits.plus(order.quantity.mul(getFactor(order.type))); if (order.type === 'DIVIDEND') { - dividend = dividend.plus(order.quantity.mul(order.unitPrice)); - dividendInBaseCurrency = dividendInBaseCurrency.plus( + const dividend = order.quantity.mul(order.unitPrice); + + totalDividend = totalDividend.plus(dividend); + totalDividendInBaseCurrency = totalDividendInBaseCurrency.plus( dividend.mul(exchangeRateAtOrderDate ?? 1) ); } @@ -1495,7 +1497,7 @@ export class PortfolioCalculator { Time weighted investment with currency effect: ${timeWeightedAverageInvestmentBetweenStartAndEndDateWithCurrencyEffect.toFixed( 2 )} - Total dividend: ${dividend.toFixed(2)} + Total dividend: ${totalDividend.toFixed(2)} Gross performance: ${totalGrossPerformance.toFixed( 2 )} / ${grossPerformancePercentage.mul(100).toFixed(2)}% @@ -1520,8 +1522,6 @@ export class PortfolioCalculator { return { currentValues, currentValuesWithCurrencyEffect, - dividend, - dividendInBaseCurrency, grossPerformancePercentage, grossPerformancePercentageWithCurrencyEffect, initialValue, @@ -1535,6 +1535,8 @@ export class PortfolioCalculator { netPerformanceValuesWithCurrencyEffect, timeWeightedInvestmentValues, timeWeightedInvestmentValuesWithCurrencyEffect, + totalDividend, + totalDividendInBaseCurrency, totalInvestment, totalInvestmentWithCurrencyEffect, grossPerformance: totalGrossPerformance, diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index b781af475..4f172b020 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -294,10 +294,9 @@ export class PortfolioService { const { items } = await this.getChart({ dateRange, impersonationId, - portfolioOrders, + portfolioCalculator, transactionPoints, userId, - userCurrency: this.request.user.Settings.settings.baseCurrency, withDataDecimation: false }); @@ -1229,9 +1228,8 @@ export class PortfolioService { const { items } = await this.getChart({ dateRange, impersonationId, - portfolioOrders, + portfolioCalculator, transactionPoints, - userCurrency, userId }); @@ -1456,17 +1454,15 @@ export class PortfolioService { private async getChart({ dateRange = 'max', impersonationId, - portfolioOrders, + portfolioCalculator, transactionPoints, - userCurrency, userId, withDataDecimation = true }: { dateRange?: DateRange; impersonationId: string; - portfolioOrders: PortfolioOrder[]; + portfolioCalculator: PortfolioCalculator; transactionPoints: TransactionPoint[]; - userCurrency: string; userId: string; withDataDecimation?: boolean; }): Promise { @@ -1480,15 +1476,6 @@ export class PortfolioService { userId = await this.getUserId(impersonationId, userId); - const portfolioCalculator = new PortfolioCalculator({ - currency: userCurrency, - currentRateService: this.currentRateService, - exchangeRateDataService: this.exchangeRateDataService, - orders: portfolioOrders - }); - - portfolioCalculator.setTransactionPoints(transactionPoints); - const endDate = new Date(); const portfolioStart = parseDate(transactionPoints[0].date); diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts index 5a2ec5265..26da886e7 100644 --- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts +++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts @@ -155,15 +155,14 @@ export class AdminMarketDataDetailComponent implements OnChanges, OnInit { day: string; yearMonth: string; }) { - const date = parseISO(`${yearMonth}-${day}`); const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice; const dialogRef = this.dialog.open(MarketDataDetailDialog, { data: { - date, marketPrice, currency: this.currency, dataSource: this.dataSource, + dateString: `${yearMonth}-${day}`, symbol: this.symbol, user: this.user }, diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts index 8f5447f9c..81188cd1f 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts @@ -5,7 +5,7 @@ import { DataSource } from '@prisma/client'; export interface MarketDataDetailDialogParams { currency: string; dataSource: DataSource; - date: Date; + dateString: string; marketPrice: number; symbol: string; user: User; diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts index df8ac6067..6a44d0dfb 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts @@ -45,7 +45,7 @@ export class MarketDataDetailDialog implements OnDestroy { this.adminService .fetchSymbolForDate({ dataSource: this.data.dataSource, - date: this.data.date, + dateString: this.data.dateString, symbol: this.data.symbol }) .pipe(takeUntil(this.unsubscribeSubject)) @@ -63,7 +63,7 @@ export class MarketDataDetailDialog implements OnDestroy { marketData: { marketData: [ { - date: this.data.date.toISOString(), + date: this.data.dateString, marketPrice: this.data.marketPrice } ] diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html index 5e16fc702..8e7e30649 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html +++ b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html @@ -9,7 +9,7 @@ matInput name="date" [matDatepicker]="date" - [(ngModel)]="data.date" + [(ngModel)]="data.dateString" /> { - return { marketPrice, date: parseDate(date).toISOString() }; - }) + marketData }, symbol: this.data.symbol }) diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.html b/apps/client/src/app/components/admin-platform/admin-platform.component.html index bd7e82560..ecf3304cf 100644 --- a/apps/client/src/app/components/admin-platform/admin-platform.component.html +++ b/apps/client/src/app/components/admin-platform/admin-platform.component.html @@ -91,7 +91,11 @@ Edit - -