Browse Source

Improve chart calculation

pull/1226/head
Thomas 3 years ago
parent
commit
d66d6ed548
  1. 23
      apps/api/src/app/portfolio/portfolio.controller.ts
  2. 76
      apps/api/src/app/portfolio/portfolio.service.ts
  3. 4
      apps/api/src/app/user/update-user-setting.dto.ts
  4. 5
      apps/client/src/app/components/home-overview/home-overview.component.ts
  5. 2
      apps/client/src/app/components/home-overview/home-overview.html
  6. 8
      apps/client/src/app/components/investment-chart/investment-chart.component.ts
  7. 2
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
  8. 18
      apps/client/src/app/pages/account/account-page.component.ts
  9. 13
      apps/client/src/app/pages/account/account-page.html
  10. 4
      apps/client/src/app/services/data.service.ts
  11. 12
      libs/common/src/lib/chart-helper.ts
  12. 1
      libs/common/src/lib/interfaces/user-settings.interface.ts
  13. 4
      libs/ui/src/lib/line-chart/line-chart.component.ts
  14. 2
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

23
apps/api/src/app/portfolio/portfolio.controller.ts

@ -35,7 +35,8 @@ import {
Param,
Query,
UseGuards,
UseInterceptors
UseInterceptors,
Version
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
@ -110,6 +111,26 @@ export class PortfolioController {
};
}
@Get('chart')
@UseGuards(AuthGuard('jwt'))
@Version('2')
public async getChartV2(
@Headers('impersonation-id') impersonationId: string,
@Query('range') range
): Promise<PortfolioChart> {
const historicalDataContainer = await this.portfolioService.getChartV2(
impersonationId,
range
);
return {
chart: historicalDataContainer.items,
hasError: false,
isAllTimeHigh: false,
isAllTimeLow: false
};
}
@Get('details')
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(RedactValuesInResponseInterceptor)

76
apps/api/src/app/portfolio/portfolio.service.ts

