|
|
|
@ -6,6 +6,8 @@ import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfol
|
|
|
|
|
import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface';
|
|
|
|
|
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
|
|
|
|
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/portfolio-calculator';
|
|
|
|
|
import { UserSettings } from '@ghostfolio/api/app/user/interfaces/user-settings.interface';
|
|
|
|
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
|
|
|
|
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
|
|
|
|
import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment';
|
|
|
|
|
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
|
|
|
@ -20,7 +22,11 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation.ser
|
|
|
|
|
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
|
|
|
|
import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface';
|
|
|
|
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
|
|
|
|
import { UNKNOWN_KEY, baseCurrency } from '@ghostfolio/common/config';
|
|
|
|
|
import {
|
|
|
|
|
ASSET_SUB_CLASS_EMERGENCY_FUND,
|
|
|
|
|
UNKNOWN_KEY,
|
|
|
|
|
baseCurrency
|
|
|
|
|
} from '@ghostfolio/common/config';
|
|
|
|
|
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
|
|
|
|
import {
|
|
|
|
|
Accounts,
|
|
|
|
@ -75,7 +81,8 @@ export class PortfolioService {
|
|
|
|
|
private readonly orderService: OrderService,
|
|
|
|
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
|
|
|
|
private readonly rulesService: RulesService,
|
|
|
|
|
private readonly symbolProfileService: SymbolProfileService
|
|
|
|
|
private readonly symbolProfileService: SymbolProfileService,
|
|
|
|
|
private readonly userService: UserService
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
public async getAccounts(aUserId: string): Promise<AccountWithValue[]> {
|
|
|
|
@ -286,7 +293,11 @@ export class PortfolioService {
|
|
|
|
|
aDateRange: DateRange = 'max'
|
|
|
|
|
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
|
|
|
|
const userId = await this.getUserId(aImpersonationId, aUserId);
|
|
|
|
|
const user = await this.userService.user({ id: userId });
|
|
|
|
|
|
|
|
|
|
const emergencyFund = new Big(
|
|
|
|
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
|
|
|
|
);
|
|
|
|
|
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
|
|
|
|
const portfolioCalculator = new PortfolioCalculator(
|
|
|
|
|
this.currentRateService,
|
|
|
|
@ -381,6 +392,7 @@ export class PortfolioService {
|
|
|
|
|
|
|
|
|
|
const cashPositions = await this.getCashPositions({
|
|
|
|
|
cashDetails,
|
|
|
|
|
emergencyFund,
|
|
|
|
|
userCurrency,
|
|
|
|
|
investment: totalInvestment,
|
|
|
|
|
value: totalValue
|
|
|
|
@ -861,6 +873,7 @@ export class PortfolioService {
|
|
|
|
|
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
|
|
|
|
const userCurrency = this.request.user.Settings.currency;
|
|
|
|
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
|
|
|
|
const user = await this.userService.user({ id: userId });
|
|
|
|
|
|
|
|
|
|
const performanceInformation = await this.getPerformance(aImpersonationId);
|
|
|
|
|
|
|
|
|
@ -873,6 +886,9 @@ export class PortfolioService {
|
|
|
|
|
userId
|
|
|
|
|
});
|
|
|
|
|
const dividend = this.getDividend(orders).toNumber();
|
|
|
|
|
const emergencyFund = new Big(
|
|
|
|
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
|
|
|
|
);
|
|
|
|
|
const fees = this.getFees(orders).toNumber();
|
|
|
|
|
const firstOrderDate = orders[0]?.date;
|
|
|
|
|
const items = this.getItems(orders).toNumber();
|
|
|
|
@ -880,6 +896,7 @@ export class PortfolioService {
|
|
|
|
|
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
|
|
|
|
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
|
|
|
|
|
|
|
|
|
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
|
|
|
|
|
const committedFunds = new Big(totalBuy).minus(totalSell);
|
|
|
|
|
|
|
|
|
|
const netWorth = new Big(balanceInBaseCurrency)
|
|
|
|
@ -889,6 +906,7 @@ export class PortfolioService {
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...performanceInformation.performance,
|
|
|
|
|
cash,
|
|
|
|
|
dividend,
|
|
|
|
|
fees,
|
|
|
|
|
firstOrderDate,
|
|
|
|
@ -898,8 +916,8 @@ export class PortfolioService {
|
|
|
|
|
totalSell,
|
|
|
|
|
annualizedPerformancePercent:
|
|
|
|
|
performanceInformation.performance.annualizedPerformancePercent,
|
|
|
|
|
cash: balanceInBaseCurrency,
|
|
|
|
|
committedFunds: committedFunds.toNumber(),
|
|
|
|
|
emergencyFund: emergencyFund.toNumber(),
|
|
|
|
|
ordersCount: orders.filter((order) => {
|
|
|
|
|
return order.type === 'BUY' || order.type === 'SELL';
|
|
|
|
|
}).length
|
|
|
|
@ -908,16 +926,18 @@ export class PortfolioService {
|
|
|
|
|
|
|
|
|
|
private async getCashPositions({
|
|
|
|
|
cashDetails,
|
|
|
|
|
emergencyFund,
|
|
|
|
|
investment,
|
|
|
|
|
userCurrency,
|
|
|
|
|
value
|
|
|
|
|
}: {
|
|
|
|
|
cashDetails: CashDetails;
|
|
|
|
|
emergencyFund: Big;
|
|
|
|
|
investment: Big;
|
|
|
|
|
userCurrency: string;
|
|
|
|
|
value: Big;
|
|
|
|
|
}) {
|
|
|
|
|
const cashPositions = {};
|
|
|
|
|
const cashPositions: PortfolioDetails['holdings'] = {};
|
|
|
|
|
|
|
|
|
|
for (const account of cashDetails.accounts) {
|
|
|
|
|
const convertedBalance = this.exchangeRateDataService.toCurrency(
|
|
|
|
@ -941,6 +961,7 @@ export class PortfolioService {
|
|
|
|
|
assetSubClass: AssetClass.CASH,
|
|
|
|
|
countries: [],
|
|
|
|
|
currency: account.currency,
|
|
|
|
|
dataSource: undefined,
|
|
|
|
|
grossPerformance: 0,
|
|
|
|
|
grossPerformancePercent: 0,
|
|
|
|
|
investment: convertedBalance,
|
|
|
|
@ -958,6 +979,28 @@ export class PortfolioService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (emergencyFund.gt(0)) {
|
|
|
|
|
cashPositions[ASSET_SUB_CLASS_EMERGENCY_FUND] = {
|
|
|
|
|
...cashPositions[userCurrency],
|
|
|
|
|
assetSubClass: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
|
|
|
|
investment: emergencyFund.toNumber(),
|
|
|
|
|
name: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
|
|
|
|
symbol: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
|
|
|
|
value: emergencyFund.toNumber()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
cashPositions[userCurrency].investment = new Big(
|
|
|
|
|
cashPositions[userCurrency].investment
|
|
|
|
|
)
|
|
|
|
|
.minus(emergencyFund)
|
|
|
|
|
.toNumber();
|
|
|
|
|
cashPositions[userCurrency].value = new Big(
|
|
|
|
|
cashPositions[userCurrency].value
|
|
|
|
|
)
|
|
|
|
|
.minus(emergencyFund)
|
|
|
|
|
.toNumber();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const symbol of Object.keys(cashPositions)) {
|
|
|
|
|
// Calculate allocations for each currency
|
|
|
|
|
cashPositions[symbol].allocationCurrent = new Big(
|
|
|
|
|