Feature/support assets in emergency fund (#1601)
* Support assets in emergency fund * Update changelog
This commit is contained in:
parent
0878941c4f
commit
d147c2313f
@ -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…
x
Reference in New Issue
Block a user