Refactor positions
This commit is contained in:
parent
fdc89f7182
commit
1226c26a9d
@ -3,10 +3,7 @@ import {
|
|||||||
GetValueObject
|
GetValueObject
|
||||||
} from '@ghostfolio/api/app/core/current-rate.service';
|
} from '@ghostfolio/api/app/core/current-rate.service';
|
||||||
import { OrderType } from '@ghostfolio/api/models/order-type';
|
import { OrderType } from '@ghostfolio/api/models/order-type';
|
||||||
import {
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
MarketState,
|
|
||||||
Type
|
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
|
||||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
@ -24,7 +21,6 @@ import {
|
|||||||
subDays
|
subDays
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { flatten } from 'lodash';
|
import { flatten } from 'lodash';
|
||||||
import { resetHours } from '@ghostfolio/common/helper';
|
|
||||||
|
|
||||||
const DATE_FORMAT = 'yyyy-MM-dd';
|
const DATE_FORMAT = 'yyyy-MM-dd';
|
||||||
|
|
||||||
@ -145,19 +141,15 @@ export class PortfolioCalculator {
|
|||||||
averagePrice: item.investment.div(item.quantity),
|
averagePrice: item.investment.div(item.quantity),
|
||||||
currency: item.currency,
|
currency: item.currency,
|
||||||
firstBuyDate: item.firstBuyDate,
|
firstBuyDate: item.firstBuyDate,
|
||||||
marketState: MarketState.open, // TODO
|
|
||||||
quantity: item.quantity,
|
|
||||||
symbol: item.symbol,
|
|
||||||
investment: item.investment,
|
|
||||||
marketPrice: marketValue?.marketPrice,
|
|
||||||
transactionCount: item.transactionCount,
|
|
||||||
grossPerformance,
|
grossPerformance,
|
||||||
grossPerformancePercentage: marketValue
|
grossPerformancePercentage: marketValue
|
||||||
? grossPerformance.div(item.investment)
|
? grossPerformance.div(item.investment)
|
||||||
: null,
|
: null,
|
||||||
url: '', // TODO
|
investment: item.investment,
|
||||||
name: '', // TODO,
|
marketPrice: marketValue?.marketPrice,
|
||||||
type: Type.Unknown // TODO
|
quantity: item.quantity,
|
||||||
|
symbol: item.symbol,
|
||||||
|
transactionCount: item.transactionCount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
import { Position } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
export interface PortfolioPositions {
|
export interface PortfolioPositions {
|
||||||
positions: TimelinePosition[];
|
positions: Position[];
|
||||||
}
|
}
|
||||||
|
@ -283,9 +283,13 @@ export class PortfolioController {
|
|||||||
@Get('positions')
|
@Get('positions')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async getPositions(
|
public async getPositions(
|
||||||
@Headers('impersonation-id') impersonationId
|
@Headers('impersonation-id') impersonationId,
|
||||||
|
@Query('range') range
|
||||||
): Promise<PortfolioPositions> {
|
): Promise<PortfolioPositions> {
|
||||||
const positions = await this.portfolioService.getPositions(impersonationId);
|
const positions = await this.portfolioService.getPositions(
|
||||||
|
impersonationId,
|
||||||
|
range
|
||||||
|
);
|
||||||
|
|
||||||
return { positions };
|
return { positions };
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,15 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider.serv
|
|||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import { IOrder } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IOrder } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
import {
|
||||||
|
MarketState,
|
||||||
|
Type
|
||||||
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||||
import {
|
import {
|
||||||
PortfolioItem,
|
PortfolioItem,
|
||||||
PortfolioOverview
|
PortfolioOverview,
|
||||||
|
Position
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { DateRange, RequestWithUser } from '@ghostfolio/common/types';
|
import { DateRange, RequestWithUser } from '@ghostfolio/common/types';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
@ -192,7 +197,10 @@ export class PortfolioService {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPositions(aImpersonationId: string) {
|
public async getPositions(
|
||||||
|
aImpersonationId: string,
|
||||||
|
aDateRange: DateRange = 'max'
|
||||||
|
): Promise<Position[]> {
|
||||||
const impersonationUserId =
|
const impersonationUserId =
|
||||||
await this.impersonationService.validateImpersonationId(
|
await this.impersonationService.validateImpersonationId(
|
||||||
aImpersonationId,
|
aImpersonationId,
|
||||||
@ -210,13 +218,23 @@ export class PortfolioService {
|
|||||||
|
|
||||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||||
|
|
||||||
|
// TODO: get positions for date range
|
||||||
|
console.log('Date range:', aDateRange);
|
||||||
const positions = await portfolioCalculator.getCurrentPositions();
|
const positions = await portfolioCalculator.getCurrentPositions();
|
||||||
|
|
||||||
return Object.values(positions).map((position) => {
|
return Object.values(positions).map((position) => {
|
||||||
return {
|
return {
|
||||||
...position,
|
...position,
|
||||||
grossPerformance: Number(position.grossPerformance),
|
averagePrice: new Big(position.averagePrice).toNumber(),
|
||||||
grossPerformancePercentage: Number(position.grossPerformancePercentage)
|
grossPerformance: new Big(position.grossPerformance).toNumber(),
|
||||||
|
grossPerformancePercentage: new Big(
|
||||||
|
position.grossPerformancePercentage
|
||||||
|
).toNumber(),
|
||||||
|
investment: new Big(position.investment).toNumber(),
|
||||||
|
name: '', // TODO
|
||||||
|
quantity: new Big(position.quantity).toNumber(),
|
||||||
|
type: Type.Unknown, // TODO
|
||||||
|
url: '' // TODO
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
|
|
||||||
this.getSymbols().forEach((symbol) => {
|
this.getSymbols().forEach((symbol) => {
|
||||||
positions[symbol] = {
|
positions[symbol] = {
|
||||||
|
symbol,
|
||||||
averagePrice: portfolioItemsYesterday?.positions[symbol]?.averagePrice,
|
averagePrice: portfolioItemsYesterday?.positions[symbol]?.averagePrice,
|
||||||
currency: portfolioItemsYesterday?.positions[symbol]?.currency,
|
currency: portfolioItemsYesterday?.positions[symbol]?.currency,
|
||||||
firstBuyDate: portfolioItemsYesterday?.positions[symbol]?.firstBuyDate,
|
firstBuyDate: portfolioItemsYesterday?.positions[symbol]?.firstBuyDate,
|
||||||
@ -723,6 +724,7 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
const positions: { [symbol: string]: Position } = {};
|
const positions: { [symbol: string]: Position } = {};
|
||||||
this.getSymbols().forEach((symbol) => {
|
this.getSymbols().forEach((symbol) => {
|
||||||
positions[symbol] = {
|
positions[symbol] = {
|
||||||
|
symbol,
|
||||||
averagePrice: 0,
|
averagePrice: 0,
|
||||||
currency: undefined,
|
currency: undefined,
|
||||||
firstBuyDate: null,
|
firstBuyDate: null,
|
||||||
@ -759,12 +761,13 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
|
|
||||||
const yesterday = getYesterday();
|
const yesterday = getYesterday();
|
||||||
|
|
||||||
let positions: { [symbol: string]: Position } = {};
|
const positions: { [symbol: string]: Position } = {};
|
||||||
|
|
||||||
if (isAfter(yesterday, this.getMinDate())) {
|
if (isAfter(yesterday, this.getMinDate())) {
|
||||||
// Add yesterday
|
// Add yesterday
|
||||||
this.getSymbols().forEach((symbol) => {
|
this.getSymbols().forEach((symbol) => {
|
||||||
positions[symbol] = {
|
positions[symbol] = {
|
||||||
|
symbol,
|
||||||
averagePrice: 0,
|
averagePrice: 0,
|
||||||
currency: undefined,
|
currency: undefined,
|
||||||
firstBuyDate: null,
|
firstBuyDate: null,
|
||||||
@ -773,6 +776,7 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
marketPrice:
|
marketPrice:
|
||||||
historicalData[symbol]?.[format(yesterday, 'yyyy-MM-dd')]
|
historicalData[symbol]?.[format(yesterday, 'yyyy-MM-dd')]
|
||||||
?.marketPrice || 0,
|
?.marketPrice || 0,
|
||||||
|
name: '',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
transactionCount: 0
|
transactionCount: 0
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
import { Position } from '@ghostfolio/common/interfaces';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export class PositionComponent implements OnDestroy, OnInit {
|
|||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() isLoading: boolean;
|
@Input() isLoading: boolean;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@Input() position: TimelinePosition;
|
@Input() position: Position;
|
||||||
@Input() range: string;
|
@Input() range: string;
|
||||||
|
|
||||||
public unknownKey = UNKNOWN_KEY;
|
public unknownKey = UNKNOWN_KEY;
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
MarketState,
|
MarketState,
|
||||||
Type
|
Type
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
import { Position } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-positions',
|
selector: 'gf-positions',
|
||||||
@ -21,12 +21,12 @@ export class PositionsComponent implements OnChanges, OnInit {
|
|||||||
@Input() baseCurrency: string;
|
@Input() baseCurrency: string;
|
||||||
@Input() deviceType: string;
|
@Input() deviceType: string;
|
||||||
@Input() locale: string;
|
@Input() locale: string;
|
||||||
@Input() positions: TimelinePosition[];
|
@Input() positions: Position[];
|
||||||
@Input() range: string;
|
@Input() range: string;
|
||||||
|
|
||||||
public hasPositions: boolean;
|
public hasPositions: boolean;
|
||||||
public positionsRest: TimelinePosition[] = [];
|
public positionsRest: Position[] = [];
|
||||||
public positionsWithPriority: TimelinePosition[] = [];
|
public positionsWithPriority: Position[] = [];
|
||||||
|
|
||||||
private ignoreTypes = [Type.Cash];
|
private ignoreTypes = [Type.Cash];
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
|
|||||||
import {
|
import {
|
||||||
PortfolioOverview,
|
PortfolioOverview,
|
||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
TimelinePosition,
|
Position,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
@ -65,7 +65,7 @@ export class HomePageComponent implements AfterViewInit, OnDestroy, OnInit {
|
|||||||
public isLoadingPerformance = true;
|
public isLoadingPerformance = true;
|
||||||
public overview: PortfolioOverview;
|
public overview: PortfolioOverview;
|
||||||
public performance: PortfolioPerformance;
|
public performance: PortfolioPerformance;
|
||||||
public positions: TimelinePosition[];
|
public positions: Position[];
|
||||||
public routeQueryParams: Subscription;
|
public routeQueryParams: Subscription;
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -231,14 +231,11 @@ export class HomePageComponent implements AfterViewInit, OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPositions(/* { range: this.dateRange } */) // TODO
|
.fetchPositions({ range: this.dateRange })
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((response) => {
|
.subscribe((response) => {
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
this.positions = response.positions;
|
this.positions = response.positions;
|
||||||
this.hasPositions =
|
this.hasPositions = this.positions?.length > 0;
|
||||||
this.positions && Object.keys(this.positions).length > 1;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
@ -107,7 +107,6 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.fetchOrders();
|
this.fetchOrders();
|
||||||
this.fetchPositions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchOrders() {
|
public fetchOrders() {
|
||||||
@ -125,15 +124,6 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPositions() {
|
|
||||||
this.dataService
|
|
||||||
.fetchPositions()
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
|
||||||
console.log(response);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public onCloneTransaction(aTransaction: OrderModel) {
|
public onCloneTransaction(aTransaction: OrderModel) {
|
||||||
this.openCreateTransactionDialog(aTransaction);
|
this.openCreateTransactionDialog(aTransaction);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { permissions } from '@ghostfolio/common/permissions';
|
import { permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
import { Order as OrderModel } from '@prisma/client';
|
import { Order as OrderModel } from '@prisma/client';
|
||||||
import { Account as AccountModel } from '@prisma/client';
|
import { Account as AccountModel } from '@prisma/client';
|
||||||
import { parseISO } from 'date-fns';
|
import { parseISO } from 'date-fns';
|
||||||
@ -84,9 +85,9 @@ export class DataService {
|
|||||||
return this.http.get<Access[]>('/api/access');
|
return this.http.get<Access[]>('/api/access');
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchChart(aParams: { [param: string]: any }) {
|
public fetchChart({ range }: { range: DateRange }) {
|
||||||
return this.http.get<HistoricalDataItem[]>('/api/portfolio/chart', {
|
return this.http.get<HistoricalDataItem[]>('/api/portfolio/chart', {
|
||||||
params: aParams
|
params: { range }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,12 +111,14 @@ export class DataService {
|
|||||||
return this.http.get<SymbolItem>(`/api/symbol/${aSymbol}`);
|
return this.http.get<SymbolItem>(`/api/symbol/${aSymbol}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPositions(): Observable<PortfolioPositions> {
|
public fetchPositions({
|
||||||
return this.http.get<PortfolioPositions>('/api/portfolio/positions').pipe(
|
range
|
||||||
map((respose) => {
|
}: {
|
||||||
return respose;
|
range: DateRange;
|
||||||
})
|
}): Observable<PortfolioPositions> {
|
||||||
);
|
return this.http.get<PortfolioPositions>('/api/portfolio/positions', {
|
||||||
|
params: { range }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchSymbols(aQuery: string) {
|
public fetchSymbols(aQuery: string) {
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
|
import {
|
||||||
|
MarketState,
|
||||||
|
Type
|
||||||
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
averagePrice: number;
|
averagePrice: number;
|
||||||
currency: Currency;
|
currency: Currency;
|
||||||
firstBuyDate: string;
|
firstBuyDate: string;
|
||||||
|
grossPerformance?: number;
|
||||||
|
grossPerformancePercentage?: number;
|
||||||
investment: number;
|
investment: number;
|
||||||
investmentInOriginalCurrency?: number;
|
investmentInOriginalCurrency?: number;
|
||||||
marketPrice?: number;
|
marketPrice?: number;
|
||||||
|
marketState?: MarketState;
|
||||||
|
name?: string;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
|
symbol: string;
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
type?: Type;
|
||||||
|
url?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import {
|
|
||||||
MarketState,
|
|
||||||
Type
|
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
|
|
||||||
@ -9,15 +5,11 @@ export interface TimelinePosition {
|
|||||||
averagePrice: Big;
|
averagePrice: Big;
|
||||||
currency: Currency;
|
currency: Currency;
|
||||||
firstBuyDate: string;
|
firstBuyDate: string;
|
||||||
marketState: MarketState;
|
grossPerformance: Big;
|
||||||
|
grossPerformancePercentage: Big;
|
||||||
|
investment: Big;
|
||||||
|
marketPrice: number;
|
||||||
quantity: Big;
|
quantity: Big;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
investment: Big;
|
|
||||||
grossPerformancePercentage: Big | number; // TODO
|
|
||||||
grossPerformance: Big | number; // TODO
|
|
||||||
marketPrice: number;
|
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
type: Type;
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user