Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror
This commit is contained in:
commit
84457f4816
CHANGELOG.md
apps
api/src/app/portfolio/calculator/twr
client/src/app/components/home-holdings
libs/ui/src/lib/holdings-table
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Optimized the portfolio calculations with smarter date interval selection
|
||||||
- Improved the language localization for German (`de`)
|
- Improved the language localization for German (`de`)
|
||||||
|
|
||||||
## 2.111.0 - 2024-09-28
|
## 2.111.0 - 2024-09-28
|
||||||
|
@ -16,13 +16,14 @@ import {
|
|||||||
addDays,
|
addDays,
|
||||||
addMilliseconds,
|
addMilliseconds,
|
||||||
differenceInDays,
|
differenceInDays,
|
||||||
eachDayOfInterval,
|
|
||||||
format,
|
format,
|
||||||
isBefore
|
isBefore
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { cloneDeep, first, last, sortBy } from 'lodash';
|
import { cloneDeep, first, last, sortBy } from 'lodash';
|
||||||
|
|
||||||
export class TWRPortfolioCalculator extends PortfolioCalculator {
|
export class TWRPortfolioCalculator extends PortfolioCalculator {
|
||||||
|
private chartDatesDescending: string[];
|
||||||
|
|
||||||
protected calculateOverallPerformance(
|
protected calculateOverallPerformance(
|
||||||
positions: TimelinePosition[]
|
positions: TimelinePosition[]
|
||||||
): PortfolioSnapshot {
|
): PortfolioSnapshot {
|
||||||
@ -820,31 +821,35 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
|||||||
startDate = start;
|
startDate = start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const endDateString = format(endDate, DATE_FORMAT);
|
||||||
|
const startDateString = format(startDate, DATE_FORMAT);
|
||||||
|
|
||||||
const currentValuesAtDateRangeStartWithCurrencyEffect =
|
const currentValuesAtDateRangeStartWithCurrencyEffect =
|
||||||
currentValuesWithCurrencyEffect[format(startDate, DATE_FORMAT)] ??
|
currentValuesWithCurrencyEffect[startDateString] ?? new Big(0);
|
||||||
new Big(0);
|
|
||||||
|
|
||||||
const investmentValuesAccumulatedAtStartDateWithCurrencyEffect =
|
const investmentValuesAccumulatedAtStartDateWithCurrencyEffect =
|
||||||
investmentValuesAccumulatedWithCurrencyEffect[
|
investmentValuesAccumulatedWithCurrencyEffect[startDateString] ??
|
||||||
format(startDate, DATE_FORMAT)
|
new Big(0);
|
||||||
] ?? new Big(0);
|
|
||||||
|
|
||||||
const grossPerformanceAtDateRangeStartWithCurrencyEffect =
|
const grossPerformanceAtDateRangeStartWithCurrencyEffect =
|
||||||
currentValuesAtDateRangeStartWithCurrencyEffect.minus(
|
currentValuesAtDateRangeStartWithCurrencyEffect.minus(
|
||||||
investmentValuesAccumulatedAtStartDateWithCurrencyEffect
|
investmentValuesAccumulatedAtStartDateWithCurrencyEffect
|
||||||
);
|
);
|
||||||
|
|
||||||
const dates = eachDayOfInterval({
|
|
||||||
end: endDate,
|
|
||||||
start: startDate
|
|
||||||
}).map((date) => {
|
|
||||||
return format(date, DATE_FORMAT);
|
|
||||||
});
|
|
||||||
|
|
||||||
let average = new Big(0);
|
let average = new Big(0);
|
||||||
let dayCount = 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 (
|
if (
|
||||||
investmentValuesAccumulatedWithCurrencyEffect[date] instanceof Big &&
|
investmentValuesAccumulatedWithCurrencyEffect[date] instanceof Big &&
|
||||||
investmentValuesAccumulatedWithCurrencyEffect[date].gt(0)
|
investmentValuesAccumulatedWithCurrencyEffect[date].gt(0)
|
||||||
@ -864,17 +869,14 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
netPerformanceWithCurrencyEffectMap[dateRange] =
|
netPerformanceWithCurrencyEffectMap[dateRange] =
|
||||||
netPerformanceValuesWithCurrencyEffect[
|
netPerformanceValuesWithCurrencyEffect[endDateString]?.minus(
|
||||||
format(endDate, DATE_FORMAT)
|
|
||||||
]?.minus(
|
|
||||||
// If the date range is 'max', take 0 as a start value. Otherwise,
|
// 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
|
// the value of the end of the day of the start date is taken which
|
||||||
// differs from the buying price.
|
// differs from the buying price.
|
||||||
dateRange === 'max'
|
dateRange === 'max'
|
||||||
? new Big(0)
|
? new Big(0)
|
||||||
: (netPerformanceValuesWithCurrencyEffect[
|
: (netPerformanceValuesWithCurrencyEffect[startDateString] ??
|
||||||
format(startDate, DATE_FORMAT)
|
new Big(0))
|
||||||
] ?? new Big(0))
|
|
||||||
) ?? new Big(0);
|
) ?? new Big(0);
|
||||||
|
|
||||||
netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0)
|
netPerformancePercentageWithCurrencyEffectMap[dateRange] = average.gt(0)
|
||||||
|
@ -111,7 +111,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onSymbolClicked({ dataSource, symbol }: AssetProfileIdentifier) {
|
public onHoldingClicked({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
if (dataSource && symbol) {
|
if (dataSource && symbol) {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
[dateRange]="user?.settings?.dateRange"
|
[dateRange]="user?.settings?.dateRange"
|
||||||
[holdings]="holdings"
|
[holdings]="holdings"
|
||||||
(treemapChartClicked)="onSymbolClicked($event)"
|
(treemapChartClicked)="onHoldingClicked($event)"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div [ngClass]="{ 'd-none': viewModeFormControl.value !== 'TABLE' }">
|
<div [ngClass]="{ 'd-none': viewModeFormControl.value !== 'TABLE' }">
|
||||||
@ -50,6 +50,7 @@
|
|||||||
[hasPermissionToCreateActivity]="hasPermissionToCreateOrder"
|
[hasPermissionToCreateActivity]="hasPermissionToCreateOrder"
|
||||||
[holdings]="holdings"
|
[holdings]="holdings"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
|
(holdingClicked)="onHoldingClicked($event)"
|
||||||
/>
|
/>
|
||||||
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
@if (hasPermissionToCreateOrder && holdings?.length > 0) {
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
@ -14,10 +14,12 @@ import {
|
|||||||
CUSTOM_ELEMENTS_SCHEMA,
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
Output,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
@ -25,7 +27,6 @@ import { MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||||
import { Router, RouterModule } from '@angular/router';
|
|
||||||
import { AssetSubClass } from '@prisma/client';
|
import { AssetSubClass } from '@prisma/client';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
@ -44,8 +45,7 @@ import { Subject, Subscription } from 'rxjs';
|
|||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
NgxSkeletonLoaderModule,
|
NgxSkeletonLoaderModule
|
||||||
RouterModule
|
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
selector: 'gf-holdings-table',
|
selector: 'gf-holdings-table',
|
||||||
@ -63,6 +63,8 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
@Input() locale = getLocale();
|
@Input() locale = getLocale();
|
||||||
@Input() pageSize = Number.MAX_SAFE_INTEGER;
|
@Input() pageSize = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
@Output() holdingClicked = new EventEmitter<AssetProfileIdentifier>();
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
public constructor(private router: Router) {}
|
public constructor() {}
|
||||||
|
|
||||||
public ngOnInit() {}
|
public ngOnInit() {}
|
||||||
|
|
||||||
@ -107,9 +109,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
|
|
||||||
public onOpenHoldingDialog({ dataSource, symbol }: AssetProfileIdentifier) {
|
public onOpenHoldingDialog({ dataSource, symbol }: AssetProfileIdentifier) {
|
||||||
if (this.hasPermissionToOpenDetails) {
|
if (this.hasPermissionToOpenDetails) {
|
||||||
this.router.navigate([], {
|
this.holdingClicked.emit({ dataSource, symbol });
|
||||||
queryParams: { dataSource, symbol, holdingDetailDialog: true }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user