|
|
@ -4,8 +4,6 @@ import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details |
|
|
|
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; |
|
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; |
|
|
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; |
|
|
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; |
|
|
|
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'; |
|
|
@ -266,15 +264,15 @@ export class PortfolioService { |
|
|
|
}): Promise<PortfolioInvestments> { |
|
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
|
|
|
|
|
const { portfolioOrders, transactionPoints } = |
|
|
|
await this.getTransactionPoints({ |
|
|
|
filters, |
|
|
|
userId, |
|
|
|
includeDrafts: true, |
|
|
|
types: ['BUY', 'SELL'] |
|
|
|
}); |
|
|
|
const { activities } = await this.orderService.getOrders({ |
|
|
|
filters, |
|
|
|
userId, |
|
|
|
includeDrafts: true, |
|
|
|
types: ['BUY', 'SELL'], |
|
|
|
userCurrency: this.getUserCurrency() |
|
|
|
}); |
|
|
|
|
|
|
|
if (transactionPoints.length === 0) { |
|
|
|
if (activities.length === 0) { |
|
|
|
return { |
|
|
|
investments: [], |
|
|
|
streaks: { currentStreak: 0, longestStreak: 0 } |
|
|
@ -282,18 +280,16 @@ export class PortfolioService { |
|
|
|
} |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
transactionPoints, |
|
|
|
activities, |
|
|
|
currency: this.request.user.Settings.settings.baseCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}); |
|
|
|
|
|
|
|
const { items } = await this.getChart({ |
|
|
|
dateRange, |
|
|
|
impersonationId, |
|
|
|
portfolioCalculator, |
|
|
|
transactionPoints, |
|
|
|
userId, |
|
|
|
withDataDecimation: false |
|
|
|
}); |
|
|
@ -364,26 +360,25 @@ export class PortfolioService { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
const { activities, portfolioOrders, transactionPoints } = |
|
|
|
await this.getTransactionPoints({ |
|
|
|
filters, |
|
|
|
types, |
|
|
|
userId, |
|
|
|
withExcludedAccounts |
|
|
|
}); |
|
|
|
const { activities } = await this.orderService.getOrders({ |
|
|
|
filters, |
|
|
|
types, |
|
|
|
userCurrency, |
|
|
|
userId, |
|
|
|
withExcludedAccounts |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
transactionPoints, |
|
|
|
activities, |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioStart = parseDate( |
|
|
|
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) |
|
|
|
const startDate = this.getStartDate( |
|
|
|
dateRange, |
|
|
|
portfolioCalculator.getStartDate() |
|
|
|
); |
|
|
|
const startDate = this.getStartDate(dateRange, portfolioStart); |
|
|
|
const currentPositions = |
|
|
|
await portfolioCalculator.getCurrentPositions(startDate); |
|
|
|
|
|
|
@ -737,39 +732,22 @@ export class PortfolioService { |
|
|
|
{ dataSource: aDataSource, symbol: aSymbol } |
|
|
|
]); |
|
|
|
|
|
|
|
const portfolioOrders: PortfolioOrder[] = orders |
|
|
|
.filter((order) => { |
|
|
|
tags = tags.concat(order.tags); |
|
|
|
|
|
|
|
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type); |
|
|
|
}) |
|
|
|
.map((order) => ({ |
|
|
|
currency: order.SymbolProfile.currency, |
|
|
|
dataSource: order.SymbolProfile.dataSource, |
|
|
|
date: format(order.date, DATE_FORMAT), |
|
|
|
fee: new Big(order.fee), |
|
|
|
name: order.SymbolProfile?.name, |
|
|
|
quantity: new Big(order.quantity), |
|
|
|
symbol: order.SymbolProfile.symbol, |
|
|
|
tags: order.tags, |
|
|
|
type: order.type, |
|
|
|
unitPrice: new Big(order.unitPrice) |
|
|
|
})); |
|
|
|
|
|
|
|
tags = uniqBy(tags, 'id'); |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
activities: orders.filter((order) => { |
|
|
|
tags = tags.concat(order.tags); |
|
|
|
|
|
|
|
return ['BUY', 'DIVIDEND', 'ITEM', 'SELL'].includes(order.type); |
|
|
|
}), |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}); |
|
|
|
|
|
|
|
portfolioCalculator.computeTransactionPoints(); |
|
|
|
const portfolioStart = portfolioCalculator.getStartDate(); |
|
|
|
const transactionPoints = portfolioCalculator.getTransactionPoints(); |
|
|
|
|
|
|
|
const portfolioStart = parseDate(transactionPoints[0].date); |
|
|
|
|
|
|
|
const currentPositions = |
|
|
|
await portfolioCalculator.getCurrentPositions(portfolioStart); |
|
|
|
|
|
|
@ -982,14 +960,14 @@ export class PortfolioService { |
|
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
|
|
const { portfolioOrders, transactionPoints } = |
|
|
|
await this.getTransactionPoints({ |
|
|
|
filters, |
|
|
|
userId, |
|
|
|
types: ['BUY', 'SELL'] |
|
|
|
}); |
|
|
|
const { activities } = await this.orderService.getOrders({ |
|
|
|
filters, |
|
|
|
userId, |
|
|
|
types: ['BUY', 'SELL'], |
|
|
|
userCurrency: this.getUserCurrency() |
|
|
|
}); |
|
|
|
|
|
|
|
if (transactionPoints?.length <= 0) { |
|
|
|
if (activities?.length <= 0) { |
|
|
|
return { |
|
|
|
hasErrors: false, |
|
|
|
positions: [] |
|
|
@ -997,15 +975,16 @@ export class PortfolioService { |
|
|
|
} |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
transactionPoints, |
|
|
|
activities, |
|
|
|
currency: this.request.user.Settings.settings.baseCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioStart = parseDate(transactionPoints[0].date); |
|
|
|
const startDate = this.getStartDate(dateRange, portfolioStart); |
|
|
|
const startDate = this.getStartDate( |
|
|
|
dateRange, |
|
|
|
portfolioCalculator.getStartDate() |
|
|
|
); |
|
|
|
const currentPositions = |
|
|
|
await portfolioCalculator.getCurrentPositions(startDate); |
|
|
|
|
|
|
@ -1154,15 +1133,15 @@ export class PortfolioService { |
|
|
|
) |
|
|
|
); |
|
|
|
|
|
|
|
const { portfolioOrders, transactionPoints } = |
|
|
|
await this.getTransactionPoints({ |
|
|
|
filters, |
|
|
|
userId, |
|
|
|
withExcludedAccounts, |
|
|
|
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] |
|
|
|
}); |
|
|
|
const { activities } = await this.orderService.getOrders({ |
|
|
|
filters, |
|
|
|
userCurrency, |
|
|
|
userId, |
|
|
|
withExcludedAccounts, |
|
|
|
types: withItems ? ['BUY', 'ITEM', 'SELL'] : ['BUY', 'SELL'] |
|
|
|
}); |
|
|
|
|
|
|
|
if (accountBalanceItems?.length <= 0 && transactionPoints?.length <= 0) { |
|
|
|
if (accountBalanceItems?.length <= 0 && activities?.length <= 0) { |
|
|
|
return { |
|
|
|
chart: [], |
|
|
|
firstOrderDate: undefined, |
|
|
@ -1184,17 +1163,16 @@ export class PortfolioService { |
|
|
|
} |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
transactionPoints, |
|
|
|
activities, |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioStart = min( |
|
|
|
[ |
|
|
|
parseDate(accountBalanceItems[0]?.date), |
|
|
|
parseDate(transactionPoints[0]?.date) |
|
|
|
portfolioCalculator.getStartDate() |
|
|
|
].filter((date) => { |
|
|
|
return isValid(date); |
|
|
|
}) |
|
|
@ -1230,7 +1208,6 @@ export class PortfolioService { |
|
|
|
dateRange, |
|
|
|
impersonationId, |
|
|
|
portfolioCalculator, |
|
|
|
transactionPoints, |
|
|
|
userId |
|
|
|
}); |
|
|
|
|
|
|
@ -1307,25 +1284,22 @@ export class PortfolioService { |
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
const userCurrency = this.getUserCurrency(user); |
|
|
|
|
|
|
|
const { activities, portfolioOrders, transactionPoints } = |
|
|
|
await this.getTransactionPoints({ |
|
|
|
userId, |
|
|
|
types: ['BUY', 'SELL'] |
|
|
|
}); |
|
|
|
const { activities } = await this.orderService.getOrders({ |
|
|
|
userCurrency, |
|
|
|
userId, |
|
|
|
types: ['BUY', 'SELL'] |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
transactionPoints, |
|
|
|
activities, |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}); |
|
|
|
|
|
|
|
const portfolioStart = parseDate( |
|
|
|
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) |
|
|
|
const currentPositions = await portfolioCalculator.getCurrentPositions( |
|
|
|
portfolioCalculator.getStartDate() |
|
|
|
); |
|
|
|
const currentPositions = |
|
|
|
await portfolioCalculator.getCurrentPositions(portfolioStart); |
|
|
|
|
|
|
|
const positions = currentPositions.positions.filter( |
|
|
|
(item) => !item.quantity.eq(0) |
|
|
@ -1455,18 +1429,16 @@ export class PortfolioService { |
|
|
|
dateRange = 'max', |
|
|
|
impersonationId, |
|
|
|
portfolioCalculator, |
|
|
|
transactionPoints, |
|
|
|
userId, |
|
|
|
withDataDecimation = true |
|
|
|
}: { |
|
|
|
dateRange?: DateRange; |
|
|
|
impersonationId: string; |
|
|
|
portfolioCalculator: PortfolioCalculator; |
|
|
|
transactionPoints: TransactionPoint[]; |
|
|
|
userId: string; |
|
|
|
withDataDecimation?: boolean; |
|
|
|
}): Promise<HistoricalDataContainer> { |
|
|
|
if (transactionPoints.length === 0) { |
|
|
|
if (portfolioCalculator.getTransactionPoints().length === 0) { |
|
|
|
return { |
|
|
|
isAllTimeHigh: false, |
|
|
|
isAllTimeLow: false, |
|
|
@ -1476,8 +1448,10 @@ export class PortfolioService { |
|
|
|
|
|
|
|
userId = await this.getUserId(impersonationId, userId); |
|
|
|
|
|
|
|
const portfolioStart = parseDate(transactionPoints[0].date); |
|
|
|
const startDate = this.getStartDate(dateRange, portfolioStart); |
|
|
|
const startDate = this.getStartDate( |
|
|
|
dateRange, |
|
|
|
portfolioCalculator.getStartDate() |
|
|
|
); |
|
|
|
const endDate = new Date(); |
|
|
|
const daysInMarket = differenceInDays(endDate, startDate) + 1; |
|
|
|
const step = withDataDecimation |
|
|
@ -1865,10 +1839,10 @@ export class PortfolioService { |
|
|
|
const daysInMarket = differenceInDays(new Date(), firstOrderDate); |
|
|
|
|
|
|
|
const annualizedPerformancePercent = new PortfolioCalculator({ |
|
|
|
activities: [], |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: [] |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}) |
|
|
|
.getAnnualizedPerformancePercent({ |
|
|
|
daysInMarket, |
|
|
@ -1880,10 +1854,10 @@ export class PortfolioService { |
|
|
|
|
|
|
|
const annualizedPerformancePercentWithCurrencyEffect = |
|
|
|
new PortfolioCalculator({ |
|
|
|
activities: [], |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: [] |
|
|
|
exchangeRateDataService: this.exchangeRateDataService |
|
|
|
}) |
|
|
|
.getAnnualizedPerformancePercent({ |
|
|
|
daysInMarket, |
|
|
@ -1955,71 +1929,9 @@ export class PortfolioService { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
private async getTransactionPoints({ |
|
|
|
filters, |
|
|
|
includeDrafts = false, |
|
|
|
types = getAllActivityTypes(), |
|
|
|
userId, |
|
|
|
withExcludedAccounts = false |
|
|
|
}: { |
|
|
|
filters?: Filter[]; |
|
|
|
includeDrafts?: boolean; |
|
|
|
types?: ActivityType[]; |
|
|
|
userId: string; |
|
|
|
withExcludedAccounts?: boolean; |
|
|
|
}): Promise<{ |
|
|
|
activities: Activity[]; |
|
|
|
transactionPoints: TransactionPoint[]; |
|
|
|
portfolioOrders: PortfolioOrder[]; |
|
|
|
}> { |
|
|
|
const userCurrency = |
|
|
|
this.request.user?.Settings?.settings.baseCurrency ?? DEFAULT_CURRENCY; |
|
|
|
|
|
|
|
const { activities, count } = await this.orderService.getOrders({ |
|
|
|
filters, |
|
|
|
includeDrafts, |
|
|
|
types, |
|
|
|
userCurrency, |
|
|
|
userId, |
|
|
|
withExcludedAccounts |
|
|
|
}); |
|
|
|
|
|
|
|
if (count <= 0) { |
|
|
|
return { activities: [], transactionPoints: [], portfolioOrders: [] }; |
|
|
|
} |
|
|
|
|
|
|
|
const portfolioOrders: PortfolioOrder[] = activities.map((order) => ({ |
|
|
|
currency: order.SymbolProfile.currency, |
|
|
|
dataSource: order.SymbolProfile.dataSource, |
|
|
|
date: format(order.date, DATE_FORMAT), |
|
|
|
fee: new Big(order.fee), |
|
|
|
name: order.SymbolProfile?.name, |
|
|
|
quantity: new Big(order.quantity), |
|
|
|
symbol: order.SymbolProfile.symbol, |
|
|
|
tags: order.tags, |
|
|
|
type: order.type, |
|
|
|
unitPrice: new Big(order.unitPrice) |
|
|
|
})); |
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
|
currency: userCurrency, |
|
|
|
currentRateService: this.currentRateService, |
|
|
|
exchangeRateDataService: this.exchangeRateDataService, |
|
|
|
orders: portfolioOrders |
|
|
|
}); |
|
|
|
|
|
|
|
portfolioCalculator.computeTransactionPoints(); |
|
|
|
|
|
|
|
return { |
|
|
|
activities, |
|
|
|
portfolioOrders, |
|
|
|
transactionPoints: portfolioCalculator.getTransactionPoints() |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private getUserCurrency(aUser: UserWithSettings) { |
|
|
|
private getUserCurrency(aUser?: UserWithSettings) { |
|
|
|
return ( |
|
|
|
aUser.Settings?.settings.baseCurrency ?? |
|
|
|
aUser?.Settings?.settings.baseCurrency ?? |
|
|
|
this.request.user?.Settings?.settings.baseCurrency ?? |
|
|
|
DEFAULT_CURRENCY |
|
|
|
); |
|
|
|