@ -57,6 +57,7 @@ import {
} from '@prisma/client';
import Big from 'big.js';
import {
addDays,
differenceInDays,
endOfToday,
format,
@ -71,7 +72,7 @@ import {
subDays,
subYears
} from 'date-fns';
import { isEmpty, sortBy, uniq, uniqBy } from 'lodash';
import { isEmpty, last, sortBy, uniq, uniqBy } from 'lodash';
import {
HistoricalDataContainer,
@ -85,6 +86,7 @@ const emergingMarkets = require('../../assets/countries/emerging-markets.json');
@Injectable()
export class PortfolioService {
private static readonly MAX_CHART_ITEMS = 250;
private baseCurrency: string;
public constructor(
@ -354,6 +356,78 @@ export class PortfolioService {
};
}
public async getChartV2(
aImpersonationId: string,
aDateRange: DateRange = 'max'
): Promise<HistoricalDataContainer> {
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
const { portfolioOrders, transactionPoints } =
await this.getTransactionPoints({
userId
});
const portfolioCalculator = new PortfolioCalculator({
currency: this.request.user.Settings.currency,
currentRateService: this.currentRateService,
orders: portfolioOrders
});
portfolioCalculator.setTransactionPoints(transactionPoints);
if (transactionPoints.length === 0) {
return {
isAllTimeHigh: false,
isAllTimeLow: false,
items: []
};
}
const endDate = new Date();
const portfolioStart = parseDate(transactionPoints[0].date);
const startDate = this.getStartDate(aDateRange, portfolioStart);
const daysInMarket = differenceInDays(new Date(), startDate);
const step = Math.round(
daysInMarket / Math.min(daysInMarket, PortfolioService.MAX_CHART_ITEMS)
);
const items: HistoricalDataItem[] = [];
let currentEndDate = startDate;
while (isBefore(currentEndDate, endDate)) {
const currentPositions = await portfolioCalculator.getCurrentPositions(
startDate,
currentEndDate
);
items.push({
date: format(currentEndDate, DATE_FORMAT),
value: currentPositions.netPerformancePercentage.toNumber() * 100
});
currentEndDate = addDays(currentEndDate, step);
}
const today = new Date();
if (last(items)?.date !== format(today, DATE_FORMAT)) {
// Add today
const { netPerformancePercentage } =
await portfolioCalculator.getCurrentPositions(startDate, today);
items.push({
date: format(today, DATE_FORMAT),
value: netPerformancePercentage.toNumber() * 100
});
}
return {
isAllTimeHigh: false,
isAllTimeLow: false,
items: items
};
}
public async getDetails(
aImpersonationId: string,
aUserId: string,

4
apps/api/src/app/user/update-user-setting.dto.ts

@ -5,6 +5,10 @@ export class UpdateUserSettingDto {
@IsOptional()
emergencyFund?: number;
@IsBoolean()
@IsOptional()
isExperimentalFeatures?: boolean;
@IsBoolean()
@IsOptional()
isRestrictedView?: boolean;

5
apps/client/src/app/components/home-overview/home-overview.component.ts

@ -106,7 +106,10 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
this.isLoadingPerformance = true;
this.dataService
.fetchChart({ range: this.dateRange })
.fetchChart({
range: this.dateRange,
version: this.user?.settings?.isExperimentalFeatures ? 2 : 1
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => {
this.historicalDataItems = chartData.chart.map((chartDataItem) => {

2
apps/client/src/app/components/home-overview/home-overview.html

@ -15,7 +15,6 @@
<gf-line-chart
class="position-absolute"
symbol="Performance"
[currency]="user?.settings?.baseCurrency"
[historicalDataItems]="historicalDataItems"
[hidden]="historicalDataItems?.length === 0"
[locale]="user?.settings?.locale"
@ -24,6 +23,7 @@
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
[unit]="user?.settings?.isExperimentalFeatures ? '%' : user?.settings?.baseCurrency"
></gf-line-chart>
</div>
</div>

8
apps/client/src/app/components/investment-chart/investment-chart.component.ts

@ -249,10 +249,10 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
private getTooltipPluginConfiguration() {
return {
...getTooltipOptions(
this.isInPercent ? undefined : this.currency,
this.isInPercent ? undefined : this.locale
),
...getTooltipOptions({
locale: this.isInPercent ? undefined : this.locale,
unit: this.isInPercent ? undefined : this.currency
}),
mode: 'index',
position: <unknown>'top',
xAlign: 'center',

2
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html

@ -23,13 +23,13 @@
class="mb-4"
benchmarkLabel="Average Unit Price"
[benchmarkDataItems]="benchmarkDataItems"
[currency]="SymbolProfile?.currency"
[historicalDataItems]="historicalDataItems"
[locale]="data.locale"
[showGradient]="true"
[showXAxis]="true"
[showYAxis]="true"
[symbol]="data.symbol"
[unit]="SymbolProfile?.currency"
></gf-line-chart>
<div class="row">

18
apps/client/src/app/pages/account/account-page.component.ts

@ -226,6 +226,24 @@ export class AccountPageComponent implements OnDestroy, OnInit {
});
}
public onExperimentalFeaturesChange(aEvent: MatSlideToggleChange) {
this.dataService
.putUserSetting({ isExperimentalFeatures: aEvent.checked })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
this.userService.remove();
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((user) => {
this.user = user;
this.changeDetectorRef.markForCheck();
});
});
}
public onRedeemCoupon() {
let couponCode = prompt($localize`Please enter your coupon code:`);
couponCode = couponCode?.trim();

13
apps/client/src/app/pages/account/account-page.html

@ -188,6 +188,19 @@
></mat-slide-toggle>
</div>
</div>
<div class="align-items-center d-flex mt-4 py-1">
<div class="pr-1 w-50">
<div i18n>Experimental Features</div>
</div>
<div class="pl-1 w-50">
<mat-slide-toggle
color="primary"
[checked]="user.settings.isExperimentalFeatures"
[disabled]="!hasPermissionToUpdateUserSettings"
(change)="onExperimentalFeaturesChange($event)"
></mat-slide-toggle>
</div>
</div>
<div class="align-items-center d-flex mt-4 py-1">
<div class="pr-1 w-50" i18n>User ID</div>
<div class="pl-1 w-50">{{ user?.id }}</div>

4
apps/client/src/app/services/data.service.ts

@ -185,8 +185,8 @@ export class DataService {
return this.http.get<BenchmarkResponse>('/api/v1/benchmark');
}
public fetchChart({ range }: { range: DateRange }) {
return this.http.get<PortfolioChart>('/api/v1/portfolio/chart', {
public fetchChart({ range, version }: { range: DateRange; version: number }) {
return this.http.get<PortfolioChart>(`/api/v${version}/portfolio/chart`, {
params: { range }
});
}

12
libs/common/src/lib/chart-helper.ts

@ -2,7 +2,13 @@ import { Chart, TooltipPosition } from 'chart.js';
import { getBackgroundColor, getTextColor } from './helper';
export function getTooltipOptions(currency = '', locale = '') {
export function getTooltipOptions({
locale = '',
unit = ''
}: {
locale?: string;
unit?: string;
} = {}) {
return {
backgroundColor: getBackgroundColor(),
bodyColor: `rgb(${getTextColor()})`,
@ -15,11 +21,11 @@ export function getTooltipOptions(currency = '', locale = '') {
label += ': ';
}
if (context.parsed.y !== null) {
if (currency) {
if (unit) {
label += `${context.parsed.y.toLocaleString(locale, {
maximumFractionDigits: 2,
minimumFractionDigits: 2
})} ${currency}`;
})} ${unit}`;
} else {
label += context.parsed.y.toFixed(2);
}

1
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -2,6 +2,7 @@ import { ViewMode } from '@prisma/client';
export interface UserSettings {
baseCurrency?: string;
isExperimentalFeatures?: boolean;
isRestrictedView?: boolean;
language?: string;
locale: string;

4
libs/ui/src/lib/line-chart/line-chart.component.ts

@ -47,7 +47,6 @@ import { LineChartItem } from './interfaces/line-chart.interface';
export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmarkLabel = '';
@Input() currency: string;
@Input() historicalDataItems: LineChartItem[];
@Input() locale: string;
@Input() showGradient = false;
@ -56,6 +55,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() showXAxis = false;
@Input() showYAxis = false;
@Input() symbol: string;
@Input() unit: string;
@Input() yMax: number;
@Input() yMaxLabel: string;
@Input() yMin: number;
@ -259,7 +259,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
private getTooltipPluginConfiguration() {
return {
...getTooltipOptions(this.currency, this.locale),
...getTooltipOptions({ locale: this.locale, unit: this.unit }),
mode: 'index',
position: <unknown>'top',
xAlign: 'center',

2
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts

@ -349,7 +349,7 @@ export class PortfolioProportionChartComponent
private getTooltipPluginConfiguration(data: ChartConfiguration['data']) {
return {
...getTooltipOptions(this.baseCurrency, this.locale),
...getTooltipOptions({ locale: this.locale, unit: this.baseCurrency }),
callbacks: {
label: (context) => {
const labelIndex =

Loading…
Cancel
Save