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;
dataProviderInfo: DataProviderInfo;
dividendInBaseCurrency: number;
stakeRewards: number;
feeInBaseCurrency: number;
firstBuyDate: string;
grossPerformance: number;

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

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

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

@ -702,6 +702,7 @@ export class PortfolioService {
averagePrice: undefined,
dataProviderInfo: undefined,
dividendInBaseCurrency: undefined,
stakeRewards: undefined,
feeInBaseCurrency: undefined,
firstBuyDate: undefined,
grossPerformance: undefined,
@ -730,7 +731,11 @@ export class PortfolioService {
.filter((order) => {
tags = tags.concat(order.tags);
return order.type === 'BUY' || order.type === 'SELL';
return (
order.type === 'BUY' ||
order.type === 'SELL' ||
order.type === 'STAKE'
);
})
.map((order) => ({
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
const investment = this.exchangeRateDataService.toCurrency(
position.investment?.toNumber(),
@ -880,6 +895,7 @@ export class PortfolioService {
averagePrice: averagePrice.toNumber(),
dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0],
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
stakeRewards: stakeRewards.toNumber(),
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
fee.toNumber(),
SymbolProfile.currency,
@ -943,6 +959,7 @@ export class PortfolioService {
averagePrice: 0,
dataProviderInfo: undefined,
dividendInBaseCurrency: 0,
stakeRewards: 0,
feeInBaseCurrency: 0,
firstBuyDate: undefined,
grossPerformance: undefined,
@ -1403,7 +1420,7 @@ export class PortfolioService {
let valueInBaseCurrencyOfEmergencyFundPositions = new Big(0);
for (const order of emergencyFundOrders) {
if (order.type === 'BUY') {
if (order.type === 'BUY' || order.type === 'STAKE') {
valueInBaseCurrencyOfEmergencyFundPositions =
valueInBaseCurrencyOfEmergencyFundPositions.plus(
order.valueInBaseCurrency
@ -1714,7 +1731,7 @@ export class PortfolioService {
userCurrency,
userId,
withExcludedAccounts,
types: ['BUY', 'SELL']
types: ['BUY', 'SELL', 'STAKE']
});
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 dividendInBaseCurrency: number;
public stakeRewards: number;
public feeInBaseCurrency: number;
public firstBuyDate: string;
public grossPerformance: number;
@ -84,6 +85,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
averagePrice,
dataProviderInfo,
dividendInBaseCurrency,
stakeRewards,
feeInBaseCurrency,
firstBuyDate,
grossPerformance,
@ -107,6 +109,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.countries = {};
this.dataProviderInfo = dataProviderInfo;
this.dividendInBaseCurrency = dividendInBaseCurrency;
this.stakeRewards = stakeRewards;
this.feeInBaseCurrency = feeInBaseCurrency;
this.firstBuyDate = firstBuyDate;
this.grossPerformance = grossPerformance;

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

@ -126,7 +126,10 @@
>Investment</gf-value
>
</div>
<div class="col-6 mb-3">
<div
*ngIf="dividendInBaseCurrency > 0 || !stakeRewards"
class="col-6 mb-3"
>
<gf-value
i18n
size="medium"
@ -137,6 +140,17 @@
>Dividend</gf-value
>
</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">
<gf-value
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['unitPrice'].value +
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 {
this.total =
this.activityForm.controls['quantity'].value *
@ -242,7 +246,7 @@ export class CreateOrUpdateActivityDialog implements OnDestroy {
if (this.activityForm.controls['searchSymbol'].invalid) {
this.data.activity.SymbolProfile = null;
} else if (
['BUY', 'DIVIDEND', 'SELL'].includes(
['BUY', 'DIVIDEND', 'SELL', 'STAKE'].includes(
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-option i18n value="BUY">Buy</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="LIABILITY">Liability</mat-option>
<mat-option i18n value="SELL">Sell</mat-option>
@ -102,7 +105,10 @@
<input formControlName="quantity" matInput type="number" />
</mat-form-field>
</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-label
><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;
case 'sell':
return Type.SELL;
case 'stake':
return Type.STAKE;
default:
break;
}

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

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

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

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

1
prisma/schema.prisma

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

Loading…
Cancel
Save