|
|
@ -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
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|