parent
b932bac9aa
commit
4ab3f81384
apps
api/src
app
order
portfolio
helper
models
services/interfaces
client/src/app/services
@ -19,7 +19,7 @@ import {
|
|||||||
Order,
|
Order,
|
||||||
Prisma,
|
Prisma,
|
||||||
Tag,
|
Tag,
|
||||||
Type as TypeOfOrder
|
Type as ActivityType
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import { endOfToday, isAfter } from 'date-fns';
|
import { endOfToday, isAfter } from 'date-fns';
|
||||||
@ -229,7 +229,7 @@ export class OrderService {
|
|||||||
sortColumn?: string;
|
sortColumn?: string;
|
||||||
sortDirection?: Prisma.SortOrder;
|
sortDirection?: Prisma.SortOrder;
|
||||||
take?: number;
|
take?: number;
|
||||||
types?: TypeOfOrder[];
|
types?: ActivityType[];
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
withExcludedAccounts?: boolean;
|
withExcludedAccounts?: boolean;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DataSource, Tag, Type as TypeOfOrder } from '@prisma/client';
|
import { DataSource, Tag, Type as ActivityType } from '@prisma/client';
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
|
|
||||||
export interface PortfolioOrder {
|
export interface PortfolioOrder {
|
||||||
@ -10,6 +10,6 @@ export interface PortfolioOrder {
|
|||||||
quantity: Big;
|
quantity: Big;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
tags?: Tag[];
|
tags?: Tag[];
|
||||||
type: TypeOfOrder;
|
type: ActivityType;
|
||||||
unitPrice: Big;
|
unitPrice: Big;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
||||||
@ -12,7 +13,6 @@ import {
|
|||||||
import { GroupBy } from '@ghostfolio/common/types';
|
import { GroupBy } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { Type as TypeOfOrder } from '@prisma/client';
|
|
||||||
import Big from 'big.js';
|
import Big from 'big.js';
|
||||||
import {
|
import {
|
||||||
addDays,
|
addDays,
|
||||||
@ -76,7 +76,7 @@ export class PortfolioCalculator {
|
|||||||
let currentTransactionPointItem: TransactionPointSymbol;
|
let currentTransactionPointItem: TransactionPointSymbol;
|
||||||
const oldAccumulatedSymbol = symbols[order.symbol];
|
const oldAccumulatedSymbol = symbols[order.symbol];
|
||||||
|
|
||||||
const factor = this.getFactor(order.type);
|
const factor = getFactor(order.type);
|
||||||
const unitPrice = new Big(order.unitPrice);
|
const unitPrice = new Big(order.unitPrice);
|
||||||
if (oldAccumulatedSymbol) {
|
if (oldAccumulatedSymbol) {
|
||||||
const newQuantity = order.quantity
|
const newQuantity = order.quantity
|
||||||
@ -820,25 +820,6 @@ export class PortfolioCalculator {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFactor(type: TypeOfOrder) {
|
|
||||||
let factor: number;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'BUY':
|
|
||||||
case 'ITEM':
|
|
||||||
factor = 1;
|
|
||||||
break;
|
|
||||||
case 'SELL':
|
|
||||||
factor = -1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
factor = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSymbolMetrics({
|
private getSymbolMetrics({
|
||||||
end,
|
end,
|
||||||
exchangeRates,
|
exchangeRates,
|
||||||
@ -989,7 +970,7 @@ export class PortfolioCalculator {
|
|||||||
itemType: 'start',
|
itemType: 'start',
|
||||||
name: '',
|
name: '',
|
||||||
quantity: new Big(0),
|
quantity: new Big(0),
|
||||||
type: TypeOfOrder.BUY,
|
type: 'BUY',
|
||||||
unitPrice: unitPriceAtStartDate
|
unitPrice: unitPriceAtStartDate
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1003,7 +984,7 @@ export class PortfolioCalculator {
|
|||||||
itemType: 'end',
|
itemType: 'end',
|
||||||
name: '',
|
name: '',
|
||||||
quantity: new Big(0),
|
quantity: new Big(0),
|
||||||
type: TypeOfOrder.BUY,
|
type: 'BUY',
|
||||||
unitPrice: unitPriceAtEndDate
|
unitPrice: unitPriceAtEndDate
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1030,7 +1011,7 @@ export class PortfolioCalculator {
|
|||||||
feeInBaseCurrency: new Big(0),
|
feeInBaseCurrency: new Big(0),
|
||||||
name: '',
|
name: '',
|
||||||
quantity: new Big(0),
|
quantity: new Big(0),
|
||||||
type: TypeOfOrder.BUY,
|
type: 'BUY',
|
||||||
unitPrice:
|
unitPrice:
|
||||||
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
|
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
|
||||||
lastUnitPrice
|
lastUnitPrice
|
||||||
@ -1131,24 +1112,24 @@ export class PortfolioCalculator {
|
|||||||
order.type === 'BUY'
|
order.type === 'BUY'
|
||||||
? order.quantity
|
? order.quantity
|
||||||
.mul(order.unitPriceInBaseCurrency)
|
.mul(order.unitPriceInBaseCurrency)
|
||||||
.mul(this.getFactor(order.type))
|
.mul(getFactor(order.type))
|
||||||
: totalUnits.gt(0)
|
: totalUnits.gt(0)
|
||||||
? totalInvestment
|
? totalInvestment
|
||||||
.div(totalUnits)
|
.div(totalUnits)
|
||||||
.mul(order.quantity)
|
.mul(order.quantity)
|
||||||
.mul(this.getFactor(order.type))
|
.mul(getFactor(order.type))
|
||||||
: new Big(0);
|
: new Big(0);
|
||||||
|
|
||||||
const transactionInvestmentWithCurrencyEffect =
|
const transactionInvestmentWithCurrencyEffect =
|
||||||
order.type === 'BUY'
|
order.type === 'BUY'
|
||||||
? order.quantity
|
? order.quantity
|
||||||
.mul(order.unitPriceInBaseCurrencyWithCurrencyEffect)
|
.mul(order.unitPriceInBaseCurrencyWithCurrencyEffect)
|
||||||
.mul(this.getFactor(order.type))
|
.mul(getFactor(order.type))
|
||||||
: totalUnits.gt(0)
|
: totalUnits.gt(0)
|
||||||
? totalInvestmentWithCurrencyEffect
|
? totalInvestmentWithCurrencyEffect
|
||||||
.div(totalUnits)
|
.div(totalUnits)
|
||||||
.mul(order.quantity)
|
.mul(order.quantity)
|
||||||
.mul(this.getFactor(order.type))
|
.mul(getFactor(order.type))
|
||||||
: new Big(0);
|
: new Big(0);
|
||||||
|
|
||||||
if (PortfolioCalculator.ENABLE_LOGGING) {
|
if (PortfolioCalculator.ENABLE_LOGGING) {
|
||||||
@ -1203,9 +1184,7 @@ export class PortfolioCalculator {
|
|||||||
order.feeInBaseCurrencyWithCurrencyEffect ?? 0
|
order.feeInBaseCurrencyWithCurrencyEffect ?? 0
|
||||||
);
|
);
|
||||||
|
|
||||||
totalUnits = totalUnits.plus(
|
totalUnits = totalUnits.plus(order.quantity.mul(getFactor(order.type)));
|
||||||
order.quantity.mul(this.getFactor(order.type))
|
|
||||||
);
|
|
||||||
|
|
||||||
const valueOfInvestment = totalUnits.mul(order.unitPriceInBaseCurrency);
|
const valueOfInvestment = totalUnits.mul(order.unitPriceInBaseCurrency);
|
||||||
|
|
||||||
@ -1214,14 +1193,14 @@ export class PortfolioCalculator {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const grossPerformanceFromSell =
|
const grossPerformanceFromSell =
|
||||||
order.type === TypeOfOrder.SELL
|
order.type === 'SELL'
|
||||||
? order.unitPriceInBaseCurrency
|
? order.unitPriceInBaseCurrency
|
||||||
.minus(lastAveragePrice)
|
.minus(lastAveragePrice)
|
||||||
.mul(order.quantity)
|
.mul(order.quantity)
|
||||||
: new Big(0);
|
: new Big(0);
|
||||||
|
|
||||||
const grossPerformanceFromSellWithCurrencyEffect =
|
const grossPerformanceFromSellWithCurrencyEffect =
|
||||||
order.type === TypeOfOrder.SELL
|
order.type === 'SELL'
|
||||||
? order.unitPriceInBaseCurrencyWithCurrencyEffect
|
? order.unitPriceInBaseCurrencyWithCurrencyEffect
|
||||||
.minus(lastAveragePriceWithCurrencyEffect)
|
.minus(lastAveragePriceWithCurrencyEffect)
|
||||||
.mul(order.quantity)
|
.mul(order.quantity)
|
||||||
|
@ -7,6 +7,7 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
|
|||||||
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
||||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
|
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
|
||||||
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
||||||
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
||||||
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
|
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
|
||||||
@ -2131,7 +2132,7 @@ export class PortfolioService {
|
|||||||
?.marketPriceInBaseCurrency ?? 0;
|
?.marketPriceInBaseCurrency ?? 0;
|
||||||
|
|
||||||
if (['LIABILITY', 'SELL'].includes(type)) {
|
if (['LIABILITY', 'SELL'].includes(type)) {
|
||||||
currentValueOfSymbolInBaseCurrency *= -1;
|
currentValueOfSymbolInBaseCurrency *= getFactor(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounts[Account?.id || UNKNOWN_KEY]?.valueInBaseCurrency) {
|
if (accounts[Account?.id || UNKNOWN_KEY]?.valueInBaseCurrency) {
|
||||||
|
21
apps/api/src/helper/portfolio.helper.ts
Normal file
21
apps/api/src/helper/portfolio.helper.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Type as ActivityType } from '@prisma/client';
|
||||||
|
|
||||||
|
export function getFactor(activityType: ActivityType) {
|
||||||
|
let factor: number;
|
||||||
|
|
||||||
|
switch (activityType) {
|
||||||
|
case 'BUY':
|
||||||
|
case 'ITEM':
|
||||||
|
factor = 1;
|
||||||
|
break;
|
||||||
|
case 'LIABILITY':
|
||||||
|
case 'SELL':
|
||||||
|
factor = -1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
factor = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return factor;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import { IOrder } from '@ghostfolio/api/services/interfaces/interfaces';
|
import { IOrder } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
|
||||||
import { Account, SymbolProfile, Type as TypeOfOrder } from '@prisma/client';
|
import { Account, SymbolProfile, Type as ActivityType } from '@prisma/client';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
export class Order {
|
export class Order {
|
||||||
@ -14,7 +14,7 @@ export class Order {
|
|||||||
private symbol: string;
|
private symbol: string;
|
||||||
private symbolProfile: SymbolProfile;
|
private symbolProfile: SymbolProfile;
|
||||||
private total: number;
|
private total: number;
|
||||||
private type: TypeOfOrder;
|
private type: ActivityType;
|
||||||
private unitPrice: number;
|
private unitPrice: number;
|
||||||
|
|
||||||
public constructor(data: IOrder) {
|
public constructor(data: IOrder) {
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
Account,
|
Account,
|
||||||
DataSource,
|
DataSource,
|
||||||
SymbolProfile,
|
SymbolProfile,
|
||||||
Type as TypeOfOrder
|
Type as ActivityType
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
|
|
||||||
export interface IOrder {
|
export interface IOrder {
|
||||||
@ -18,7 +18,7 @@ export interface IOrder {
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
symbolProfile: SymbolProfile;
|
symbolProfile: SymbolProfile;
|
||||||
type: TypeOfOrder;
|
type: ActivityType;
|
||||||
unitPrice: number;
|
unitPrice: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { parseDate as parseDateHelper } from '@ghostfolio/common/helper';
|
|||||||
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Account, DataSource, Type } from '@prisma/client';
|
import { Account, DataSource, Type as ActivityType } from '@prisma/client';
|
||||||
import { isFinite } from 'lodash';
|
import { isFinite } from 'lodash';
|
||||||
import { parse as csvToJson } from 'papaparse';
|
import { parse as csvToJson } from 'papaparse';
|
||||||
import { EMPTY } from 'rxjs';
|
import { EMPTY } from 'rxjs';
|
||||||
@ -328,26 +328,26 @@ export class ImportActivitiesService {
|
|||||||
content: any[];
|
content: any[];
|
||||||
index: number;
|
index: number;
|
||||||
item: any;
|
item: any;
|
||||||
}) {
|
}): ActivityType {
|
||||||
item = this.lowercaseKeys(item);
|
item = this.lowercaseKeys(item);
|
||||||
|
|
||||||
for (const key of ImportActivitiesService.TYPE_KEYS) {
|
for (const key of ImportActivitiesService.TYPE_KEYS) {
|
||||||
if (item[key]) {
|
if (item[key]) {
|
||||||
switch (item[key].toLowerCase()) {
|
switch (item[key].toLowerCase()) {
|
||||||
case 'buy':
|
case 'buy':
|
||||||
return Type.BUY;
|
return 'BUY';
|
||||||
case 'dividend':
|
case 'dividend':
|
||||||
return Type.DIVIDEND;
|
return 'DIVIDEND';
|
||||||
case 'fee':
|
case 'fee':
|
||||||
return Type.FEE;
|
return 'FEE';
|
||||||
case 'interest':
|
case 'interest':
|
||||||
return Type.INTEREST;
|
return 'INTEREST';
|
||||||
case 'item':
|
case 'item':
|
||||||
return Type.ITEM;
|
return 'ITEM';
|
||||||
case 'liability':
|
case 'liability':
|
||||||
return Type.LIABILITY;
|
return 'LIABILITY';
|
||||||
case 'sell':
|
case 'sell':
|
||||||
return Type.SELL;
|
return 'SELL';
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user