Feature/improve consistent use of symbol with data source (#656)

* Improve the consistent use of symbol with dataSource

* Update changelog
pull/659/head
Thomas Kaul 2 years ago committed by GitHub
parent 035d8ad9eb
commit 62885ea890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Improved the consistent use of `symbol` in combination with `dataSource`
## 1.108.0 - 27.01.2022
### Changed

@ -30,6 +30,7 @@ import {
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { Response } from 'express';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@ -337,15 +338,16 @@ export class PortfolioController {
return summary;
}
@Get('position/:symbol')
@Get('position/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
public async getPosition(
@Headers('impersonation-id') impersonationId: string,
@Param('dataSource') dataSource,
@Param('symbol') symbol
): Promise<PortfolioPositionDetail> {
let position = await this.portfolioServiceStrategy
.get()
.getPosition(impersonationId, symbol);
.getPosition(dataSource, impersonationId, symbol);
if (position) {
if (

@ -357,6 +357,7 @@ export class PortfolioServiceNew {
assetSubClass: symbolProfile.assetSubClass,
countries: symbolProfile.countries,
currency: item.currency,
dataSource: symbolProfile.dataSource,
exchange: dataProviderResponse.exchange,
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
grossPerformancePercent:
@ -397,6 +398,7 @@ export class PortfolioServiceNew {
}
public async getPosition(
aDataSource: DataSource,
aImpersonationId: string,
aSymbol: string
): Promise<PortfolioPositionDetail> {
@ -405,7 +407,9 @@ export class PortfolioServiceNew {
const orders = (
await this.orderService.getOrders({ userCurrency, userId })
).filter((order) => order.symbol === aSymbol);
).filter((order) => {
return order.dataSource === aDataSource && order.symbol === aSymbol;
});
if (orders.length <= 0) {
return {

@ -345,6 +345,7 @@ export class PortfolioService {
assetSubClass: symbolProfile.assetSubClass,
countries: symbolProfile.countries,
currency: item.currency,
dataSource: symbolProfile.dataSource,
exchange: dataProviderResponse.exchange,
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
grossPerformancePercent:
@ -385,6 +386,7 @@ export class PortfolioService {
}
public async getPosition(
aDataSource: DataSource,
aImpersonationId: string,
aSymbol: string
): Promise<PortfolioPositionDetail> {
@ -393,7 +395,9 @@ export class PortfolioService {
const orders = (
await this.orderService.getOrders({ userCurrency, userId })
).filter((order) => order.symbol === aSymbol);
).filter((order) => {
return order.dataSource === aDataSource && order.symbol === aSymbol;
});
if (orders.length <= 0) {
return {
@ -467,7 +471,6 @@ export class PortfolioService {
} = position;
// Convert investment, gross and net performance to currency of user
const userCurrency = this.request.user.Settings.currency;
const investment = this.exchangeRateDataService.toCurrency(
position.investment?.toNumber(),
currency,

@ -12,6 +12,7 @@ import { defaultDateRangeOptions } from '@ghostfolio/common/config';
import { Position, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -47,8 +48,15 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (params['positionDetailDialog'] && params['symbol']) {
this.openPositionDialog({ symbol: params['symbol'] });
if (
params['dataSource'] &&
params['positionDetailDialog'] &&
params['symbol']
) {
this.openPositionDialog({
dataSource: params['dataSource'],
symbol: params['symbol']
});
}
});
@ -91,7 +99,13 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete();
}
private openPositionDialog({ symbol }: { symbol: string }) {
private openPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
@ -101,6 +115,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
const dialogRef = this.dialog.open(PositionDetailDialog, {
autoFocus: false,
data: {
dataSource,
symbol,
baseCurrency: this.user?.settings?.baseCurrency,
deviceType: this.deviceType,

@ -1,6 +0,0 @@
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
export interface PositionDetailDialogParams {
deviceType: string;
historicalDataItems: LineChartItem[];
}

@ -1,12 +0,0 @@
:host {
display: block;
.mat-dialog-content {
max-height: unset;
gf-line-chart {
aspect-ratio: 16 / 9;
margin: 0 -1rem;
}
}
}

@ -1,94 +0,0 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
Inject
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { isToday, parse } from 'date-fns';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { PositionDetailDialogParams } from './interfaces/interfaces';
@Component({
selector: 'gf-performance-chart-dialog',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: 'performance-chart-dialog.html',
styleUrls: ['./performance-chart-dialog.component.scss']
})
export class PerformanceChartDialog {
public benchmarkDataItems: LineChartItem[];
public benchmarkSymbol = 'VOO';
public currency: string;
public firstBuyDate: string;
public marketPrice: number;
public historicalDataItems: LineChartItem[];
private unsubscribeSubject = new Subject<void>();
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
public dialogRef: MatDialogRef<PerformanceChartDialog>,
@Inject(MAT_DIALOG_DATA) public data: PositionDetailDialogParams
) {
this.dataService
.fetchPositionDetail(this.benchmarkSymbol)
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ currency, firstBuyDate, historicalData, marketPrice }) => {
this.benchmarkDataItems = [];
this.currency = currency;
this.firstBuyDate = firstBuyDate;
this.historicalDataItems = [];
this.marketPrice = marketPrice;
let coefficient = 1;
this.historicalDataItems = this.data.historicalDataItems;
this.historicalDataItems?.forEach((historicalDataItem) => {
const benchmarkItem = historicalData.find((item) => {
return item.date === historicalDataItem.date;
});
if (benchmarkItem) {
if (coefficient === 1) {
coefficient = historicalDataItem.value / benchmarkItem.value || 1;
}
this.benchmarkDataItems.push({
date: historicalDataItem.date,
value: benchmarkItem.value * coefficient
});
} else if (
isToday(parse(historicalDataItem.date, DATE_FORMAT, new Date()))
) {
this.benchmarkDataItems.push({
date: historicalDataItem.date,
value: marketPrice * coefficient
});
} else {
this.benchmarkDataItems.push({
date: historicalDataItem.date,
value: undefined
});
}
});
this.changeDetectorRef.markForCheck();
});
}
public onClose(): void {
this.dialogRef.close();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

@ -1,27 +0,0 @@
<gf-dialog-header
mat-dialog-title
title="Performance"
[deviceType]="data.deviceType"
(closeButtonClicked)="onClose()"
></gf-dialog-header>
<div mat-dialog-content>
<div class="container p-0">
<gf-line-chart
class="mb-4"
symbol="Performance"
[benchmarkDataItems]="benchmarkDataItems"
[historicalDataItems]="historicalDataItems"
[showGradient]="true"
[showLegend]="true"
[showXAxis]="true"
[showYAxis]="false"
></gf-line-chart>
</div>
</div>
<gf-dialog-footer
mat-dialog-actions
[deviceType]="data.deviceType"
(closeButtonClicked)="onClose()"
></gf-dialog-footer>

@ -1,28 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfDialogFooterModule } from '../dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '../dialog-header/dialog-header.module';
import { PerformanceChartDialog } from './performance-chart-dialog.component';
@NgModule({
declarations: [PerformanceChartDialog],
exports: [],
imports: [
CommonModule,
GfDialogFooterModule,
GfDialogHeaderModule,
GfLineChartModule,
GfValueModule,
MatButtonModule,
MatDialogModule,
NgxSkeletonLoaderModule
],
providers: []
})
export class GfPerformanceChartDialogModule {}

@ -1,5 +1,8 @@
import { DataSource } from '@prisma/client';
export interface PositionDetailDialogParams {
baseCurrency: string;
dataSource: DataSource;
deviceType: string;
locale: string;
symbol: string;

@ -59,7 +59,10 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
public ngOnInit(): void {
this.dataService
.fetchPositionDetail(this.data.symbol)
.fetchPositionDetail({
dataSource: this.data.dataSource,
symbol: this.data.symbol
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(
({

@ -3,7 +3,11 @@
<a
class="d-flex p-3 w-100"
[routerLink]="[]"
[queryParams]="{ positionDetailDialog: true, symbol: position?.symbol }"
[queryParams]="{
dataSource: position?.dataSource,
positionDetailDialog: true,
symbol: position?.symbol
}"
>
<div class="d-flex mr-2">
<gf-trend-indicator

@ -108,7 +108,7 @@
}"
(click)="
!ignoreAssetSubClasses.includes(row.assetSubClass) &&
onOpenPositionDialog({ symbol: row.symbol })
onOpenPositionDialog({ dataSource: row.dataSource, symbol: row.symbol })
"
></tr>
</table>

@ -14,7 +14,7 @@ import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
import { AssetClass, Order as OrderModel } from '@prisma/client';
import { AssetClass, DataSource, Order as OrderModel } from '@prisma/client';
import { Subject, Subscription } from 'rxjs';
@Component({
@ -75,9 +75,15 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
this.dataSource.filter = filterValue.trim().toLowerCase();
}*/
public onOpenPositionDialog({ symbol }: { symbol: string }): void {
public onOpenPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): void {
this.router.navigate([], {
queryParams: { positionDetailDialog: true, symbol }
queryParams: { dataSource, symbol, positionDetailDialog: true }
});
}

@ -14,7 +14,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { ToggleOption } from '@ghostfolio/common/types';
import { AssetClass } from '@prisma/client';
import { AssetClass, DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -84,8 +84,13 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.routeQueryParams = route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (params['positionDetailDialog'] && params['symbol']) {
if (
params['dataSource'] &&
params['positionDetailDialog'] &&
params['symbol']
) {
this.openPositionDialog({
dataSource: params['dataSource'],
symbol: params['symbol']
});
}
@ -291,7 +296,13 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete();
}
private openPositionDialog({ symbol }: { symbol: string }) {
private openPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
@ -301,6 +312,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
const dialogRef = this.dialog.open(PositionDetailDialog, {
autoFocus: false,
data: {
dataSource,
symbol,
baseCurrency: this.user?.settings?.baseCurrency,
deviceType: this.deviceType,

@ -75,8 +75,13 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
} else {
this.router.navigate(['.'], { relativeTo: this.route });
}
} else if (params['positionDetailDialog'] && params['symbol']) {
} else if (
params['dataSource'] &&
params['positionDetailDialog'] &&
params['symbol']
) {
this.openPositionDialog({
dataSource: params['dataSource'],
symbol: params['symbol']
});
}
@ -387,7 +392,13 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
});
}
private openPositionDialog({ symbol }: { symbol: string }) {
private openPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.userService
.get()
.pipe(takeUntil(this.unsubscribeSubject))
@ -397,6 +408,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
const dialogRef = this.dialog.open(PositionDetailDialog, {
autoFocus: false,
data: {
dataSource,
symbol,
baseCurrency: this.user?.settings?.baseCurrency,
deviceType: this.deviceType,

@ -225,19 +225,27 @@ export class DataService {
);
}
public fetchPositionDetail(aSymbol: string) {
return this.http.get<any>(`/api/portfolio/position/${aSymbol}`).pipe(
map((data) => {
if (data.orders) {
for (const order of data.orders) {
order.createdAt = parseISO(order.createdAt);
order.date = parseISO(order.date);
public fetchPositionDetail({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.http
.get<any>(`/api/portfolio/position/${dataSource}/${symbol}`)
.pipe(
map((data) => {
if (data.orders) {
for (const order of data.orders) {
order.createdAt = parseISO(order.createdAt);
order.date = parseISO(order.date);
}
}
}
return data;
})
);
return data;
})
);
}
public loginAnonymous(accessToken: string) {

@ -1,5 +1,5 @@
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
import { AssetClass, AssetSubClass } from '@prisma/client';
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
import { Country } from './country.interface';
import { Sector } from './sector.interface';
@ -11,6 +11,7 @@ export interface PortfolioPosition {
assetSubClass?: AssetSubClass | 'CASH';
countries: Country[];
currency: string;
dataSource: DataSource;
exchange?: string;
grossPerformance: number;
grossPerformancePercent: number;

@ -1,10 +1,11 @@
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
import { AssetClass } from '@prisma/client';
import { AssetClass, DataSource } from '@prisma/client';
export interface Position {
assetClass: AssetClass;
averagePrice: number;
currency: string;
dataSource: DataSource;
firstBuyDate: string;
grossPerformance?: number;
grossPerformancePercentage?: number;

@ -327,6 +327,7 @@
hasPermissionToOpenDetails &&
!row.isDraft &&
onOpenPositionDialog({
dataSource: row.dataSource,
symbol: row.symbol
})
"

@ -22,6 +22,7 @@ import { Router } from '@angular/router';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
import { OrderWithAccount } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client';
import Big from 'big.js';
import { endOfToday, format, isAfter } from 'date-fns';
import { isNumber } from 'lodash';
@ -190,9 +191,15 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
this.import.emit();
}
public onOpenPositionDialog({ symbol }: { symbol: string }): void {
public onOpenPositionDialog({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): void {
this.router.navigate([], {
queryParams: { positionDetailDialog: true, symbol }
queryParams: { dataSource, symbol, positionDetailDialog: true }
});
}

Loading…
Cancel
Save