Feature/dividend (#547)

* Add dividend to order type

* Support dividend in transactions table

* Support dividend in transaction dialog

* Extend import file with dividend

* Add dividend to portfolio summary

* Update changelog

Co-authored-by: Fly Man <fly.man.opensim@gmail.com>
pull/592/head
Thomas Kaul 2 years ago committed by GitHub
parent ff638adf03
commit 9e1a7fc981
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added the transactions to the position detail dialog
- Added support for dividend
### Todo
- Apply data migration (`yarn database:migrate`)
## 1.96.0 - 27.12.2021

@ -3,7 +3,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.se
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { OrderWithAccount } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { DataSource, Order, Prisma } from '@prisma/client';
import { DataSource, Order, Prisma, Type as TypeOfOrder } from '@prisma/client';
import Big from 'big.js';
import { endOfToday, isAfter } from 'date-fns';
@ -85,9 +85,11 @@ export class OrderService {
public async getOrders({
includeDrafts = false,
types,
userId
}: {
includeDrafts?: boolean;
types?: TypeOfOrder[];
userId: string;
}) {
const where: Prisma.OrderWhereInput = { userId };
@ -96,6 +98,16 @@ export class OrderService {
where.isDraft = false;
}
if (types) {
where.OR = types.map((type) => {
return {
type: {
equals: type
}
};
});
}
return (
await this.orders({
where,

@ -330,6 +330,7 @@ export class PortfolioController {
'currentGrossPerformance',
'currentNetPerformance',
'currentValue',
'dividend',
'fees',
'netWorth',
'totalBuy',

@ -401,17 +401,21 @@ export class PortfolioService {
const positionCurrency = orders[0].currency;
const name = orders[0].SymbolProfile?.name ?? '';
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
currency: order.currency,
dataSource: order.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.symbol,
type: order.type,
unitPrice: new Big(order.unitPrice)
}));
const portfolioOrders: PortfolioOrder[] = orders
.filter((order) => {
return order.type === 'BUY' || order.type === 'SELL';
})
.map((order) => ({
currency: order.currency,
dataSource: order.dataSource,
date: format(order.date, DATE_FORMAT),
fee: new Big(order.fee),
name: order.SymbolProfile?.name,
quantity: new Big(order.quantity),
symbol: order.symbol,
type: order.type,
unitPrice: new Big(order.unitPrice)
}));
const portfolioCalculator = new PortfolioCalculator(
this.currentRateService,
@ -729,22 +733,6 @@ export class PortfolioService {
};
}
public getFees(orders: OrderWithAccount[], date = new Date(0)) {
return orders
.filter((order) => {
// Filter out all orders before given date
return isBefore(date, new Date(order.date));
})
.map((order) => {
return this.exchangeRateDataService.toCurrency(
order.fee,
order.currency,
this.request.user.Settings.currency
);
})
.reduce((previous, current) => previous + current, 0);
}
public async getReport(impersonationId: string): Promise<PortfolioReport> {
const currency = this.request.user.Settings.currency;
const userId = await this.getUserId(impersonationId, this.request.user.id);
@ -825,7 +813,7 @@ export class PortfolioService {
new FeeRatioInitialInvestment(
this.exchangeRateDataService,
currentPositions.totalInvestment.toNumber(),
this.getFees(orders)
this.getFees(orders).toNumber()
)
],
{ baseCurrency: currency }
@ -844,8 +832,11 @@ export class PortfolioService {
userId,
currency
);
const orders = await this.orderService.getOrders({ userId });
const fees = this.getFees(orders);
const orders = await this.orderService.getOrders({
userId
});
const dividend = this.getDividend(orders).toNumber();
const fees = this.getFees(orders).toNumber();
const firstOrderDate = orders[0]?.date;
const totalBuy = this.getTotalByType(orders, currency, 'BUY');
@ -859,14 +850,17 @@ export class PortfolioService {
return {
...performanceInformation.performance,
dividend,
fees,
firstOrderDate,
netWorth,
totalBuy,
totalSell,
cash: balance,
committedFunds: committedFunds.toNumber(),
ordersCount: orders.length,
totalBuy: totalBuy,
totalSell: totalSell
ordersCount: orders.filter((order) => {
return order.type === 'BUY' || order.type === 'SELL';
}).length
};
}
@ -939,6 +933,47 @@ export class PortfolioService {
return cashPositions;
}
private getDividend(orders: OrderWithAccount[], date = new Date(0)) {
return orders
.filter((order) => {
// Filter out all orders before given date and type dividend
return (
isBefore(date, new Date(order.date)) &&
order.type === TypeOfOrder.DIVIDEND
);
})
.map((order) => {
return this.exchangeRateDataService.toCurrency(
new Big(order.quantity).mul(order.unitPrice).toNumber(),
order.currency,
this.request.user.Settings.currency
);
})
.reduce(
(previous, current) => new Big(previous).plus(current),
new Big(0)
);
}
private getFees(orders: OrderWithAccount[], date = new Date(0)) {
return orders
.filter((order) => {
// Filter out all orders before given date
return isBefore(date, new Date(order.date));
})
.map((order) => {
return this.exchangeRateDataService.toCurrency(
order.fee,
order.currency,
this.request.user.Settings.currency
);
})
.reduce(
(previous, current) => new Big(previous).plus(current),
new Big(0)
);
}
private getStartDate(aDateRange: DateRange, portfolioStart: Date) {
switch (aDateRange) {
case '1d':
@ -967,7 +1002,11 @@ export class PortfolioService {
transactionPoints: TransactionPoint[];
orders: OrderWithAccount[];
}> {
const orders = await this.orderService.getOrders({ includeDrafts, userId });
const orders = await this.orderService.getOrders({
includeDrafts,
userId,
types: ['BUY', 'SELL']
});
if (orders.length <= 0) {
return { transactionPoints: [], orders: [] };

@ -169,4 +169,18 @@
></gf-value>
</div>
</div>
<div class="row">
<div class="col"><hr /></div>
</div>
<div class="row px-3 py-1">
<div class="d-flex flex-grow-1" i18n>Dividend</div>
<div class="d-flex justify-content-end">
<gf-value
class="justify-content-end"
[currency]="baseCurrency"
[locale]="locale"
[value]="isLoading ? undefined : summary?.dividend"
></gf-value>
</div>
</div>
</div>

@ -77,11 +77,15 @@
<td mat-cell *matCellDef="let element" class="px-1">
<div
class="d-inline-flex p-1 type-badge"
[ngClass]="element.type == 'BUY' ? 'buy' : 'sell'"
[ngClass]="{
buy: element.type === 'BUY',
dividend: element.type === 'DIVIDEND',
sell: element.type === 'SELL'
}"
>
<ion-icon
[name]="
element.type === 'BUY'
element.type === 'BUY' || element.type === 'DIVIDEND'
? 'arrow-forward-circle-outline'
: 'arrow-back-circle-outline'
"

@ -37,6 +37,10 @@
color: var(--green);
}
&.dividend {
color: var(--blue);
}
&.sell {
color: var(--orange);
}

@ -52,8 +52,9 @@
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Type</mat-label>
<mat-select name="type" required [(value)]="data.transaction.type">
<mat-option value="BUY" i18n> BUY </mat-option>
<mat-option value="SELL" i18n> SELL </mat-option>
<mat-option value="BUY" i18n>BUY</mat-option>
<mat-option value="DIVIDEND" i18n>DIVIDEND</mat-option>
<mat-option value="SELL" i18n>SELL</mat-option>
</mat-select>
</mat-form-field>
</div>
@ -141,7 +142,7 @@
[(ngModel)]="data.transaction.unitPrice"
/>
<button
*ngIf="currentMarketPrice"
*ngIf="currentMarketPrice && (data.transaction.type === 'BUY' || data.transaction.type === 'SELL')"
mat-icon-button
matSuffix
title="Apply current market price"

@ -214,10 +214,15 @@ export class ImportTransactionsService {
for (const key of ImportTransactionsService.TYPE_KEYS) {
if (item[key]) {
if (item[key].toLowerCase() === 'buy') {
return Type.BUY;
} else if (item[key].toLowerCase() === 'sell') {
return Type.SELL;
switch (item[key].toLowerCase()) {
case 'buy':
return Type.BUY;
case 'dividend':
return Type.DIVIDEND;
case 'sell':
return Type.SELL;
default:
break;
}
}
}

@ -3,6 +3,7 @@ import { PortfolioPerformance } from './portfolio-performance.interface';
export interface PortfolioSummary extends PortfolioPerformance {
annualizedPerformancePercent: number;
cash: number;
dividend: number;
committedFunds: number;
fees: number;
firstOrderDate: Date;

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "Type" ADD VALUE 'DIVIDEND';

@ -206,5 +206,6 @@ enum Role {
enum Type {
BUY
DIVIDEND
SELL
}

@ -1,2 +1,3 @@
Date,Code,Currency,Price,Quantity,Action,Fee
17/11/2021,MSFT,USD,0.62,5,dividend,0.00
16/09/2021,MSFT,USD,298.580,5,buy,19.00

1 Date Code Currency Price Quantity Action Fee
2 17/11/2021 MSFT USD 0.62 5 dividend 0.00
3 16/09/2021 MSFT USD 298.580 5 buy 19.00
Loading…
Cancel
Save