Feature/support assets in emergency fund (#1601)

* Support assets in emergency fund

* Update changelog
pull/1603/head
Thomas Kaul 2 years ago committed by GitHub
parent 0878941c4f
commit d147c2313f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added support for assets other than cash in emergency fund (affecting buying power)
- Added support for translated tags - Added support for translated tags
### Changed ### Changed

@ -1,5 +1,6 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service'; import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface'; import { CashDetails } from '@ghostfolio/api/app/account/interfaces/cash-details.interface';
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
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';
@ -597,7 +598,12 @@ export class PortfolioService {
const summary = await this.getSummary({ const summary = await this.getSummary({
impersonationId, impersonationId,
userCurrency, userCurrency,
userId userId,
balanceInBaseCurrency: cashDetails.balanceInBaseCurrency,
emergencyFundPositionsValueInBaseCurrency:
this.getEmergencyFundPositionsValueInBaseCurrency({
activities: orders
})
}); });
return { return {
@ -1167,7 +1173,7 @@ export class PortfolioService {
new FeeRatioInitialInvestment( new FeeRatioInitialInvestment(
this.exchangeRateDataService, this.exchangeRateDataService,
currentPositions.totalInvestment.toNumber(), currentPositions.totalInvestment.toNumber(),
this.getFees({ orders, userCurrency }).toNumber() this.getFees({ userCurrency, activities: orders }).toNumber()
) )
], ],
<UserSettings>this.request.user.Settings.settings <UserSettings>this.request.user.Settings.settings
@ -1254,26 +1260,27 @@ export class PortfolioService {
} }
private getDividend({ private getDividend({
activities,
date = new Date(0), date = new Date(0),
orders,
userCurrency userCurrency
}: { }: {
activities: OrderWithAccount[];
date?: Date; date?: Date;
orders: OrderWithAccount[];
userCurrency: string; userCurrency: string;
}) { }) {
return orders return activities
.filter((order) => { .filter((activity) => {
// Filter out all orders before given date and type dividend // Filter out all activities before given date and type dividend
return ( return (
isBefore(date, new Date(order.date)) && isBefore(date, new Date(activity.date)) &&
order.type === TypeOfOrder.DIVIDEND activity.type === TypeOfOrder.DIVIDEND
); );
}) })
.map((order) => { .map(({ quantity, SymbolProfile, unitPrice }) => {
return this.exchangeRateDataService.toCurrency( return this.exchangeRateDataService.toCurrency(
new Big(order.quantity).mul(order.unitPrice).toNumber(), new Big(quantity).mul(unitPrice).toNumber(),
order.SymbolProfile.currency, SymbolProfile.currency,
userCurrency userCurrency
); );
}) })
@ -1345,24 +1352,56 @@ export class PortfolioService {
return dividendsByGroup; return dividendsByGroup;
} }
private getEmergencyFundPositionsValueInBaseCurrency({
activities
}: {
activities: Activity[];
}) {
const emergencyFundOrders = activities.filter((activity) => {
return (
activity.tags?.some(({ name }) => {
return name === 'EMERGENCY_FUND';
}) ?? false
);
});
let valueInBaseCurrencyOfEmergencyFundPositions = new Big(0);
for (const order of emergencyFundOrders) {
if (order.type === 'BUY') {
valueInBaseCurrencyOfEmergencyFundPositions =
valueInBaseCurrencyOfEmergencyFundPositions.plus(
order.valueInBaseCurrency
);
} else if (order.type === 'SELL') {
valueInBaseCurrencyOfEmergencyFundPositions =
valueInBaseCurrencyOfEmergencyFundPositions.minus(
order.valueInBaseCurrency
);
}
}
return valueInBaseCurrencyOfEmergencyFundPositions.toNumber();
}
private getFees({ private getFees({
activities,
date = new Date(0), date = new Date(0),
orders,
userCurrency userCurrency
}: { }: {
activities: OrderWithAccount[];
date?: Date; date?: Date;
orders: OrderWithAccount[];
userCurrency: string; userCurrency: string;
}) { }) {
return orders return activities
.filter((order) => { .filter((activity) => {
// Filter out all orders before given date // Filter out all activities before given date
return isBefore(date, new Date(order.date)); return isBefore(date, new Date(activity.date));
}) })
.map((order) => { .map(({ fee, SymbolProfile }) => {
return this.exchangeRateDataService.toCurrency( return this.exchangeRateDataService.toCurrency(
order.fee, fee,
order.SymbolProfile.currency, SymbolProfile.currency,
userCurrency userCurrency
); );
}) })
@ -1445,10 +1484,14 @@ export class PortfolioService {
} }
private async getSummary({ private async getSummary({
balanceInBaseCurrency,
emergencyFundPositionsValueInBaseCurrency,
impersonationId, impersonationId,
userCurrency, userCurrency,
userId userId
}: { }: {
balanceInBaseCurrency: number;
emergencyFundPositionsValueInBaseCurrency: number;
impersonationId: string; impersonationId: string;
userCurrency: string; userCurrency: string;
userId: string; userId: string;
@ -1461,11 +1504,7 @@ export class PortfolioService {
userId userId
}); });
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({ const activities = await this.orderService.getOrders({
userId,
currency: userCurrency
});
const orders = await this.orderService.getOrders({
userCurrency, userCurrency,
userId userId
}); });
@ -1480,18 +1519,24 @@ export class PortfolioService {
return account?.isExcluded ?? false; return account?.isExcluded ?? false;
}); });
const dividend = this.getDividend({ orders, userCurrency }).toNumber(); const dividend = this.getDividend({
activities,
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, userCurrency }).toNumber(); const fees = this.getFees({ activities, userCurrency }).toNumber();
const firstOrderDate = orders[0]?.date; const firstOrderDate = activities[0]?.date;
const items = this.getItems(orders).toNumber(); const items = this.getItems(activities).toNumber();
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY'); const totalBuy = this.getTotalByType(activities, userCurrency, 'BUY');
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL'); const totalSell = this.getTotalByType(activities, userCurrency, 'SELL');
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber(); const cash = new Big(balanceInBaseCurrency)
.minus(emergencyFund)
.plus(emergencyFundPositionsValueInBaseCurrency)
.toNumber();
const committedFunds = new Big(totalBuy).minus(totalSell); const committedFunds = new Big(totalBuy).minus(totalSell);
const totalOfExcludedActivities = new Big( const totalOfExcludedActivities = new Big(
this.getTotalByType(excludedActivities, userCurrency, 'BUY') this.getTotalByType(excludedActivities, userCurrency, 'BUY')
@ -1547,8 +1592,8 @@ export class PortfolioService {
totalSell, totalSell,
committedFunds: committedFunds.toNumber(), committedFunds: committedFunds.toNumber(),
emergencyFund: emergencyFund.toNumber(), emergencyFund: emergencyFund.toNumber(),
ordersCount: orders.filter((order) => { ordersCount: activities.filter(({ type }) => {
return order.type === 'BUY' || order.type === 'SELL'; return type === 'BUY' || type === 'SELL';
}).length }).length
}; };
} }
@ -1565,7 +1610,7 @@ export class PortfolioService {
withExcludedAccounts?: boolean; withExcludedAccounts?: boolean;
}): Promise<{ }): Promise<{
transactionPoints: TransactionPoint[]; transactionPoints: TransactionPoint[];
orders: OrderWithAccount[]; orders: Activity[];
portfolioOrders: PortfolioOrder[]; portfolioOrders: PortfolioOrder[];
}> { }> {
const userCurrency = const userCurrency =

@ -288,6 +288,16 @@ async function main() {
skipDuplicates: true skipDuplicates: true
}); });
await prisma.tag.createMany({
data: [
{
id: '4452656d-9fa4-4bd0-ba38-70492e31d180',
name: 'EMERGENCY_FUND'
}
],
skipDuplicates: true
});
console.log({ console.log({
platformBitcoinSuisse, platformBitcoinSuisse,
platformBitpanda, platformBitpanda,

Loading…
Cancel
Save