Feature/add date range component to benchmark comparator (#1240)
* Add date range component * Update changelog
This commit is contained in:
parent
fc4bb71184
commit
aece76d98f
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added the date range component to the benchmark comparator
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved the mobile layout of the benchmark comparator
|
- Improved the mobile layout of the benchmark comparator
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
import { MarketDataModule } from '@ghostfolio/api/services/market-data.module';
|
import { MarketDataModule } from '@ghostfolio/api/services/market-data.module';
|
||||||
@ -18,6 +19,7 @@ import { BenchmarkService } from './benchmark.service';
|
|||||||
MarketDataModule,
|
MarketDataModule,
|
||||||
PropertyModule,
|
PropertyModule,
|
||||||
RedisCacheModule,
|
RedisCacheModule,
|
||||||
|
SymbolModule,
|
||||||
SymbolProfileModule
|
SymbolProfileModule
|
||||||
],
|
],
|
||||||
providers: [BenchmarkService]
|
providers: [BenchmarkService]
|
||||||
|
@ -4,7 +4,7 @@ describe('BenchmarkService', () => {
|
|||||||
let benchmarkService: BenchmarkService;
|
let benchmarkService: BenchmarkService;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
benchmarkService = new BenchmarkService(null, null, null, null, null);
|
benchmarkService = new BenchmarkService(null, null, null, null, null, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calculateChangeInPercentage', async () => {
|
it('calculateChangeInPercentage', async () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||||
|
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
||||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||||
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
|
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
|
||||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||||
@ -14,6 +15,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BenchmarkService {
|
export class BenchmarkService {
|
||||||
@ -24,7 +26,8 @@ export class BenchmarkService {
|
|||||||
private readonly marketDataService: MarketDataService,
|
private readonly marketDataService: MarketDataService,
|
||||||
private readonly propertyService: PropertyService,
|
private readonly propertyService: PropertyService,
|
||||||
private readonly redisCacheService: RedisCacheService,
|
private readonly redisCacheService: RedisCacheService,
|
||||||
private readonly symbolProfileService: SymbolProfileService
|
private readonly symbolProfileService: SymbolProfileService,
|
||||||
|
private readonly symbolService: SymbolService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public calculateChangeInPercentage(baseValue: number, currentValue: number) {
|
public calculateChangeInPercentage(baseValue: number, currentValue: number) {
|
||||||
@ -127,17 +130,32 @@ export class BenchmarkService {
|
|||||||
startDate,
|
startDate,
|
||||||
symbol
|
symbol
|
||||||
}: { startDate: Date } & UniqueAsset): Promise<BenchmarkMarketDataDetails> {
|
}: { startDate: Date } & UniqueAsset): Promise<BenchmarkMarketDataDetails> {
|
||||||
const marketDataItems = await this.marketDataService.marketDataItems({
|
const [currentSymbolItem, marketDataItems] = await Promise.all([
|
||||||
orderBy: {
|
this.symbolService.get({
|
||||||
date: 'asc'
|
dataGatheringItem: {
|
||||||
},
|
dataSource,
|
||||||
where: {
|
symbol
|
||||||
dataSource,
|
|
||||||
symbol,
|
|
||||||
date: {
|
|
||||||
gte: startDate
|
|
||||||
}
|
}
|
||||||
}
|
}),
|
||||||
|
this.marketDataService.marketDataItems({
|
||||||
|
orderBy: {
|
||||||
|
date: 'asc'
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
dataSource,
|
||||||
|
symbol,
|
||||||
|
date: {
|
||||||
|
gte: startDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
marketDataItems.push({
|
||||||
|
...currentSymbolItem,
|
||||||
|
createdAt: new Date(),
|
||||||
|
date: new Date(),
|
||||||
|
id: uuidv4()
|
||||||
});
|
});
|
||||||
|
|
||||||
const marketPriceAtStartDate = marketDataItems?.[0]?.marketPrice ?? 0;
|
const marketPriceAtStartDate = marketDataItems?.[0]?.marketPrice ?? 0;
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
import type { DateRange } from '@ghostfolio/common/types';
|
import type { DateRange } from '@ghostfolio/common/types';
|
||||||
import { ViewMode } from '@prisma/client';
|
import { ViewMode } from '@prisma/client';
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
|
IsObject,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString
|
IsString
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
@ -13,6 +15,10 @@ export class UpdateUserSettingDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
baseCurrency?: string;
|
baseCurrency?: string;
|
||||||
|
|
||||||
|
@IsObject()
|
||||||
|
@IsOptional()
|
||||||
|
benchmark?: UniqueAsset;
|
||||||
|
|
||||||
@IsIn(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd'])
|
@IsIn(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd'])
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
|
@ -14,16 +14,27 @@
|
|||||||
<mat-label i18n>Compare with...</mat-label>
|
<mat-label i18n>Compare with...</mat-label>
|
||||||
<mat-select
|
<mat-select
|
||||||
name="benchmark"
|
name="benchmark"
|
||||||
|
[compareWith]="compareUniqueAssets"
|
||||||
[value]="benchmark"
|
[value]="benchmark"
|
||||||
(selectionChange)="onChangeBenchmark($event.value)"
|
(selectionChange)="onChangeBenchmark($event.value)"
|
||||||
>
|
>
|
||||||
<mat-option *ngFor="let benchmark of benchmarks" [value]="benchmark">{{
|
<mat-option
|
||||||
benchmark.symbol
|
*ngFor="let currentBenchmark of benchmarks"
|
||||||
}}</mat-option>
|
[value]="currentBenchmark"
|
||||||
|
>{{ currentBenchmark.symbol }}</mat-option
|
||||||
|
>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="user.settings.viewMode !== 'ZEN'" class="mb-3 text-center">
|
||||||
|
<gf-toggle
|
||||||
|
[defaultValue]="user?.settings?.dateRange"
|
||||||
|
[isLoading]="isLoading"
|
||||||
|
[options]="dateRangeOptions"
|
||||||
|
(change)="onChangeDateRange($event.value)"
|
||||||
|
></gf-toggle>
|
||||||
|
</div>
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<ngx-skeleton-loader
|
<ngx-skeleton-loader
|
||||||
*ngIf="isLoading"
|
*ngIf="isLoading"
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { ToggleComponent } from '@ghostfolio/client/components/toggle/toggle.component';
|
||||||
import {
|
import {
|
||||||
getTooltipOptions,
|
getTooltipOptions,
|
||||||
getTooltipPositionerMapTop,
|
getTooltipPositionerMapTop,
|
||||||
@ -27,6 +28,7 @@ import {
|
|||||||
UniqueAsset,
|
UniqueAsset,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
import {
|
import {
|
||||||
Chart,
|
Chart,
|
||||||
LineController,
|
LineController,
|
||||||
@ -46,6 +48,7 @@ import annotationPlugin from 'chartjs-plugin-annotation';
|
|||||||
})
|
})
|
||||||
export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
||||||
@Input() benchmarkDataItems: LineChartItem[] = [];
|
@Input() benchmarkDataItems: LineChartItem[] = [];
|
||||||
|
@Input() benchmark: UniqueAsset;
|
||||||
@Input() benchmarks: UniqueAsset[];
|
@Input() benchmarks: UniqueAsset[];
|
||||||
@Input() daysInMarket: number;
|
@Input() daysInMarket: number;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@ -53,11 +56,12 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
@Input() user: User;
|
@Input() user: User;
|
||||||
|
|
||||||
@Output() benchmarkChanged = new EventEmitter<UniqueAsset>();
|
@Output() benchmarkChanged = new EventEmitter<UniqueAsset>();
|
||||||
|
@Output() dateRangeChanged = new EventEmitter<DateRange>();
|
||||||
|
|
||||||
@ViewChild('chartCanvas') chartCanvas;
|
@ViewChild('chartCanvas') chartCanvas;
|
||||||
|
|
||||||
public benchmark: UniqueAsset;
|
|
||||||
public chart: Chart<any>;
|
public chart: Chart<any>;
|
||||||
|
public dateRangeOptions = ToggleComponent.DEFAULT_DATE_RANGE_OPTIONS;
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
@ -81,8 +85,22 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeBenchmark(aBenchmark: UniqueAsset) {
|
public compareUniqueAssets(
|
||||||
this.benchmarkChanged.next(aBenchmark);
|
uniqueAsset1: UniqueAsset,
|
||||||
|
uniqueAsset2: UniqueAsset
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
uniqueAsset1?.dataSource === uniqueAsset2?.dataSource &&
|
||||||
|
uniqueAsset1?.symbol === uniqueAsset2?.symbol
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChangeBenchmark(benchmark: UniqueAsset) {
|
||||||
|
this.benchmarkChanged.next(benchmark);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChangeDateRange(dateRange: DateRange) {
|
||||||
|
this.dateRangeChanged.next(dateRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
|
@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
|
import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
|
||||||
@ -12,6 +13,7 @@ import { BenchmarkComparatorComponent } from './benchmark-comparator.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
GfToggleModule,
|
||||||
MatSelectModule,
|
MatSelectModule,
|
||||||
NgxSkeletonLoaderModule,
|
NgxSkeletonLoaderModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule
|
||||||
|
@ -97,6 +97,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((user) => {
|
.subscribe((user) => {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -244,7 +244,7 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
)
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.snackBarRef = this.snackBar.open(
|
this.snackBarRef = this.snackBar.open(
|
||||||
'✅' + $localize`Coupon code has been redeemed`,
|
'✅ ' + $localize`Coupon code has been redeemed`,
|
||||||
$localize`Reload`,
|
$localize`Reload`,
|
||||||
{
|
{
|
||||||
duration: 3000
|
duration: 3000
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
|
||||||
import { GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
import { DateRange, GroupBy, ToggleOption } from '@ghostfolio/common/types';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
@ -64,15 +64,76 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.hasImpersonationId = !!aId;
|
this.hasImpersonationId = !!aId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dataService
|
this.userService.stateChanged
|
||||||
.fetchChart({ range: 'max', version: 2 })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ chart }) => {
|
.subscribe((state) => {
|
||||||
this.firstOrderDate = new Date(chart?.[0]?.date);
|
if (state?.user) {
|
||||||
this.performanceDataItems = chart;
|
this.user = state.user;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.update();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChangeBenchmark(benchmark: UniqueAsset) {
|
||||||
|
this.dataService
|
||||||
|
.putUserSetting({ benchmark })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.userService.remove();
|
||||||
|
|
||||||
|
this.userService
|
||||||
|
.get()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((user) => {
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChangeDateRange(dateRange: DateRange) {
|
||||||
|
this.dataService
|
||||||
|
.putUserSetting({ dateRange })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.userService.remove();
|
||||||
|
|
||||||
|
this.userService
|
||||||
|
.get()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((user) => {
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChangeGroupBy(aMode: GroupBy) {
|
||||||
|
this.mode = aMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
if (this.user.settings.isExperimentalFeatures) {
|
||||||
|
this.dataService
|
||||||
|
.fetchChart({ range: this.user?.settings?.dateRange, version: 2 })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ chart }) => {
|
||||||
|
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
|
||||||
|
this.performanceDataItems = chart;
|
||||||
|
|
||||||
|
this.updateBenchmarkDataItems();
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchInvestments()
|
.fetchInvestments()
|
||||||
@ -113,43 +174,27 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.changeDetectorRef.markForCheck();
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
}
|
||||||
.subscribe((state) => {
|
|
||||||
if (state?.user) {
|
private updateBenchmarkDataItems() {
|
||||||
this.user = state.user;
|
if (this.user.settings.benchmark) {
|
||||||
|
this.dataService
|
||||||
|
.fetchBenchmarkBySymbol({
|
||||||
|
...this.user.settings.benchmark,
|
||||||
|
startDate: this.firstOrderDate
|
||||||
|
})
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(({ marketData }) => {
|
||||||
|
this.benchmarkDataItems = marketData.map(({ date, value }) => {
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangeBenchmark({ dataSource, symbol }: UniqueAsset) {
|
|
||||||
this.dataService
|
|
||||||
.fetchBenchmarkBySymbol({
|
|
||||||
dataSource,
|
|
||||||
symbol,
|
|
||||||
startDate: this.firstOrderDate
|
|
||||||
})
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe(({ marketData }) => {
|
|
||||||
this.benchmarkDataItems = marketData.map(({ date, value }) => {
|
|
||||||
return {
|
|
||||||
date,
|
|
||||||
value
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onChangeGroupBy(aMode: GroupBy) {
|
|
||||||
this.mode = aMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
|
||||||
this.unsubscribeSubject.next();
|
|
||||||
this.unsubscribeSubject.complete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<div class="col-lg">
|
<div class="col-lg">
|
||||||
<gf-benchmark-comparator
|
<gf-benchmark-comparator
|
||||||
class="h-100"
|
class="h-100"
|
||||||
|
[benchmark]="user?.settings?.benchmark"
|
||||||
[benchmarkDataItems]="benchmarkDataItems"
|
[benchmarkDataItems]="benchmarkDataItems"
|
||||||
[benchmarks]="benchmarks"
|
[benchmarks]="benchmarks"
|
||||||
[daysInMarket]="daysInMarket"
|
[daysInMarket]="daysInMarket"
|
||||||
@ -11,6 +12,7 @@
|
|||||||
[performanceDataItems]="performanceDataItems"
|
[performanceDataItems]="performanceDataItems"
|
||||||
[user]="user"
|
[user]="user"
|
||||||
(benchmarkChanged)="onChangeBenchmark($event)"
|
(benchmarkChanged)="onChangeBenchmark($event)"
|
||||||
|
(dateRangeChanged)="onChangeDateRange($event)"
|
||||||
></gf-benchmark-comparator>
|
></gf-benchmark-comparator>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
import { ViewMode } from '@prisma/client';
|
import { ViewMode } from '@prisma/client';
|
||||||
|
|
||||||
|
import { UniqueAsset } from './unique-asset.interface';
|
||||||
|
|
||||||
export interface UserSettings {
|
export interface UserSettings {
|
||||||
baseCurrency?: string;
|
baseCurrency?: string;
|
||||||
|
benchmark?: UniqueAsset;
|
||||||
dateRange?: DateRange;
|
dateRange?: DateRange;
|
||||||
emergencyFund?: number;
|
emergencyFund?: number;
|
||||||
isExperimentalFeatures?: boolean;
|
isExperimentalFeatures?: boolean;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user