|
@ -3,7 +3,6 @@ import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details |
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
import { OrderService } from '@ghostfolio/api/app/order/order.service'; |
|
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; |
|
|
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service'; |
|
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; |
|
|
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface'; |
|
|
import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.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 { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; |
|
|
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; |
|
@ -36,7 +35,8 @@ import { |
|
|
PortfolioSummary, |
|
|
PortfolioSummary, |
|
|
Position, |
|
|
Position, |
|
|
TimelinePosition, |
|
|
TimelinePosition, |
|
|
UserSettings |
|
|
UserSettings, |
|
|
|
|
|
UserWithSettings |
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
} from '@ghostfolio/common/interfaces'; |
|
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; |
|
|
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; |
|
|
import type { |
|
|
import type { |
|
@ -67,11 +67,9 @@ import { |
|
|
isAfter, |
|
|
isAfter, |
|
|
isBefore, |
|
|
isBefore, |
|
|
max, |
|
|
max, |
|
|
parse, |
|
|
|
|
|
parseISO, |
|
|
parseISO, |
|
|
set, |
|
|
set, |
|
|
setDayOfYear, |
|
|
setDayOfYear, |
|
|
startOfDay, |
|
|
|
|
|
subDays, |
|
|
subDays, |
|
|
subYears |
|
|
subYears |
|
|
} from 'date-fns'; |
|
|
} from 'date-fns'; |
|
@ -130,9 +128,9 @@ export class PortfolioService { |
|
|
}), |
|
|
}), |
|
|
this.getDetails({ |
|
|
this.getDetails({ |
|
|
filters, |
|
|
filters, |
|
|
userId, |
|
|
|
|
|
withExcludedAccounts, |
|
|
withExcludedAccounts, |
|
|
impersonationId: userId |
|
|
impersonationId: userId, |
|
|
|
|
|
userId: this.request.user.id |
|
|
}) |
|
|
}) |
|
|
]); |
|
|
]); |
|
|
|
|
|
|
|
@ -304,12 +302,16 @@ export class PortfolioService { |
|
|
|
|
|
|
|
|
public async getChart({ |
|
|
public async getChart({ |
|
|
dateRange = 'max', |
|
|
dateRange = 'max', |
|
|
impersonationId |
|
|
impersonationId, |
|
|
|
|
|
userCurrency, |
|
|
|
|
|
userId |
|
|
}: { |
|
|
}: { |
|
|
dateRange?: DateRange; |
|
|
dateRange?: DateRange; |
|
|
impersonationId: string; |
|
|
impersonationId: string; |
|
|
|
|
|
userCurrency: string; |
|
|
|
|
|
userId: string; |
|
|
}): Promise<HistoricalDataContainer> { |
|
|
}): Promise<HistoricalDataContainer> { |
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
userId = await this.getUserId(impersonationId, userId); |
|
|
|
|
|
|
|
|
const { portfolioOrders, transactionPoints } = |
|
|
const { portfolioOrders, transactionPoints } = |
|
|
await this.getTransactionPoints({ |
|
|
await this.getTransactionPoints({ |
|
@ -317,7 +319,7 @@ export class PortfolioService { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
currency: this.request.user.Settings.settings.baseCurrency, |
|
|
currency: userCurrency, |
|
|
currentRateService: this.currentRateService, |
|
|
currentRateService: this.currentRateService, |
|
|
orders: portfolioOrders |
|
|
orders: portfolioOrders |
|
|
}); |
|
|
}); |
|
@ -355,28 +357,24 @@ export class PortfolioService { |
|
|
|
|
|
|
|
|
public async getDetails({ |
|
|
public async getDetails({ |
|
|
impersonationId, |
|
|
impersonationId, |
|
|
userId, |
|
|
|
|
|
dateRange = 'max', |
|
|
dateRange = 'max', |
|
|
filters, |
|
|
filters, |
|
|
|
|
|
userId, |
|
|
withExcludedAccounts = false |
|
|
withExcludedAccounts = false |
|
|
}: { |
|
|
}: { |
|
|
impersonationId: string; |
|
|
impersonationId: string; |
|
|
userId: string; |
|
|
|
|
|
dateRange?: DateRange; |
|
|
dateRange?: DateRange; |
|
|
filters?: Filter[]; |
|
|
filters?: Filter[]; |
|
|
|
|
|
userId: string; |
|
|
withExcludedAccounts?: boolean; |
|
|
withExcludedAccounts?: boolean; |
|
|
}): Promise<PortfolioDetails & { hasErrors: boolean }> { |
|
|
}): Promise<PortfolioDetails & { hasErrors: boolean }> { |
|
|
// TODO
|
|
|
|
|
|
userId = await this.getUserId(impersonationId, userId); |
|
|
userId = await this.getUserId(impersonationId, userId); |
|
|
const user = await this.userService.user({ id: userId }); |
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
const userCurrency = this.getUserCurrency(user); |
|
|
|
|
|
|
|
|
const emergencyFund = new Big( |
|
|
const emergencyFund = new Big( |
|
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 |
|
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 |
|
|
); |
|
|
); |
|
|
const userCurrency = |
|
|
|
|
|
user.Settings?.settings.baseCurrency ?? |
|
|
|
|
|
this.request.user?.Settings?.settings.baseCurrency ?? |
|
|
|
|
|
this.baseCurrency; |
|
|
|
|
|
|
|
|
|
|
|
const { orders, portfolioOrders, transactionPoints } = |
|
|
const { orders, portfolioOrders, transactionPoints } = |
|
|
await this.getTransactionPoints({ |
|
|
await this.getTransactionPoints({ |
|
@ -540,7 +538,11 @@ export class PortfolioService { |
|
|
withExcludedAccounts |
|
|
withExcludedAccounts |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const summary = await this.getSummary({ impersonationId }); |
|
|
const summary = await this.getSummary({ |
|
|
|
|
|
impersonationId, |
|
|
|
|
|
userCurrency, |
|
|
|
|
|
userId |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
accounts, |
|
|
accounts, |
|
@ -560,8 +562,9 @@ export class PortfolioService { |
|
|
aImpersonationId: string, |
|
|
aImpersonationId: string, |
|
|
aSymbol: string |
|
|
aSymbol: string |
|
|
): Promise<PortfolioPositionDetail> { |
|
|
): Promise<PortfolioPositionDetail> { |
|
|
const userCurrency = this.request.user.Settings.settings.baseCurrency; |
|
|
|
|
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id); |
|
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id); |
|
|
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
const userCurrency = this.getUserCurrency(user); |
|
|
|
|
|
|
|
|
const orders = ( |
|
|
const orders = ( |
|
|
await this.orderService.getOrders({ |
|
|
await this.orderService.getOrders({ |
|
@ -883,12 +886,16 @@ export class PortfolioService { |
|
|
|
|
|
|
|
|
public async getPerformance({ |
|
|
public async getPerformance({ |
|
|
dateRange = 'max', |
|
|
dateRange = 'max', |
|
|
impersonationId |
|
|
impersonationId, |
|
|
|
|
|
userId |
|
|
}: { |
|
|
}: { |
|
|
dateRange?: DateRange; |
|
|
dateRange?: DateRange; |
|
|
impersonationId: string; |
|
|
impersonationId: string; |
|
|
|
|
|
userId: string; |
|
|
}): Promise<PortfolioPerformanceResponse> { |
|
|
}): Promise<PortfolioPerformanceResponse> { |
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
userId = await this.getUserId(impersonationId, userId); |
|
|
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
const userCurrency = this.getUserCurrency(user); |
|
|
|
|
|
|
|
|
const { portfolioOrders, transactionPoints } = |
|
|
const { portfolioOrders, transactionPoints } = |
|
|
await this.getTransactionPoints({ |
|
|
await this.getTransactionPoints({ |
|
@ -896,7 +903,7 @@ export class PortfolioService { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
currency: this.request.user.Settings.settings.baseCurrency, |
|
|
currency: userCurrency, |
|
|
currentRateService: this.currentRateService, |
|
|
currentRateService: this.currentRateService, |
|
|
orders: portfolioOrders |
|
|
orders: portfolioOrders |
|
|
}); |
|
|
}); |
|
@ -947,7 +954,9 @@ export class PortfolioService { |
|
|
|
|
|
|
|
|
const historicalDataContainer = await this.getChart({ |
|
|
const historicalDataContainer = await this.getChart({ |
|
|
dateRange, |
|
|
dateRange, |
|
|
impersonationId |
|
|
impersonationId, |
|
|
|
|
|
userCurrency, |
|
|
|
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const itemOfToday = historicalDataContainer.items.find((item) => { |
|
|
const itemOfToday = historicalDataContainer.items.find((item) => { |
|
@ -995,8 +1004,9 @@ export class PortfolioService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getReport(impersonationId: string): Promise<PortfolioReport> { |
|
|
public async getReport(impersonationId: string): Promise<PortfolioReport> { |
|
|
const currency = this.request.user.Settings.settings.baseCurrency; |
|
|
|
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
const userCurrency = this.getUserCurrency(user); |
|
|
|
|
|
|
|
|
const { orders, portfolioOrders, transactionPoints } = |
|
|
const { orders, portfolioOrders, transactionPoints } = |
|
|
await this.getTransactionPoints({ |
|
|
await this.getTransactionPoints({ |
|
@ -1010,7 +1020,7 @@ export class PortfolioService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
const portfolioCalculator = new PortfolioCalculator({ |
|
|
currency, |
|
|
currency: userCurrency, |
|
|
currentRateService: this.currentRateService, |
|
|
currentRateService: this.currentRateService, |
|
|
orders: portfolioOrders |
|
|
orders: portfolioOrders |
|
|
}); |
|
|
}); |
|
@ -1030,7 +1040,7 @@ export class PortfolioService { |
|
|
orders, |
|
|
orders, |
|
|
portfolioItemsNow, |
|
|
portfolioItemsNow, |
|
|
userId, |
|
|
userId, |
|
|
userCurrency: currency |
|
|
userCurrency |
|
|
}); |
|
|
}); |
|
|
return { |
|
|
return { |
|
|
rules: { |
|
|
rules: { |
|
@ -1077,7 +1087,7 @@ export class PortfolioService { |
|
|
new FeeRatioInitialInvestment( |
|
|
new FeeRatioInitialInvestment( |
|
|
this.exchangeRateDataService, |
|
|
this.exchangeRateDataService, |
|
|
currentPositions.totalInvestment.toNumber(), |
|
|
currentPositions.totalInvestment.toNumber(), |
|
|
this.getFees(orders).toNumber() |
|
|
this.getFees({ orders, userCurrency }).toNumber() |
|
|
) |
|
|
) |
|
|
], |
|
|
], |
|
|
<UserSettings>this.request.user.Settings.settings |
|
|
<UserSettings>this.request.user.Settings.settings |
|
@ -1180,7 +1190,15 @@ export class PortfolioService { |
|
|
return cashPositions; |
|
|
return cashPositions; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getDividend(orders: OrderWithAccount[], date = new Date(0)) { |
|
|
private getDividend({ |
|
|
|
|
|
date = new Date(0), |
|
|
|
|
|
orders, |
|
|
|
|
|
userCurrency |
|
|
|
|
|
}: { |
|
|
|
|
|
date?: Date; |
|
|
|
|
|
orders: OrderWithAccount[]; |
|
|
|
|
|
userCurrency: string; |
|
|
|
|
|
}) { |
|
|
return orders |
|
|
return orders |
|
|
.filter((order) => { |
|
|
.filter((order) => { |
|
|
// Filter out all orders before given date and type dividend
|
|
|
// Filter out all orders before given date and type dividend
|
|
@ -1193,7 +1211,7 @@ export class PortfolioService { |
|
|
return this.exchangeRateDataService.toCurrency( |
|
|
return this.exchangeRateDataService.toCurrency( |
|
|
new Big(order.quantity).mul(order.unitPrice).toNumber(), |
|
|
new Big(order.quantity).mul(order.unitPrice).toNumber(), |
|
|
order.SymbolProfile.currency, |
|
|
order.SymbolProfile.currency, |
|
|
this.request.user.Settings.settings.baseCurrency |
|
|
userCurrency |
|
|
); |
|
|
); |
|
|
}) |
|
|
}) |
|
|
.reduce( |
|
|
.reduce( |
|
@ -1202,7 +1220,15 @@ export class PortfolioService { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getFees(orders: OrderWithAccount[], date = new Date(0)) { |
|
|
private getFees({ |
|
|
|
|
|
date = new Date(0), |
|
|
|
|
|
orders, |
|
|
|
|
|
userCurrency |
|
|
|
|
|
}: { |
|
|
|
|
|
date?: Date; |
|
|
|
|
|
orders: OrderWithAccount[]; |
|
|
|
|
|
userCurrency: string; |
|
|
|
|
|
}) { |
|
|
return orders |
|
|
return orders |
|
|
.filter((order) => { |
|
|
.filter((order) => { |
|
|
// Filter out all orders before given date
|
|
|
// Filter out all orders before given date
|
|
@ -1212,7 +1238,7 @@ export class PortfolioService { |
|
|
return this.exchangeRateDataService.toCurrency( |
|
|
return this.exchangeRateDataService.toCurrency( |
|
|
order.fee, |
|
|
order.fee, |
|
|
order.SymbolProfile.currency, |
|
|
order.SymbolProfile.currency, |
|
|
this.request.user.Settings.settings.baseCurrency |
|
|
userCurrency |
|
|
); |
|
|
); |
|
|
}) |
|
|
}) |
|
|
.reduce( |
|
|
.reduce( |
|
@ -1262,16 +1288,20 @@ export class PortfolioService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private async getSummary({ |
|
|
private async getSummary({ |
|
|
impersonationId |
|
|
impersonationId, |
|
|
|
|
|
userCurrency, |
|
|
|
|
|
userId |
|
|
}: { |
|
|
}: { |
|
|
impersonationId: string; |
|
|
impersonationId: string; |
|
|
|
|
|
userCurrency: string; |
|
|
|
|
|
userId: string; |
|
|
}): Promise<PortfolioSummary> { |
|
|
}): Promise<PortfolioSummary> { |
|
|
const userCurrency = this.request.user.Settings.settings.baseCurrency; |
|
|
userId = await this.getUserId(impersonationId, userId); |
|
|
const userId = await this.getUserId(impersonationId, this.request.user.id); |
|
|
|
|
|
const user = await this.userService.user({ id: userId }); |
|
|
const user = await this.userService.user({ id: userId }); |
|
|
|
|
|
|
|
|
const performanceInformation = await this.getPerformance({ |
|
|
const performanceInformation = await this.getPerformance({ |
|
|
impersonationId |
|
|
impersonationId, |
|
|
|
|
|
userId |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({ |
|
|
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({ |
|
@ -1293,11 +1323,11 @@ export class PortfolioService { |
|
|
return account?.isExcluded ?? false; |
|
|
return account?.isExcluded ?? false; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const dividend = this.getDividend(orders).toNumber(); |
|
|
const dividend = this.getDividend({ orders, userCurrency }).toNumber(); |
|
|
const emergencyFund = new Big( |
|
|
const emergencyFund = new Big( |
|
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 |
|
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0 |
|
|
); |
|
|
); |
|
|
const fees = this.getFees(orders).toNumber(); |
|
|
const fees = this.getFees({ orders, userCurrency }).toNumber(); |
|
|
const firstOrderDate = orders[0]?.date; |
|
|
const firstOrderDate = orders[0]?.date; |
|
|
const items = this.getItems(orders).toNumber(); |
|
|
const items = this.getItems(orders).toNumber(); |
|
|
|
|
|
|
|
@ -1565,4 +1595,12 @@ export class PortfolioService { |
|
|
}) |
|
|
}) |
|
|
.reduce((previous, current) => previous + current, 0); |
|
|
.reduce((previous, current) => previous + current, 0); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private getUserCurrency(aUser: UserWithSettings) { |
|
|
|
|
|
return ( |
|
|
|
|
|
aUser.Settings?.settings.baseCurrency ?? |
|
|
|
|
|
this.request.user?.Settings?.settings.baseCurrency ?? |
|
|
|
|
|
this.baseCurrency |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|