Browse Source

Added STAKE order type

- Calculation handling
- Displaying
- DB & Controller update
pull/2145/head
Daniel Devaud 2 years ago
parent
commit
5d06b13141
  1. 1
      apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts
  2. 19
      apps/api/src/app/portfolio/portfolio-calculator.ts
  3. 23
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 3
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts
  5. 16
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
  6. 6
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  7. 8
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html
  8. 2
      apps/client/src/app/services/import-activities.service.ts
  9. 9
      libs/ui/src/lib/activities-table/activities-table.component.html
  10. 4
      libs/ui/src/lib/activities-table/activities-table.component.scss
  11. 1
      prisma/schema.prisma

1
apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts

@ -10,6 +10,7 @@ export interface PortfolioPositionDetail {
averagePrice: number; averagePrice: number;
dataProviderInfo: DataProviderInfo; dataProviderInfo: DataProviderInfo;
dividendInBaseCurrency: number; dividendInBaseCurrency: number;
stakeRewards: number;
feeInBaseCurrency: number; feeInBaseCurrency: number;
firstBuyDate: string; firstBuyDate: string;
grossPerformance: number; grossPerformance: number;

19
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -92,7 +92,7 @@ export class PortfolioCalculator {
let investment = new Big(0); let investment = new Big(0);
if (newQuantity.gt(0)) { if (newQuantity.gt(0)) {
if (order.type === 'BUY') { if (order.type === 'BUY' || order.type === 'STAKE') {
investment = oldAccumulatedSymbol.investment.plus( investment = oldAccumulatedSymbol.investment.plus(
order.quantity.mul(unitPrice) order.quantity.mul(unitPrice)
); );
@ -931,6 +931,7 @@ export class PortfolioCalculator {
switch (type) { switch (type) {
case 'BUY': case 'BUY':
case 'STAKE':
factor = 1; factor = 1;
break; break;
case 'SELL': case 'SELL':
@ -1087,6 +1088,20 @@ export class PortfolioCalculator {
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ?? marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
lastUnitPrice lastUnitPrice
}); });
} else {
let orderIndex = orders.findIndex(
(o) => o.date === format(day, DATE_FORMAT) && o.type === 'STAKE'
);
if (orderIndex >= 0) {
let order = orders[orderIndex];
orders.splice(orderIndex, 1);
orders.push({
...order,
unitPrice:
marketSymbolMap[format(day, DATE_FORMAT)]?.[symbol] ??
lastUnitPrice
});
}
} }
lastUnitPrice = last(orders).unitPrice; lastUnitPrice = last(orders).unitPrice;
@ -1156,7 +1171,7 @@ export class PortfolioCalculator {
} }
const transactionInvestment = const transactionInvestment =
order.type === 'BUY' order.type === 'BUY' || order.type === 'STAKE'
? order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type)) ? order.quantity.mul(order.unitPrice).mul(this.getFactor(order.type))
: totalUnits.gt(0) : totalUnits.gt(0)
? totalInvestment ? totalInvestment

23
apps/api/src/app/portfolio/portfolio.service.ts

@ -702,6 +702,7 @@ export class PortfolioService {
averagePrice: undefined, averagePrice: undefined,
dataProviderInfo: undefined, dataProviderInfo: undefined,
dividendInBaseCurrency: undefined, dividendInBaseCurrency: undefined,
stakeRewards: undefined,
feeInBaseCurrency: undefined, feeInBaseCurrency: undefined,
firstBuyDate: undefined, firstBuyDate: undefined,
grossPerformance: undefined, grossPerformance: undefined,
@ -730,7 +731,11 @@ export class PortfolioService {
.filter((order) => { .filter((order) => {
tags = tags.concat(order.tags); tags = tags.concat(order.tags);
return order.type === 'BUY' || order.type === 'SELL'; return (
order.type === 'BUY' ||
order.type === 'SELL' ||
order.type === 'STAKE'
);
}) })
.map((order) => ({ .map((order) => ({
currency: order.SymbolProfile.currency, currency: order.SymbolProfile.currency,
@ -786,6 +791,16 @@ export class PortfolioService {
}) })
); );
const stakeRewards = getSum(
orders
.filter(({ type }) => {
return type === 'STAKE';
})
.map(({ quantity }) => {
return new Big(quantity);
})
);
// Convert investment, gross and net performance to currency of user // Convert investment, gross and net performance to currency of user
const investment = this.exchangeRateDataService.toCurrency( const investment = this.exchangeRateDataService.toCurrency(
position.investment?.toNumber(), position.investment?.toNumber(),
@ -880,6 +895,7 @@ export class PortfolioService {
averagePrice: averagePrice.toNumber(), averagePrice: averagePrice.toNumber(),
dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0], dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0],
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
stakeRewards: stakeRewards.toNumber(),
feeInBaseCurrency: this.exchangeRateDataService.toCurrency( feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
fee.toNumber(), fee.toNumber(),
SymbolProfile.currency, SymbolProfile.currency,
@ -943,6 +959,7 @@ export class PortfolioService {
averagePrice: 0, averagePrice: 0,
dataProviderInfo: undefined, dataProviderInfo: undefined,
dividendInBaseCurrency: 0, dividendInBaseCurrency: 0,
stakeRewards: 0,
feeInBaseCurrency: 0, feeInBaseCurrency: 0,
firstBuyDate: undefined, firstBuyDate: undefined,
grossPerformance: undefined, grossPerformance: undefined,
@ -1403,7 +1420,7 @@ export class PortfolioService {
let valueInBaseCurrencyOfEmergencyFundPositions = new Big(0); let valueInBaseCurrencyOfEmergencyFundPositions = new Big(0);
for (const order of emergencyFundOrders) { for (const order of emergencyFundOrders) {
if (order.type === 'BUY') { if (order.type === 'BUY' || order.type === 'STAKE') {
valueInBaseCurrencyOfEmergencyFundPositions = valueInBaseCurrencyOfEmergencyFundPositions =
valueInBaseCurrencyOfEmergencyFundPositions.plus( valueInBaseCurrencyOfEmergencyFundPositions.plus(
order.valueInBaseCurrency order.valueInBaseCurrency
@ -1714,7 +1731,7 @@ export class PortfolioService {
userCurrency, userCurrency,
userId, userId,
withExcludedAccounts, withExcludedAccounts,
types: ['BUY', 'SELL'] types: ['BUY', 'SELL', 'STAKE']
}); });
if (orders.length <= 0) { if (orders.length <= 0) {

3
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts

@ -40,6 +40,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
}; };
public dataProviderInfo: DataProviderInfo; public dataProviderInfo: DataProviderInfo;
public dividendInBaseCurrency: number; public dividendInBaseCurrency: number;
public stakeRewards: number;
public feeInBaseCurrency: number; public feeInBaseCurrency: number;
public firstBuyDate: string; public firstBuyDate: string;
public grossPerformance: number; public grossPerformance: number;
@ -84,6 +85,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
averagePrice, averagePrice,
dataProviderInfo, dataProviderInfo,
dividendInBaseCurrency, dividendInBaseCurrency,
stakeRewards,
feeInBaseCurrency, feeInBaseCurrency,
firstBuyDate, firstBuyDate,
grossPerformance, grossPerformance,
@ -107,6 +109,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.countries = {}; this.countries = {};
this.dataProviderInfo = dataProviderInfo; this.dataProviderInfo = dataProviderInfo;
this.dividendInBaseCurrency = dividendInBaseCurrency; this.dividendInBaseCurrency = dividendInBaseCurrency;
this.stakeRewards = stakeRewards;
this.feeInBaseCurrency = feeInBaseCurrency; this.feeInBaseCurrency = feeInBaseCurrency;
this.firstBuyDate = firstBuyDate; this.firstBuyDate = firstBuyDate;
this.grossPerformance = grossPerformance; this.grossPerformance = grossPerformance;

16
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html

@ -126,7 +126,10 @@
>Investment</gf-value >Investment</gf-value
> >
</div> </div>
<div class="col-6 mb-3"> <div
*ngIf="dividendInBaseCurrency > 0 || !stakeRewards"
class="col-6 mb-3"
>
<gf-value <gf-value
i18n i18n
size="medium" size="medium"
@ -137,6 +140,17 @@
>Dividend</gf-value >Dividend</gf-value
> >
</div> </div>
<div *ngIf="stakeRewards > 0" class="col-6 mb-3">
<gf-value
i18n
size="medium"
[locale]="data.locale"
[precision]="quantityPrecision"
[value]="stakeRewards"
>Stake Rewards
</gf-value>
</div>
<div class="col-6 mb-3"> <div class="col-6 mb-3">
<gf-value <gf-value
i18n i18n

6
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -228,6 +228,10 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
this.activityForm.controls['quantity'].value * this.activityForm.controls['quantity'].value *
this.activityForm.controls['unitPrice'].value + this.activityForm.controls['unitPrice'].value +
this.activityForm.controls['fee'].value ?? 0; this.activityForm.controls['fee'].value ?? 0;
} else if (this.activityForm.controls['type'].value === 'STAKE') {
this.total =
this.activityForm.controls['quantity'].value *
this.currentMarketPrice ?? 0;
} else { } else {
this.total = this.total =
this.activityForm.controls['quantity'].value * this.activityForm.controls['quantity'].value *
@ -242,7 +246,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
if (this.activityForm.controls['searchSymbol'].invalid) { if (this.activityForm.controls['searchSymbol'].invalid) {
this.data.activity.SymbolProfile = null; this.data.activity.SymbolProfile = null;
} else if ( } else if (
['BUY', 'DIVIDEND', 'SELL'].includes( ['BUY', 'DIVIDEND', 'SELL', 'STAKE'].includes(
this.activityForm.controls['type'].value this.activityForm.controls['type'].value
) )
) { ) {

8
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html

@ -13,6 +13,9 @@
<mat-select formControlName="type"> <mat-select formControlName="type">
<mat-option i18n value="BUY">Buy</mat-option> <mat-option i18n value="BUY">Buy</mat-option>
<mat-option i18n value="DIVIDEND">Dividend</mat-option> <mat-option i18n value="DIVIDEND">Dividend</mat-option>
<mat-option i18n value="STAKE"
>Stake reward / Stockdividend</mat-option
>
<mat-option i18n value="ITEM">Item</mat-option> <mat-option i18n value="ITEM">Item</mat-option>
<mat-option i18n value="LIABILITY">Liability</mat-option> <mat-option i18n value="LIABILITY">Liability</mat-option>
<mat-option i18n value="SELL">Sell</mat-option> <mat-option i18n value="SELL">Sell</mat-option>
@ -102,7 +105,10 @@
<input formControlName="quantity" matInput type="number" /> <input formControlName="quantity" matInput type="number" />
</mat-form-field> </mat-form-field>
</div> </div>
<div class="align-items-start d-flex mb-3"> <div
*ngIf="activityForm.controls['type']?.value !== 'STAKE'"
class="align-items-start d-flex mb-3"
>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label <mat-label
><ng-container [ngSwitch]="activityForm.controls['type']?.value"> ><ng-container [ngSwitch]="activityForm.controls['type']?.value">

2
apps/client/src/app/services/import-activities.service.ts

@ -346,6 +346,8 @@ export class ImportActivitiesService {
return Type.LIABILITY; return Type.LIABILITY;
case 'sell': case 'sell':
return Type.SELL; return Type.SELL;
case 'stake':
return Type.STAKE;
default: default:
break; break;
} }

9
libs/ui/src/lib/activities-table/activities-table.component.html

@ -163,11 +163,16 @@
dividend: element.type === 'DIVIDEND', dividend: element.type === 'DIVIDEND',
item: element.type === 'ITEM', item: element.type === 'ITEM',
liability: element.type === 'LIABILITY', liability: element.type === 'LIABILITY',
sell: element.type === 'SELL' sell: element.type === 'SELL',
stake: element.type === 'STAKE'
}" }"
> >
<ion-icon <ion-icon
*ngIf="element.type === 'BUY' || element.type === 'DIVIDEND'" *ngIf="
element.type === 'BUY' ||
element.type === 'DIVIDEND' ||
element.type === 'STAKE'
"
name="arrow-up-circle-outline" name="arrow-up-circle-outline"
></ion-icon> ></ion-icon>
<ion-icon <ion-icon

4
libs/ui/src/lib/activities-table/activities-table.component.scss

@ -33,6 +33,10 @@
color: var(--blue); color: var(--blue);
} }
&.stake {
color: var(--blue);
}
&.item { &.item {
color: var(--purple); color: var(--purple);
} }

1
prisma/schema.prisma

@ -240,6 +240,7 @@ enum Type {
ITEM ITEM
LIABILITY LIABILITY
SELL SELL
STAKE
} }
enum ViewMode { enum ViewMode {

Loading…
Cancel
Save