Browse Source

Refactor positions

pull/239/head
Thomas 3 years ago
parent
commit
1226c26a9d
  1. 20
      apps/api/src/app/core/portfolio-calculator.ts
  2. 4
      apps/api/src/app/portfolio/interfaces/portfolio-positions.interface.ts
  3. 8
      apps/api/src/app/portfolio/portfolio.controller.ts
  4. 26
      apps/api/src/app/portfolio/portfolio.service.ts
  5. 6
      apps/api/src/models/portfolio.ts
  6. 4
      apps/client/src/app/components/position/position.component.ts
  7. 8
      apps/client/src/app/components/positions/positions.component.ts
  8. 11
      apps/client/src/app/pages/home/home-page.component.ts
  9. 10
      apps/client/src/app/pages/transactions/transactions-page.component.ts
  10. 19
      apps/client/src/app/services/data.service.ts
  11. 11
      libs/common/src/lib/interfaces/position.interface.ts
  12. 16
      libs/common/src/lib/interfaces/timeline-position.interface.ts

20
apps/api/src/app/core/portfolio-calculator.ts

@ -3,10 +3,7 @@ import {
GetValueObject
} from '@ghostfolio/api/app/core/current-rate.service';
import { OrderType } from '@ghostfolio/api/models/order-type';
import {
MarketState,
Type
} from '@ghostfolio/api/services/interfaces/interfaces';
import { resetHours } from '@ghostfolio/common/helper';
import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Currency } from '@prisma/client';
import Big from 'big.js';
@ -24,7 +21,6 @@ import {
subDays
} from 'date-fns';
import { flatten } from 'lodash';
import { resetHours } from '@ghostfolio/common/helper';
const DATE_FORMAT = 'yyyy-MM-dd';
@ -145,19 +141,15 @@ export class PortfolioCalculator {
averagePrice: item.investment.div(item.quantity),
currency: item.currency,
firstBuyDate: item.firstBuyDate,
marketState: MarketState.open, // TODO
quantity: item.quantity,
symbol: item.symbol,
investment: item.investment,
marketPrice: marketValue?.marketPrice,
transactionCount: item.transactionCount,
grossPerformance,
grossPerformancePercentage: marketValue
? grossPerformance.div(item.investment)
: null,
url: '', // TODO
name: '', // TODO,
type: Type.Unknown // TODO
investment: item.investment,
marketPrice: marketValue?.marketPrice,
quantity: item.quantity,
symbol: item.symbol,
transactionCount: item.transactionCount
};
}

4
apps/api/src/app/portfolio/interfaces/portfolio-positions.interface.ts

@ -1,5 +1,5 @@
import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Position } from '@ghostfolio/common/interfaces';
export interface PortfolioPositions {
positions: TimelinePosition[];
positions: Position[];
}

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

