Browse Source

Refactoring

pull/938/head
Thomas 3 years ago
parent
commit
b0983087aa
  1. 4
      apps/api/src/app/portfolio/current-rate.service.mock.ts
  2. 2
      apps/api/src/app/portfolio/current-rate.service.spec.ts
  3. 4
      apps/api/src/app/portfolio/current-rate.service.ts
  4. 1
      apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts
  5. 2
      apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts
  6. 1
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts
  7. 1
      apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts
  8. 1
      apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  9. 56
      apps/api/src/app/portfolio/portfolio-calculator.ts
  10. 21
      apps/api/src/app/portfolio/portfolio.service.ts
  11. 4
      apps/api/src/models/rule.ts
  12. 9
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts
  13. 9
      apps/client/src/app/services/data.service.ts
  14. 1
      libs/common/src/lib/interfaces/timeline-position.interface.ts

4
apps/api/src/app/portfolio/current-rate.service.mock.ts

@ -48,6 +48,8 @@ export const CurrentRateServiceMock = {
for (const dataGatheringItem of dataGatheringItems) { for (const dataGatheringItem of dataGatheringItems) {
result.push({ result.push({
date, date,
marketPrice: mockGetValue(dataGatheringItem.symbol, date)
.marketPrice,
marketPriceInBaseCurrency: mockGetValue( marketPriceInBaseCurrency: mockGetValue(
dataGatheringItem.symbol, dataGatheringItem.symbol,
date date
@ -61,6 +63,8 @@ export const CurrentRateServiceMock = {
for (const dataGatheringItem of dataGatheringItems) { for (const dataGatheringItem of dataGatheringItems) {
result.push({ result.push({
date, date,
marketPrice: mockGetValue(dataGatheringItem.symbol, date)
.marketPrice,
marketPriceInBaseCurrency: mockGetValue( marketPriceInBaseCurrency: mockGetValue(
dataGatheringItem.symbol, dataGatheringItem.symbol,
date date

2
apps/api/src/app/portfolio/current-rate.service.spec.ts

@ -100,11 +100,13 @@ describe('CurrentRateService', () => {
).toMatchObject<GetValueObject[]>([ ).toMatchObject<GetValueObject[]>([
{ {
date: undefined, date: undefined,
marketPrice: 1841.823902,
marketPriceInBaseCurrency: 1841.823902, marketPriceInBaseCurrency: 1841.823902,
symbol: 'AMZN' symbol: 'AMZN'
}, },
{ {
date: undefined, date: undefined,
marketPrice: 1847.839966,
marketPriceInBaseCurrency: 1847.839966, marketPriceInBaseCurrency: 1847.839966,
symbol: 'AMZN' symbol: 'AMZN'
} }

4
apps/api/src/app/portfolio/current-rate.service.ts

@ -40,6 +40,9 @@ export class CurrentRateService {
for (const dataGatheringItem of dataGatheringItems) { for (const dataGatheringItem of dataGatheringItems) {
result.push({ result.push({
date: today, date: today,
marketPrice:
dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice ??
0,
marketPriceInBaseCurrency: marketPriceInBaseCurrency:
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
dataResultProvider?.[dataGatheringItem.symbol] dataResultProvider?.[dataGatheringItem.symbol]
@ -69,6 +72,7 @@ export class CurrentRateService {
return data.map((marketDataItem) => { return data.map((marketDataItem) => {
return { return {
date: marketDataItem.date, date: marketDataItem.date,
marketPrice: marketDataItem.marketPrice,
marketPriceInBaseCurrency: marketPriceInBaseCurrency:
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
marketDataItem.marketPrice, marketDataItem.marketPrice,

1
apps/api/src/app/portfolio/interfaces/get-value-object.interface.ts

@ -1,5 +1,6 @@
export interface GetValueObject { export interface GetValueObject {
date: Date; date: Date;
marketPrice: number;
marketPriceInBaseCurrency: number; marketPriceInBaseCurrency: number;
symbol: string; symbol: string;
} }

2
apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts

@ -20,7 +20,7 @@ export interface PortfolioPositionDetail {
SymbolProfile: EnhancedSymbolProfile; SymbolProfile: EnhancedSymbolProfile;
tags: Tag[]; tags: Tag[];
transactionCount: number; transactionCount: number;
value: number; valueInBaseCurrency: number;
} }
export interface HistoricalDataContainer { export interface HistoricalDataContainer {

1
apps/api/src/app/portfolio/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -84,6 +84,7 @@ describe('PortfolioCalculator', () => {
netPerformance: new Big('-15.8'), netPerformance: new Big('-15.8'),
netPerformancePercentage: new Big('-0.0552834149755073478'), netPerformancePercentage: new Big('-0.0552834149755073478'),
marketPrice: 148.9, marketPrice: 148.9,
marketPriceInBaseCurrency: 148.9,
quantity: new Big('0'), quantity: new Big('0'),
symbol: 'BALN.SW', symbol: 'BALN.SW',
transactionCount: 2 transactionCount: 2

1
apps/api/src/app/portfolio/portfolio-calculator-baln-buy.spec.ts

@ -73,6 +73,7 @@ describe('PortfolioCalculator', () => {
netPerformance: new Big('23.05'), netPerformance: new Big('23.05'),
netPerformancePercentage: new Big('0.08437042459736456808'), netPerformancePercentage: new Big('0.08437042459736456808'),
marketPrice: 148.9, marketPrice: 148.9,
marketPriceInBaseCurrency: 148.9,
quantity: new Big('2'), quantity: new Big('2'),
symbol: 'BALN.SW', symbol: 'BALN.SW',
transactionCount: 1 transactionCount: 1

1
apps/api/src/app/portfolio/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -84,6 +84,7 @@ describe('PortfolioCalculator', () => {
netPerformance: new Big('17.68'), netPerformance: new Big('17.68'),
netPerformancePercentage: new Big('0.11662269129287598945'), netPerformancePercentage: new Big('0.11662269129287598945'),
marketPrice: 87.8, marketPrice: 87.8,
marketPriceInBaseCurrency: 87.8,
quantity: new Big('1'), quantity: new Big('1'),
symbol: 'NOVN.SW', symbol: 'NOVN.SW',
transactionCount: 2 transactionCount: 2

56
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -36,7 +36,7 @@ export class PortfolioCalculator {
private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT = private static readonly CALCULATE_PERCENTAGE_PERFORMANCE_WITH_MAX_INVESTMENT =
true; true;
private static readonly ENABLE_LOGGING = false; private static readonly ENABLE_LOGGING = true;
private currency: string; private currency: string;
private currentRateService: CurrentRateService; private currentRateService: CurrentRateService;
@ -223,7 +223,9 @@ export class PortfolioCalculator {
}); });
const marketSymbolMap: { const marketSymbolMap: {
[date: string]: { [symbol: string]: Big }; [date: string]: {
[symbol: string]: { marketPrice: Big; marketPriceInBaseCurrency: Big };
};
} = {}; } = {};
for (const marketSymbol of marketSymbols) { for (const marketSymbol of marketSymbols) {
@ -231,11 +233,12 @@ export class PortfolioCalculator {
if (!marketSymbolMap[date]) { if (!marketSymbolMap[date]) {
marketSymbolMap[date] = {}; marketSymbolMap[date] = {};
} }
if (marketSymbol.marketPriceInBaseCurrency) { marketSymbolMap[date][marketSymbol.symbol] = {
marketSymbolMap[date][marketSymbol.symbol] = new Big( marketPrice: new Big(marketSymbol.marketPrice),
marketPriceInBaseCurrency: new Big(
marketSymbol.marketPriceInBaseCurrency marketSymbol.marketPriceInBaseCurrency
); )
} };
} }
const todayString = format(today, DATE_FORMAT); const todayString = format(today, DATE_FORMAT);
@ -251,7 +254,10 @@ export class PortfolioCalculator {
const errors: ResponseError['errors'] = []; const errors: ResponseError['errors'] = [];
for (const item of lastTransactionPoint.items) { for (const item of lastTransactionPoint.items) {
const marketValue = marketSymbolMap[todayString]?.[item.symbol]; const marketPrice =
marketSymbolMap[todayString]?.[item.symbol].marketPrice;
const marketValue =
marketSymbolMap[todayString]?.[item.symbol].marketPriceInBaseCurrency;
const { const {
grossPerformance, grossPerformance,
@ -281,7 +287,8 @@ export class PortfolioCalculator {
? grossPerformancePercentage ?? null ? grossPerformancePercentage ?? null
: null, : null,
investment: item.investment, investment: item.investment,
marketPrice: marketValue?.toNumber() ?? null, marketPrice: marketPrice?.toNumber() ?? null,
marketPriceInBaseCurrency: marketValue?.toNumber() ?? null,
netPerformance: !hasErrors ? netPerformance ?? null : null, netPerformance: !hasErrors ? netPerformance ?? null : null,
netPerformancePercentage: !hasErrors netPerformancePercentage: !hasErrors
? netPerformancePercentage ?? null ? netPerformancePercentage ?? null
@ -432,9 +439,28 @@ export class PortfolioCalculator {
let totalInvestment = new Big(0); let totalInvestment = new Big(0);
for (const currentPosition of positions) { for (const currentPosition of positions) {
if (currentPosition.symbol === 'GOOG') {
console.log(
'grossPerformance',
currentPosition.grossPerformance.toNumber()
);
console.log(
'netPerformance',
currentPosition.netPerformance.toNumber()
);
console.log('marketPrice', currentPosition.marketPrice);
console.log(
'marketPriceInBaseCurrency',
currentPosition.marketPriceInBaseCurrency
);
}
// Market price or market price in base currency?
if (currentPosition.marketPrice) { if (currentPosition.marketPrice) {
currentValue = currentValue.plus( currentValue = currentValue.plus(
new Big(currentPosition.marketPrice).mul(currentPosition.quantity) currentPosition.quantity.mul(
currentPosition.marketPriceInBaseCurrency
)
); );
} else { } else {
hasErrors = true; hasErrors = true;
@ -649,7 +675,9 @@ export class PortfolioCalculator {
symbol symbol
}: { }: {
marketSymbolMap: { marketSymbolMap: {
[date: string]: { [symbol: string]: Big }; [date: string]: {
[symbol: string]: { marketPrice: Big; marketPriceInBaseCurrency: Big };
};
}; };
start: Date; start: Date;
symbol: string; symbol: string;
@ -673,10 +701,10 @@ export class PortfolioCalculator {
const endDate = new Date(Date.now()); const endDate = new Date(Date.now());
const unitPriceAtStartDate = const unitPriceAtStartDate =
marketSymbolMap[format(start, DATE_FORMAT)]?.[symbol]; marketSymbolMap[format(start, DATE_FORMAT)]?.[symbol]?.marketPrice;
const unitPriceAtEndDate = const unitPriceAtEndDate =
marketSymbolMap[format(endDate, DATE_FORMAT)]?.[symbol]; marketSymbolMap[format(endDate, DATE_FORMAT)]?.[symbol]?.marketPrice;
if ( if (
!unitPriceAtEndDate || !unitPriceAtEndDate ||
@ -962,7 +990,9 @@ export class PortfolioCalculator {
) )
.minus(1); .minus(1);
if (PortfolioCalculator.ENABLE_LOGGING) { if (PortfolioCalculator.ENABLE_LOGGING && symbol === 'GOOG') {
console.log(orders);
console.log( console.log(
` `
${symbol} ${symbol}

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

@ -389,7 +389,7 @@ export class PortfolioService {
continue; continue;
} }
const value = item.quantity.mul(item.marketPrice); const value = item.quantity.mul(item.marketPriceInBaseCurrency);
const symbolProfile = symbolProfileMap[item.symbol]; const symbolProfile = symbolProfileMap[item.symbol];
const dataProviderResponse = dataProviderResponses[item.symbol]; const dataProviderResponse = dataProviderResponses[item.symbol];
@ -428,7 +428,7 @@ export class PortfolioService {
grossPerformancePercent: grossPerformancePercent:
item.grossPerformancePercentage?.toNumber() ?? 0, item.grossPerformancePercentage?.toNumber() ?? 0,
investment: item.investment.toNumber(), investment: item.investment.toNumber(),
marketPrice: item.marketPrice, marketPrice: item.marketPriceInBaseCurrency,
marketState: dataProviderResponse.marketState, marketState: dataProviderResponse.marketState,
name: symbolProfile.name, name: symbolProfile.name,
netPerformance: item.netPerformance?.toNumber() ?? 0, netPerformance: item.netPerformance?.toNumber() ?? 0,
@ -508,11 +508,10 @@ export class PortfolioService {
quantity: undefined, quantity: undefined,
SymbolProfile: undefined, SymbolProfile: undefined,
transactionCount: undefined, transactionCount: undefined,
value: undefined valueInBaseCurrency: undefined
}; };
} }
const positionCurrency = orders[0].SymbolProfile.currency;
const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([ const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([
aSymbol aSymbol
]); ]);
@ -538,7 +537,7 @@ export class PortfolioService {
tags = uniqBy(tags, 'id'); tags = uniqBy(tags, 'id');
const portfolioCalculator = new PortfolioCalculator({ const portfolioCalculator = new PortfolioCalculator({
currency: positionCurrency, currency: userCurrency,
currentRateService: this.currentRateService, currentRateService: this.currentRateService,
orders: portfolioOrders orders: portfolioOrders
}); });
@ -562,6 +561,7 @@ export class PortfolioService {
dataSource, dataSource,
firstBuyDate, firstBuyDate,
marketPrice, marketPrice,
marketPriceInBaseCurrency,
quantity, quantity,
transactionCount transactionCount
} = position; } = position;
@ -653,11 +653,7 @@ export class PortfolioService {
historicalData: historicalDataArray, historicalData: historicalDataArray,
netPerformancePercent: position.netPerformancePercentage?.toNumber(), netPerformancePercent: position.netPerformancePercentage?.toNumber(),
quantity: quantity.toNumber(), quantity: quantity.toNumber(),
value: this.exchangeRateDataService.toCurrency( valueInBaseCurrency: quantity.mul(marketPriceInBaseCurrency).toNumber()
quantity.mul(marketPrice).toNumber(),
currency,
userCurrency
)
}; };
} else { } else {
const currentData = await this.dataProviderService.getQuotes([ const currentData = await this.dataProviderService.getQuotes([
@ -713,7 +709,7 @@ export class PortfolioService {
netPerformancePercent: undefined, netPerformancePercent: undefined,
quantity: 0, quantity: 0,
transactionCount: undefined, transactionCount: undefined,
value: 0 valueInBaseCurrency: 0
}; };
} }
} }
@ -1321,7 +1317,8 @@ export class PortfolioService {
for (const order of ordersByAccount) { for (const order of ordersByAccount) {
let currentValueOfSymbolInBaseCurrency = let currentValueOfSymbolInBaseCurrency =
order.quantity * order.quantity *
portfolioItemsNow[order.SymbolProfile.symbol].marketPrice; portfolioItemsNow[order.SymbolProfile.symbol]
.marketPriceInBaseCurrency;
let originalValueOfSymbolInBaseCurrency = let originalValueOfSymbolInBaseCurrency =
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
order.quantity * order.unitPrice, order.quantity * order.unitPrice,

4
apps/api/src/models/rule.ts

@ -42,7 +42,9 @@ export abstract class Rule<T extends RuleSettings> implements RuleInterface<T> {
(previousValue, currentValue) => (previousValue, currentValue) =>
previousValue + previousValue +
this.exchangeRateDataService.toCurrency( this.exchangeRateDataService.toCurrency(
currentValue.quantity.mul(currentValue.marketPrice).toNumber(), currentValue.quantity
.mul(currentValue.marketPriceInBaseCurrency)
.toNumber(),
currentValue.currency, currentValue.currency,
baseCurrency baseCurrency
), ),

9
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts

@ -7,11 +7,12 @@ import {
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper'; import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import { OrderWithAccount } from '@ghostfolio/common/types'; import { OrderWithAccount } from '@ghostfolio/common/types';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface'; import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { SymbolProfile, Tag } from '@prisma/client'; import { Tag } from '@prisma/client';
import { format, isSameMonth, isToday, parseISO } from 'date-fns'; import { format, isSameMonth, isToday, parseISO } from 'date-fns';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -48,7 +49,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
public sectors: { public sectors: {
[name: string]: { name: string; value: number }; [name: string]: { name: string; value: number };
}; };
public SymbolProfile: SymbolProfile; public SymbolProfile: EnhancedSymbolProfile;
public tags: Tag[]; public tags: Tag[];
public transactionCount: number; public transactionCount: number;
public value: number; public value: number;
@ -87,7 +88,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
SymbolProfile, SymbolProfile,
tags, tags,
transactionCount, transactionCount,
value valueInBaseCurrency
}) => { }) => {
this.averagePrice = averagePrice; this.averagePrice = averagePrice;
this.benchmarkDataItems = []; this.benchmarkDataItems = [];
@ -121,7 +122,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.SymbolProfile = SymbolProfile; this.SymbolProfile = SymbolProfile;
this.tags = tags; this.tags = tags;
this.transactionCount = transactionCount; this.transactionCount = transactionCount;
this.value = value; this.value = valueInBaseCurrency;
if (SymbolProfile?.countries?.length > 0) { if (SymbolProfile?.countries?.length > 0) {
for (const country of SymbolProfile.countries) { for (const country of SymbolProfile.countries) {

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

@ -6,6 +6,7 @@ import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { Activities } from '@ghostfolio/api/app/order/interfaces/activities.interface';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { PortfolioPositionDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position-detail.interface';
import { PortfolioPositions } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-positions.interface'; import { PortfolioPositions } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-positions.interface';
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
@ -273,13 +274,15 @@ export class DataService {
symbol: string; symbol: string;
}) { }) {
return this.http return this.http
.get<any>(`/api/v1/portfolio/position/${dataSource}/${symbol}`) .get<PortfolioPositionDetail>(
`/api/v1/portfolio/position/${dataSource}/${symbol}`
)
.pipe( .pipe(
map((data) => { map((data) => {
if (data.orders) { if (data.orders) {
for (const order of data.orders) { for (const order of data.orders) {
order.createdAt = parseISO(order.createdAt); order.createdAt = parseISO(<string>(<unknown>order.createdAt));
order.date = parseISO(order.date); order.date = parseISO(<string>(<unknown>order.date));
} }
} }

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

@ -10,6 +10,7 @@ export interface TimelinePosition {
grossPerformancePercentage: Big; grossPerformancePercentage: Big;
investment: Big; investment: Big;
marketPrice: number; marketPrice: number;
marketPriceInBaseCurrency: number;
netPerformance: Big; netPerformance: Big;
netPerformancePercentage: Big; netPerformancePercentage: Big;
quantity: Big; quantity: Big;

Loading…
Cancel
Save