Feature/improve consistent use of symbol with data source (#656)
* Improve the consistent use of symbol with dataSource * Update changelog
This commit is contained in:
parent
035d8ad9eb
commit
62885ea890
@ -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/),
|
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).
|
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
|
## 1.108.0 - 27.01.2022
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { DataSource } from '@prisma/client';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
@ -337,15 +338,16 @@ export class PortfolioController {
|
|||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('position/:symbol')
|
@Get('position/:dataSource/:symbol')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getPosition(
|
public async getPosition(
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
|
@Param('dataSource') dataSource,
|
||||||
@Param('symbol') symbol
|
@Param('symbol') symbol
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
let position = await this.portfolioServiceStrategy
|
let position = await this.portfolioServiceStrategy
|
||||||
.get()
|
.get()
|
||||||
.getPosition(impersonationId, symbol);
|
.getPosition(dataSource, impersonationId, symbol);
|
||||||
|
|
||||||
if (position) {
|
if (position) {
|
||||||
if (
|
if (
|
||||||
|
@ -357,6 +357,7 @@ export class PortfolioServiceNew {
|
|||||||
assetSubClass: symbolProfile.assetSubClass,
|
assetSubClass: symbolProfile.assetSubClass,
|
||||||
countries: symbolProfile.countries,
|
countries: symbolProfile.countries,
|
||||||
currency: item.currency,
|
currency: item.currency,
|
||||||
|
dataSource: symbolProfile.dataSource,
|
||||||
exchange: dataProviderResponse.exchange,
|
exchange: dataProviderResponse.exchange,
|
||||||
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
|
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
|
||||||
grossPerformancePercent:
|
grossPerformancePercent:
|
||||||
@ -397,6 +398,7 @@ export class PortfolioServiceNew {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getPosition(
|
public async getPosition(
|
||||||
|
aDataSource: DataSource,
|
||||||
aImpersonationId: string,
|
aImpersonationId: string,
|
||||||
aSymbol: string
|
aSymbol: string
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
@ -405,7 +407,9 @@ export class PortfolioServiceNew {
|
|||||||
|
|
||||||
const orders = (
|
const orders = (
|
||||||
await this.orderService.getOrders({ userCurrency, userId })
|
await this.orderService.getOrders({ userCurrency, userId })
|
||||||
).filter((order) => order.symbol === aSymbol);
|
).filter((order) => {
|
||||||
|
return order.dataSource === aDataSource && order.symbol === aSymbol;
|
||||||
|
});
|
||||||
|
|
||||||
if (orders.length <= 0) {
|
if (orders.length <= 0) {
|
||||||
return {
|
return {
|
||||||
|
@ -345,6 +345,7 @@ export class PortfolioService {
|
|||||||
assetSubClass: symbolProfile.assetSubClass,
|
assetSubClass: symbolProfile.assetSubClass,
|
||||||
countries: symbolProfile.countries,
|
countries: symbolProfile.countries,
|
||||||
currency: item.currency,
|
currency: item.currency,
|
||||||
|
dataSource: symbolProfile.dataSource,
|
||||||
exchange: dataProviderResponse.exchange,
|
exchange: dataProviderResponse.exchange,
|
||||||
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
|
grossPerformance: item.grossPerformance?.toNumber() ?? 0,
|
||||||
grossPerformancePercent:
|
grossPerformancePercent:
|
||||||
@ -385,6 +386,7 @@ export class PortfolioService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getPosition(
|
public async getPosition(
|
||||||
|
aDataSource: DataSource,
|
||||||
aImpersonationId: string,
|
aImpersonationId: string,
|
||||||
aSymbol: string
|
aSymbol: string
|
||||||
): Promise<PortfolioPositionDetail> {
|
): Promise<PortfolioPositionDetail> {
|
||||||
@ -393,7 +395,9 @@ export class PortfolioService {
|
|||||||
|
|
||||||
const orders = (
|
const orders = (
|
||||||
await this.orderService.getOrders({ userCurrency, userId })
|
await this.orderService.getOrders({ userCurrency, userId })
|
||||||
).filter((order) => order.symbol === aSymbol);
|
).filter((order) => {
|
||||||
|
return order.dataSource === aDataSource && order.symbol === aSymbol;
|
||||||
|
});
|
||||||
|
|
||||||
if (orders.length <= 0) {
|
if (orders.length <= 0) {
|
||||||
return {
|
return {
|
||||||
@ -467,7 +471,6 @@ export class PortfolioService {
|
|||||||
} = position;
|
} = position;
|
||||||
|
|
||||||
// Convert investment, gross and net performance to currency of user
|
// Convert investment, gross and net performance to currency of user
|
||||||
const userCurrency = this.request.user.Settings.currency;
|
|
||||||
const investment = this.exchangeRateDataService.toCurrency(
|
const investment = this.exchangeRateDataService.toCurrency(
|
||||||
position.investment?.toNumber(),
|
position.investment?.toNumber(),
|
||||||
currency,
|
currency,
|
||||||
|
@ -12,6 +12,7 @@ import { defaultDateRangeOptions } from '@ghostfolio/common/config';
|
|||||||
import { Position, User } from '@ghostfolio/common/interfaces';
|
import { Position, User } from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
|
import { DataSource } from '@prisma/client';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
@ -47,8 +48,15 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
route.queryParams
|
route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
if (params['positionDetailDialog'] && params['symbol']) {
|
if (
|
||||||
this.openPositionDialog({ symbol: params['symbol'] });
|
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();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private openPositionDialog({ symbol }: { symbol: string }) {
|
private openPositionDialog({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
dataSource: DataSource;
|
||||||
|
symbol: string;
|
||||||
|
}) {
|
||||||
this.userService
|
this.userService
|
||||||
.get()
|
.get()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -101,6 +115,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
|
|||||||
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
data: {
|
data: {
|
||||||
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
deviceType: this.deviceType,
|
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 {
|
export interface PositionDetailDialogParams {
|
||||||
baseCurrency: string;
|
baseCurrency: string;
|
||||||
|
dataSource: DataSource;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
|
@ -59,7 +59,10 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
|
|||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPositionDetail(this.data.symbol)
|
.fetchPositionDetail({
|
||||||
|
dataSource: this.data.dataSource,
|
||||||
|
symbol: this.data.symbol
|
||||||
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
({
|
({
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
<a
|
<a
|
||||||
class="d-flex p-3 w-100"
|
class="d-flex p-3 w-100"
|
||||||
[routerLink]="[]"
|
[routerLink]="[]"
|
||||||
[queryParams]="{ positionDetailDialog: true, symbol: position?.symbol }"
|
[queryParams]="{
|
||||||
|
dataSource: position?.dataSource,
|
||||||
|
positionDetailDialog: true,
|
||||||
|
symbol: position?.symbol
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div class="d-flex mr-2">
|
<div class="d-flex mr-2">
|
||||||
<gf-trend-indicator
|
<gf-trend-indicator
|
||||||
|
@ -108,7 +108,7 @@
|
|||||||
}"
|
}"
|
||||||
(click)="
|
(click)="
|
||||||
!ignoreAssetSubClasses.includes(row.assetSubClass) &&
|
!ignoreAssetSubClasses.includes(row.assetSubClass) &&
|
||||||
onOpenPositionDialog({ symbol: row.symbol })
|
onOpenPositionDialog({ dataSource: row.dataSource, symbol: row.symbol })
|
||||||
"
|
"
|
||||||
></tr>
|
></tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -14,7 +14,7 @@ import { MatSort } from '@angular/material/sort';
|
|||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
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';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -75,9 +75,15 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
|
|||||||
this.dataSource.filter = filterValue.trim().toLowerCase();
|
this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
public onOpenPositionDialog({ symbol }: { symbol: string }): void {
|
public onOpenPositionDialog({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
dataSource: DataSource;
|
||||||
|
symbol: string;
|
||||||
|
}): void {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { positionDetailDialog: true, symbol }
|
queryParams: { dataSource, symbol, positionDetailDialog: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { ToggleOption } from '@ghostfolio/common/types';
|
import { ToggleOption } from '@ghostfolio/common/types';
|
||||||
import { AssetClass } from '@prisma/client';
|
import { AssetClass, DataSource } from '@prisma/client';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
@ -84,8 +84,13 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.routeQueryParams = route.queryParams
|
this.routeQueryParams = route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
if (params['positionDetailDialog'] && params['symbol']) {
|
if (
|
||||||
|
params['dataSource'] &&
|
||||||
|
params['positionDetailDialog'] &&
|
||||||
|
params['symbol']
|
||||||
|
) {
|
||||||
this.openPositionDialog({
|
this.openPositionDialog({
|
||||||
|
dataSource: params['dataSource'],
|
||||||
symbol: params['symbol']
|
symbol: params['symbol']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -291,7 +296,13 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private openPositionDialog({ symbol }: { symbol: string }) {
|
private openPositionDialog({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
dataSource: DataSource;
|
||||||
|
symbol: string;
|
||||||
|
}) {
|
||||||
this.userService
|
this.userService
|
||||||
.get()
|
.get()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -301,6 +312,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
|||||||
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
data: {
|
data: {
|
||||||
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
deviceType: this.deviceType,
|
deviceType: this.deviceType,
|
||||||
|
@ -75,8 +75,13 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.router.navigate(['.'], { relativeTo: this.route });
|
this.router.navigate(['.'], { relativeTo: this.route });
|
||||||
}
|
}
|
||||||
} else if (params['positionDetailDialog'] && params['symbol']) {
|
} else if (
|
||||||
|
params['dataSource'] &&
|
||||||
|
params['positionDetailDialog'] &&
|
||||||
|
params['symbol']
|
||||||
|
) {
|
||||||
this.openPositionDialog({
|
this.openPositionDialog({
|
||||||
|
dataSource: params['dataSource'],
|
||||||
symbol: params['symbol']
|
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
|
this.userService
|
||||||
.get()
|
.get()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -397,6 +408,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
data: {
|
data: {
|
||||||
|
dataSource,
|
||||||
symbol,
|
symbol,
|
||||||
baseCurrency: this.user?.settings?.baseCurrency,
|
baseCurrency: this.user?.settings?.baseCurrency,
|
||||||
deviceType: this.deviceType,
|
deviceType: this.deviceType,
|
||||||
|
@ -225,19 +225,27 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPositionDetail(aSymbol: string) {
|
public fetchPositionDetail({
|
||||||
return this.http.get<any>(`/api/portfolio/position/${aSymbol}`).pipe(
|
dataSource,
|
||||||
map((data) => {
|
symbol
|
||||||
if (data.orders) {
|
}: {
|
||||||
for (const order of data.orders) {
|
dataSource: DataSource;
|
||||||
order.createdAt = parseISO(order.createdAt);
|
symbol: string;
|
||||||
order.date = parseISO(order.date);
|
}) {
|
||||||
|
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) {
|
public loginAnonymous(accessToken: string) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
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 { Country } from './country.interface';
|
||||||
import { Sector } from './sector.interface';
|
import { Sector } from './sector.interface';
|
||||||
@ -11,6 +11,7 @@ export interface PortfolioPosition {
|
|||||||
assetSubClass?: AssetSubClass | 'CASH';
|
assetSubClass?: AssetSubClass | 'CASH';
|
||||||
countries: Country[];
|
countries: Country[];
|
||||||
currency: string;
|
currency: string;
|
||||||
|
dataSource: DataSource;
|
||||||
exchange?: string;
|
exchange?: string;
|
||||||
grossPerformance: number;
|
grossPerformance: number;
|
||||||
grossPerformancePercent: number;
|
grossPerformancePercent: number;
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { AssetClass } from '@prisma/client';
|
import { AssetClass, DataSource } from '@prisma/client';
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
assetClass: AssetClass;
|
assetClass: AssetClass;
|
||||||
averagePrice: number;
|
averagePrice: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
dataSource: DataSource;
|
||||||
firstBuyDate: string;
|
firstBuyDate: string;
|
||||||
grossPerformance?: number;
|
grossPerformance?: number;
|
||||||
grossPerformancePercentage?: number;
|
grossPerformancePercentage?: number;
|
||||||
|
@ -327,6 +327,7 @@
|
|||||||
hasPermissionToOpenDetails &&
|
hasPermissionToOpenDetails &&
|
||||||
!row.isDraft &&
|
!row.isDraft &&
|
||||||
onOpenPositionDialog({
|
onOpenPositionDialog({
|
||||||
|
dataSource: row.dataSource,
|
||||||
symbol: row.symbol
|
symbol: row.symbol
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
|
@ -22,6 +22,7 @@ import { Router } from '@angular/router';
|
|||||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||||
|
import { DataSource } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import { endOfToday, format, isAfter } from 'date-fns';
|
import { endOfToday, format, isAfter } from 'date-fns';
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
@ -190,9 +191,15 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
|||||||
this.import.emit();
|
this.import.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOpenPositionDialog({ symbol }: { symbol: string }): void {
|
public onOpenPositionDialog({
|
||||||
|
dataSource,
|
||||||
|
symbol
|
||||||
|
}: {
|
||||||
|
dataSource: DataSource;
|
||||||
|
symbol: string;
|
||||||
|
}): void {
|
||||||
this.router.navigate([], {
|
this.router.navigate([], {
|
||||||
queryParams: { positionDetailDialog: true, symbol }
|
queryParams: { dataSource, symbol, positionDetailDialog: true }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user