@ -283,9 +283,13 @@ export class PortfolioController {
@Get('positions')
@UseGuards(AuthGuard('jwt'))
public async getPositions(
@Headers('impersonation-id') impersonationId
@Headers('impersonation-id') impersonationId,
@Query('range') range
): Promise<PortfolioPositions> {
const positions = await this.portfolioService.getPositions(impersonationId);
const positions = await this.portfolioService.getPositions(
impersonationId,
range
);
return { positions };
}

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

@ -14,10 +14,15 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider.serv
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
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 {
PortfolioItem,
PortfolioOverview
PortfolioOverview,
Position
} from '@ghostfolio/common/interfaces';
import { DateRange, RequestWithUser } from '@ghostfolio/common/types';
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 =
await this.impersonationService.validateImpersonationId(
aImpersonationId,
@ -210,13 +218,23 @@ export class PortfolioService {
portfolioCalculator.setTransactionPoints(transactionPoints);
// TODO: get positions for date range
console.log('Date range:', aDateRange);
const positions = await portfolioCalculator.getCurrentPositions();
return Object.values(positions).map((position) => {
return {
...position,
grossPerformance: Number(position.grossPerformance),
grossPerformancePercentage: Number(position.grossPerformancePercentage)
averagePrice: new Big(position.averagePrice).toNumber(),
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
};
});
}

6
apps/api/src/models/portfolio.ts

@ -80,6 +80,7 @@ export class Portfolio implements PortfolioInterface {
this.getSymbols().forEach((symbol) => {
positions[symbol] = {
symbol,
averagePrice: portfolioItemsYesterday?.positions[symbol]?.averagePrice,
currency: portfolioItemsYesterday?.positions[symbol]?.currency,
firstBuyDate: portfolioItemsYesterday?.positions[symbol]?.firstBuyDate,
@ -723,6 +724,7 @@ export class Portfolio implements PortfolioInterface {
const positions: { [symbol: string]: Position } = {};
this.getSymbols().forEach((symbol) => {
positions[symbol] = {
symbol,
averagePrice: 0,
currency: undefined,
firstBuyDate: null,
@ -759,12 +761,13 @@ export class Portfolio implements PortfolioInterface {
const yesterday = getYesterday();
let positions: { [symbol: string]: Position } = {};
const positions: { [symbol: string]: Position } = {};
if (isAfter(yesterday, this.getMinDate())) {
// Add yesterday
this.getSymbols().forEach((symbol) => {
positions[symbol] = {
symbol,
averagePrice: 0,
currency: undefined,
firstBuyDate: null,
@ -773,6 +776,7 @@ export class Portfolio implements PortfolioInterface {
marketPrice:
historicalData[symbol]?.[format(yesterday, 'yyyy-MM-dd')]
?.marketPrice || 0,
name: '',
quantity: 0,
transactionCount: 0
};

4
apps/client/src/app/components/position/position.component.ts

@ -8,7 +8,7 @@ import {
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router';
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Position } from '@ghostfolio/common/interfaces';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -25,7 +25,7 @@ export class PositionComponent implements OnDestroy, OnInit {
@Input() deviceType: string;
@Input() isLoading: boolean;
@Input() locale: string;
@Input() position: TimelinePosition;
@Input() position: Position;
@Input() range: string;
public unknownKey = UNKNOWN_KEY;

8
apps/client/src/app/components/positions/positions.component.ts

@ -9,7 +9,7 @@ import {
MarketState,
Type
} from '@ghostfolio/api/services/interfaces/interfaces';
import { TimelinePosition } from '@ghostfolio/common/interfaces';
import { Position } from '@ghostfolio/common/interfaces';
@Component({
selector: 'gf-positions',
@ -21,12 +21,12 @@ export class PositionsComponent implements OnChanges, OnInit {
@Input() baseCurrency: string;
@Input() deviceType: string;
@Input() locale: string;
@Input() positions: TimelinePosition[];
@Input() positions: Position[];
@Input() range: string;
public hasPositions: boolean;
public positionsRest: TimelinePosition[] = [];
public positionsWithPriority: TimelinePosition[] = [];
public positionsRest: Position[] = [];
public positionsWithPriority: Position[] = [];
private ignoreTypes = [Type.Cash];

11
apps/client/src/app/pages/home/home-page.component.ts

@ -24,7 +24,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
PortfolioOverview,
PortfolioPerformance,
TimelinePosition,
Position,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -65,7 +65,7 @@ export class HomePageComponent implements AfterViewInit, OnDestroy, OnInit {
public isLoadingPerformance = true;
public overview: PortfolioOverview;
public performance: PortfolioPerformance;
public positions: TimelinePosition[];
public positions: Position[];
public routeQueryParams: Subscription;
public user: User;
@ -231,14 +231,11 @@ export class HomePageComponent implements AfterViewInit, OnDestroy, OnInit {
});
this.dataService
.fetchPositions(/* { range: this.dateRange } */) // TODO
.fetchPositions({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
console.log(response);
this.positions = response.positions;
this.hasPositions =
this.positions && Object.keys(this.positions).length > 1;
this.hasPositions = this.positions?.length > 0;
this.changeDetectorRef.markForCheck();
});

10
apps/client/src/app/pages/transactions/transactions-page.component.ts

@ -107,7 +107,6 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
});
this.fetchOrders();
this.fetchPositions();
}
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) {
this.openCreateTransactionDialog(aTransaction);
}

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

@ -27,6 +27,7 @@ import {
User
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types';
import { Order as OrderModel } from '@prisma/client';
import { Account as AccountModel } from '@prisma/client';
import { parseISO } from 'date-fns';
@ -84,9 +85,9 @@ export class DataService {
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', {
params: aParams
params: { range }
});
}
@ -110,12 +111,14 @@ export class DataService {
return this.http.get<SymbolItem>(`/api/symbol/${aSymbol}`);
}
public fetchPositions(): Observable<PortfolioPositions> {
return this.http.get<PortfolioPositions>('/api/portfolio/positions').pipe(
map((respose) => {
return respose;
})
);
public fetchPositions({
range
}: {
range: DateRange;
}): Observable<PortfolioPositions> {
return this.http.get<PortfolioPositions>('/api/portfolio/positions', {
params: { range }
});
}
public fetchSymbols(aQuery: string) {

11
libs/common/src/lib/interfaces/position.interface.ts

@ -1,12 +1,23 @@
import {
MarketState,
Type
} from '@ghostfolio/api/services/interfaces/interfaces';
import { Currency } from '@prisma/client';
export interface Position {
averagePrice: number;
currency: Currency;
firstBuyDate: string;
grossPerformance?: number;
grossPerformancePercentage?: number;
investment: number;
investmentInOriginalCurrency?: number;
marketPrice?: number;
marketState?: MarketState;
name?: string;
quantity: number;
symbol: string;
transactionCount: number;
type?: Type;
url?: string;
}

16
libs/common/src/lib/interfaces/timeline-position.interface.ts

@ -1,7 +1,3 @@
import {
MarketState,
Type
} from '@ghostfolio/api/services/interfaces/interfaces';
import { Currency } from '@prisma/client';
import Big from 'big.js';
@ -9,15 +5,11 @@ export interface TimelinePosition {
averagePrice: Big;
currency: Currency;
firstBuyDate: string;
marketState: MarketState;
quantity: Big;
symbol: string;
grossPerformance: Big;
grossPerformancePercentage: Big;
investment: Big;
grossPerformancePercentage: Big | number; // TODO
grossPerformance: Big | number; // TODO
marketPrice: number;
quantity: Big;
symbol: string;
transactionCount: number;
name: string;
url: string;
type: Type;
}

Loading…
Cancel
Save