|
|
|
@ -8,6 +8,7 @@ import { DataService } from '@ghostfolio/client/services/data.service';
|
|
|
|
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
|
|
|
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|
|
|
|
import {
|
|
|
|
|
Filter,
|
|
|
|
|
HistoricalDataItem,
|
|
|
|
|
Position,
|
|
|
|
|
User
|
|
|
|
@ -15,12 +16,13 @@ import {
|
|
|
|
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
|
|
|
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
|
|
|
|
import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
|
|
|
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
|
|
|
|
import { translate } from '@ghostfolio/ui/i18n';
|
|
|
|
|
import { AssetClass, DataSource, SymbolProfile } from '@prisma/client';
|
|
|
|
|
import { differenceInDays } from 'date-fns';
|
|
|
|
|
import { sortBy } from 'lodash';
|
|
|
|
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
|
|
|
|
import { Subject } from 'rxjs';
|
|
|
|
|
import { takeUntil } from 'rxjs/operators';
|
|
|
|
|
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
|
|
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
host: { class: 'page' },
|
|
|
|
@ -29,6 +31,8 @@ import { takeUntil } from 'rxjs/operators';
|
|
|
|
|
templateUrl: './analysis-page.html'
|
|
|
|
|
})
|
|
|
|
|
export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
public activeFilters: Filter[] = [];
|
|
|
|
|
public allFilters: Filter[];
|
|
|
|
|
public benchmarkDataItems: HistoricalDataItem[] = [];
|
|
|
|
|
public benchmarks: Partial<SymbolProfile>[];
|
|
|
|
|
public bottom3: Position[];
|
|
|
|
@ -37,6 +41,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
public deviceType: string;
|
|
|
|
|
public dividendsByMonth: InvestmentItem[];
|
|
|
|
|
public dividendTimelineDataLabel = $localize`Dividend`;
|
|
|
|
|
public filters$ = new Subject<Filter[]>();
|
|
|
|
|
public firstOrderDate: Date;
|
|
|
|
|
public hasImpersonationId: boolean;
|
|
|
|
|
public investments: InvestmentItem[];
|
|
|
|
@ -50,6 +55,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
];
|
|
|
|
|
public performanceDataItems: HistoricalDataItem[];
|
|
|
|
|
public performanceDataItemsInPercentage: HistoricalDataItem[];
|
|
|
|
|
public placeholder = '';
|
|
|
|
|
public portfolioEvolutionDataLabel = $localize`Deposit`;
|
|
|
|
|
public top3: Position[];
|
|
|
|
|
public user: User;
|
|
|
|
@ -95,12 +101,63 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
this.hasImpersonationId = !!aId;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.filters$
|
|
|
|
|
.pipe(
|
|
|
|
|
distinctUntilChanged(),
|
|
|
|
|
map((filters) => {
|
|
|
|
|
this.activeFilters = filters;
|
|
|
|
|
this.placeholder =
|
|
|
|
|
this.activeFilters.length <= 0
|
|
|
|
|
? $localize`Filter by account or tag...`
|
|
|
|
|
: '';
|
|
|
|
|
|
|
|
|
|
this.update();
|
|
|
|
|
}),
|
|
|
|
|
takeUntil(this.unsubscribeSubject)
|
|
|
|
|
)
|
|
|
|
|
.subscribe(() => {});
|
|
|
|
|
|
|
|
|
|
this.userService.stateChanged
|
|
|
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
|
|
|
.subscribe((state) => {
|
|
|
|
|
if (state?.user) {
|
|
|
|
|
this.user = state.user;
|
|
|
|
|
|
|
|
|
|
const accountFilters: Filter[] = this.user.accounts
|
|
|
|
|
.filter(({ accountType }) => {
|
|
|
|
|
return accountType === 'SECURITIES';
|
|
|
|
|
})
|
|
|
|
|
.map(({ id, name }) => {
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
label: name,
|
|
|
|
|
type: 'ACCOUNT'
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const assetClassFilters: Filter[] = [];
|
|
|
|
|
for (const assetClass of Object.keys(AssetClass)) {
|
|
|
|
|
assetClassFilters.push({
|
|
|
|
|
id: assetClass,
|
|
|
|
|
label: translate(assetClass),
|
|
|
|
|
type: 'ASSET_CLASS'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tagFilters: Filter[] = this.user.tags.map(({ id, name }) => {
|
|
|
|
|
return {
|
|
|
|
|
id,
|
|
|
|
|
label: name,
|
|
|
|
|
type: 'TAG'
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.allFilters = [
|
|
|
|
|
...accountFilters,
|
|
|
|
|
...assetClassFilters,
|
|
|
|
|
...tagFilters
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
this.update();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
@ -198,6 +255,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
|
|
|
|
|
this.dataService
|
|
|
|
|
.fetchPortfolioPerformance({
|
|
|
|
|
filters: this.activeFilters,
|
|
|
|
|
range: this.user?.settings?.dateRange
|
|
|
|
|
})
|
|
|
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
|
|
@ -235,6 +293,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
|
|
|
|
|
this.dataService
|
|
|
|
|
.fetchDividends({
|
|
|
|
|
filters: this.activeFilters,
|
|
|
|
|
groupBy: 'month',
|
|
|
|
|
range: this.user?.settings?.dateRange
|
|
|
|
|
})
|
|
|
|
@ -247,6 +306,7 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
|
|
|
|
|
this.dataService
|
|
|
|
|
.fetchInvestments({
|
|
|
|
|
filters: this.activeFilters,
|
|
|
|
|
groupBy: 'month',
|
|
|
|
|
range: this.user?.settings?.dateRange
|
|
|
|
|
})
|
|
|
|
@ -258,7 +318,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.dataService
|
|
|
|
|
.fetchPositions({ range: this.user?.settings?.dateRange })
|
|
|
|
|
.fetchPositions({
|
|
|
|
|
filters: this.activeFilters,
|
|
|
|
|
range: this.user?.settings?.dateRange
|
|
|
|
|
})
|
|
|
|
|
.pipe(takeUntil(this.unsubscribeSubject))
|
|
|
|
|
.subscribe(({ positions }) => {
|
|
|
|
|
const positionsSorted = sortBy(
|
|
|
|
|