parent
72974e888f
commit
f01a3f893d
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support to exclude an account from analysis
|
||||||
|
|
||||||
## 1.197.0 - 24.09.2022
|
## 1.197.0 - 24.09.2022
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -96,7 +96,9 @@ export class AccountController {
|
|||||||
|
|
||||||
let accountsWithAggregations =
|
let accountsWithAggregations =
|
||||||
await this.portfolioService.getAccountsWithAggregations(
|
await this.portfolioService.getAccountsWithAggregations(
|
||||||
impersonationUserId || this.request.user.id
|
impersonationUserId || this.request.user.id,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -139,7 +141,8 @@ export class AccountController {
|
|||||||
let accountsWithAggregations =
|
let accountsWithAggregations =
|
||||||
await this.portfolioService.getAccountsWithAggregations(
|
await this.portfolioService.getAccountsWithAggregations(
|
||||||
impersonationUserId || this.request.user.id,
|
impersonationUserId || this.request.user.id,
|
||||||
[{ id, type: 'ACCOUNT' }]
|
[{ id, type: 'ACCOUNT' }],
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -107,15 +107,23 @@ export class AccountService {
|
|||||||
public async getCashDetails({
|
public async getCashDetails({
|
||||||
currency,
|
currency,
|
||||||
filters = [],
|
filters = [],
|
||||||
userId
|
userId,
|
||||||
|
withExcludedAccounts = false
|
||||||
}: {
|
}: {
|
||||||
currency: string;
|
currency: string;
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
userId: string;
|
userId: string;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
}): Promise<CashDetails> {
|
}): Promise<CashDetails> {
|
||||||
let totalCashBalanceInBaseCurrency = new Big(0);
|
let totalCashBalanceInBaseCurrency = new Big(0);
|
||||||
|
|
||||||
const where: Prisma.AccountWhereInput = { userId };
|
const where: Prisma.AccountWhereInput = {
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (withExcludedAccounts === false) {
|
||||||
|
where.isExcluded = false;
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ACCOUNT: filtersByAccount,
|
ACCOUNT: filtersByAccount,
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { AccountType } from '@prisma/client';
|
import { AccountType } from '@prisma/client';
|
||||||
import { IsNumber, IsString, ValidateIf } from 'class-validator';
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateIf
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
export class CreateAccountDto {
|
export class CreateAccountDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -11,6 +17,10 @@ export class CreateAccountDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
isExcluded?: boolean;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { AccountType } from '@prisma/client';
|
import { AccountType } from '@prisma/client';
|
||||||
import { IsNumber, IsString, ValidateIf } from 'class-validator';
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
ValidateIf
|
||||||
|
} from 'class-validator';
|
||||||
|
|
||||||
export class UpdateAccountDto {
|
export class UpdateAccountDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -14,6 +20,10 @@ export class UpdateAccountDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
isExcluded?: boolean;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ -109,7 +109,8 @@ export class OrderController {
|
|||||||
filters,
|
filters,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
includeDrafts: true,
|
includeDrafts: true,
|
||||||
userId: impersonationUserId || this.request.user.id
|
userId: impersonationUserId || this.request.user.id,
|
||||||
|
withExcludedAccounts: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -189,13 +189,15 @@ export class OrderService {
|
|||||||
includeDrafts = false,
|
includeDrafts = false,
|
||||||
types,
|
types,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId
|
userId,
|
||||||
|
withExcludedAccounts = false
|
||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
includeDrafts?: boolean;
|
includeDrafts?: boolean;
|
||||||
types?: TypeOfOrder[];
|
types?: TypeOfOrder[];
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
}): Promise<Activity[]> {
|
}): Promise<Activity[]> {
|
||||||
const where: Prisma.OrderWhereInput = { userId };
|
const where: Prisma.OrderWhereInput = { userId };
|
||||||
|
|
||||||
@ -284,24 +286,28 @@ export class OrderService {
|
|||||||
},
|
},
|
||||||
orderBy: { date: 'asc' }
|
orderBy: { date: 'asc' }
|
||||||
})
|
})
|
||||||
).map((order) => {
|
)
|
||||||
const value = new Big(order.quantity).mul(order.unitPrice).toNumber();
|
.filter((order) => {
|
||||||
|
return withExcludedAccounts || order.Account?.isExcluded === false;
|
||||||
|
})
|
||||||
|
.map((order) => {
|
||||||
|
const value = new Big(order.quantity).mul(order.unitPrice).toNumber();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...order,
|
...order,
|
||||||
value,
|
|
||||||
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
|
||||||
order.fee,
|
|
||||||
order.SymbolProfile.currency,
|
|
||||||
userCurrency
|
|
||||||
),
|
|
||||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
|
||||||
value,
|
value,
|
||||||
order.SymbolProfile.currency,
|
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||||
userCurrency
|
order.fee,
|
||||||
)
|
order.SymbolProfile.currency,
|
||||||
};
|
userCurrency
|
||||||
});
|
),
|
||||||
|
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||||
|
value,
|
||||||
|
order.SymbolProfile.currency,
|
||||||
|
userCurrency
|
||||||
|
)
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateOrder({
|
public async updateOrder({
|
||||||
|
@ -148,12 +148,15 @@ export class PortfolioController {
|
|||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let portfolioSummary: PortfolioSummary;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
accounts,
|
accounts,
|
||||||
filteredValueInBaseCurrency,
|
filteredValueInBaseCurrency,
|
||||||
filteredValueInPercentage,
|
filteredValueInPercentage,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
holdings,
|
holdings,
|
||||||
|
summary,
|
||||||
totalValueInBaseCurrency
|
totalValueInBaseCurrency
|
||||||
} = await this.portfolioService.getDetails(
|
} = await this.portfolioService.getDetails(
|
||||||
impersonationId,
|
impersonationId,
|
||||||
@ -166,6 +169,8 @@ export class PortfolioController {
|
|||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
portfolioSummary = summary;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
impersonationId ||
|
impersonationId ||
|
||||||
this.userService.isRestrictedView(this.request.user)
|
this.userService.isRestrictedView(this.request.user)
|
||||||
@ -199,6 +204,22 @@ export class PortfolioController {
|
|||||||
accounts[name].current = current / totalValue;
|
accounts[name].current = current / totalValue;
|
||||||
accounts[name].original = original / totalInvestment;
|
accounts[name].original = original / totalInvestment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
portfolioSummary = nullifyValuesInObject(summary, [
|
||||||
|
'cash',
|
||||||
|
'committedFunds',
|
||||||
|
'currentGrossPerformance',
|
||||||
|
'currentNetPerformance',
|
||||||
|
'currentValue',
|
||||||
|
'dividend',
|
||||||
|
'emergencyFund',
|
||||||
|
'excludedAccountsAndActivities',
|
||||||
|
'fees',
|
||||||
|
'items',
|
||||||
|
'netWorth',
|
||||||
|
'totalBuy',
|
||||||
|
'totalSell'
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasDetails = true;
|
let hasDetails = true;
|
||||||
@ -224,7 +245,8 @@ export class PortfolioController {
|
|||||||
filteredValueInPercentage,
|
filteredValueInPercentage,
|
||||||
hasError,
|
hasError,
|
||||||
holdings,
|
holdings,
|
||||||
totalValueInBaseCurrency
|
totalValueInBaseCurrency,
|
||||||
|
summary: hasDetails ? portfolioSummary : undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,46 +442,6 @@ export class PortfolioController {
|
|||||||
return portfolioPublicDetails;
|
return portfolioPublicDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('summary')
|
|
||||||
@UseGuards(AuthGuard('jwt'))
|
|
||||||
public async getSummary(
|
|
||||||
@Headers('impersonation-id') impersonationId
|
|
||||||
): Promise<PortfolioSummary> {
|
|
||||||
if (
|
|
||||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
|
||||||
this.request.user.subscription.type === 'Basic'
|
|
||||||
) {
|
|
||||||
throw new HttpException(
|
|
||||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
|
||||||
StatusCodes.FORBIDDEN
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let summary = await this.portfolioService.getSummary(impersonationId);
|
|
||||||
|
|
||||||
if (
|
|
||||||
impersonationId ||
|
|
||||||
this.userService.isRestrictedView(this.request.user)
|
|
||||||
) {
|
|
||||||
summary = nullifyValuesInObject(summary, [
|
|
||||||
'cash',
|
|
||||||
'committedFunds',
|
|
||||||
'currentGrossPerformance',
|
|
||||||
'currentNetPerformance',
|
|
||||||
'currentValue',
|
|
||||||
'dividend',
|
|
||||||
'emergencyFund',
|
|
||||||
'fees',
|
|
||||||
'items',
|
|
||||||
'netWorth',
|
|
||||||
'totalBuy',
|
|
||||||
'totalSell'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('position/:dataSource/:symbol')
|
@Get('position/:dataSource/:symbol')
|
||||||
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
|
@ -50,8 +50,11 @@ import type {
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import {
|
import {
|
||||||
|
Account,
|
||||||
AssetClass,
|
AssetClass,
|
||||||
DataSource,
|
DataSource,
|
||||||
|
Order,
|
||||||
|
Platform,
|
||||||
Prisma,
|
Prisma,
|
||||||
Tag,
|
Tag,
|
||||||
Type as TypeOfOrder
|
Type as TypeOfOrder
|
||||||
@ -106,7 +109,8 @@ export class PortfolioService {
|
|||||||
|
|
||||||
public async getAccounts(
|
public async getAccounts(
|
||||||
aUserId: string,
|
aUserId: string,
|
||||||
aFilters?: Filter[]
|
aFilters?: Filter[],
|
||||||
|
withExcludedAccounts = false
|
||||||
): Promise<AccountWithValue[]> {
|
): Promise<AccountWithValue[]> {
|
||||||
const where: Prisma.AccountWhereInput = { userId: aUserId };
|
const where: Prisma.AccountWhereInput = { userId: aUserId };
|
||||||
|
|
||||||
@ -120,7 +124,13 @@ export class PortfolioService {
|
|||||||
include: { Order: true, Platform: true },
|
include: { Order: true, Platform: true },
|
||||||
orderBy: { name: 'asc' }
|
orderBy: { name: 'asc' }
|
||||||
}),
|
}),
|
||||||
this.getDetails(aUserId, aUserId, undefined, aFilters)
|
this.getDetails(
|
||||||
|
aUserId,
|
||||||
|
aUserId,
|
||||||
|
undefined,
|
||||||
|
aFilters,
|
||||||
|
withExcludedAccounts
|
||||||
|
)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||||
@ -160,9 +170,14 @@ export class PortfolioService {
|
|||||||
|
|
||||||
public async getAccountsWithAggregations(
|
public async getAccountsWithAggregations(
|
||||||
aUserId: string,
|
aUserId: string,
|
||||||
aFilters?: Filter[]
|
aFilters?: Filter[],
|
||||||
|
withExcludedAccounts = false
|
||||||
): Promise<Accounts> {
|
): Promise<Accounts> {
|
||||||
const accounts = await this.getAccounts(aUserId, aFilters);
|
const accounts = await this.getAccounts(
|
||||||
|
aUserId,
|
||||||
|
aFilters,
|
||||||
|
withExcludedAccounts
|
||||||
|
);
|
||||||
let totalBalanceInBaseCurrency = new Big(0);
|
let totalBalanceInBaseCurrency = new Big(0);
|
||||||
let totalValueInBaseCurrency = new Big(0);
|
let totalValueInBaseCurrency = new Big(0);
|
||||||
let transactionCount = 0;
|
let transactionCount = 0;
|
||||||
@ -410,7 +425,8 @@ export class PortfolioService {
|
|||||||
aImpersonationId: string,
|
aImpersonationId: string,
|
||||||
aUserId: string,
|
aUserId: string,
|
||||||
aDateRange: DateRange = 'max',
|
aDateRange: DateRange = 'max',
|
||||||
aFilters?: Filter[]
|
aFilters?: Filter[],
|
||||||
|
withExcludedAccounts = false
|
||||||
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
||||||
const userId = await this.getUserId(aImpersonationId, aUserId);
|
const userId = await this.getUserId(aImpersonationId, aUserId);
|
||||||
const user = await this.userService.user({ id: userId });
|
const user = await this.userService.user({ id: userId });
|
||||||
@ -426,6 +442,7 @@ export class PortfolioService {
|
|||||||
const { orders, portfolioOrders, transactionPoints } =
|
const { orders, portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
userId,
|
userId,
|
||||||
|
withExcludedAccounts,
|
||||||
filters: aFilters
|
filters: aFilters
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -580,6 +597,7 @@ export class PortfolioService {
|
|||||||
portfolioItemsNow,
|
portfolioItemsNow,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId,
|
userId,
|
||||||
|
withExcludedAccounts,
|
||||||
filters: aFilters
|
filters: aFilters
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -588,6 +606,7 @@ export class PortfolioService {
|
|||||||
return {
|
return {
|
||||||
accounts,
|
accounts,
|
||||||
holdings,
|
holdings,
|
||||||
|
summary,
|
||||||
filteredValueInBaseCurrency: filteredValueInBaseCurrency.toNumber(),
|
filteredValueInBaseCurrency: filteredValueInBaseCurrency.toNumber(),
|
||||||
filteredValueInPercentage: summary.netWorth
|
filteredValueInPercentage: summary.netWorth
|
||||||
? filteredValueInBaseCurrency.div(summary.netWorth).toNumber()
|
? filteredValueInBaseCurrency.div(summary.netWorth).toNumber()
|
||||||
@ -606,7 +625,11 @@ export class PortfolioService {
|
|||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||||
|
|
||||||
const orders = (
|
const orders = (
|
||||||
await this.orderService.getOrders({ userCurrency, userId })
|
await this.orderService.getOrders({
|
||||||
|
userCurrency,
|
||||||
|
userId,
|
||||||
|
withExcludedAccounts: true
|
||||||
|
})
|
||||||
).filter(({ SymbolProfile }) => {
|
).filter(({ SymbolProfile }) => {
|
||||||
return (
|
return (
|
||||||
SymbolProfile.dataSource === aDataSource &&
|
SymbolProfile.dataSource === aDataSource &&
|
||||||
@ -1181,74 +1204,6 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
|
||||||
const user = await this.userService.user({ id: userId });
|
|
||||||
|
|
||||||
const performanceInformation = await this.getPerformance(aImpersonationId);
|
|
||||||
|
|
||||||
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
|
|
||||||
userId,
|
|
||||||
currency: userCurrency
|
|
||||||
});
|
|
||||||
const orders = await this.orderService.getOrders({
|
|
||||||
userCurrency,
|
|
||||||
userId
|
|
||||||
});
|
|
||||||
const dividend = this.getDividend(orders).toNumber();
|
|
||||||
const emergencyFund = new Big(
|
|
||||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
|
||||||
);
|
|
||||||
const fees = this.getFees(orders).toNumber();
|
|
||||||
const firstOrderDate = orders[0]?.date;
|
|
||||||
const items = this.getItems(orders).toNumber();
|
|
||||||
|
|
||||||
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
|
||||||
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
|
||||||
|
|
||||||
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
|
|
||||||
const committedFunds = new Big(totalBuy).minus(totalSell);
|
|
||||||
|
|
||||||
const netWorth = new Big(balanceInBaseCurrency)
|
|
||||||
.plus(performanceInformation.performance.currentValue)
|
|
||||||
.plus(items)
|
|
||||||
.toNumber();
|
|
||||||
|
|
||||||
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
|
||||||
|
|
||||||
const annualizedPerformancePercent = new PortfolioCalculator({
|
|
||||||
currency: userCurrency,
|
|
||||||
currentRateService: this.currentRateService,
|
|
||||||
orders: []
|
|
||||||
})
|
|
||||||
.getAnnualizedPerformancePercent({
|
|
||||||
daysInMarket,
|
|
||||||
netPerformancePercent: new Big(
|
|
||||||
performanceInformation.performance.currentNetPerformancePercent
|
|
||||||
)
|
|
||||||
})
|
|
||||||
?.toNumber();
|
|
||||||
|
|
||||||
return {
|
|
||||||
...performanceInformation.performance,
|
|
||||||
annualizedPerformancePercent,
|
|
||||||
cash,
|
|
||||||
dividend,
|
|
||||||
fees,
|
|
||||||
firstOrderDate,
|
|
||||||
items,
|
|
||||||
netWorth,
|
|
||||||
totalBuy,
|
|
||||||
totalSell,
|
|
||||||
committedFunds: committedFunds.toNumber(),
|
|
||||||
emergencyFund: emergencyFund.toNumber(),
|
|
||||||
ordersCount: orders.filter((order) => {
|
|
||||||
return order.type === 'BUY' || order.type === 'SELL';
|
|
||||||
}).length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getCashPositions({
|
private async getCashPositions({
|
||||||
cashDetails,
|
cashDetails,
|
||||||
emergencyFund,
|
emergencyFund,
|
||||||
@ -1424,14 +1379,117 @@ export class PortfolioService {
|
|||||||
return portfolioStart;
|
return portfolioStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getSummary(
|
||||||
|
aImpersonationId: string
|
||||||
|
): Promise<PortfolioSummary> {
|
||||||
|
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||||
|
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||||
|
const user = await this.userService.user({ id: userId });
|
||||||
|
|
||||||
|
const performanceInformation = await this.getPerformance(aImpersonationId);
|
||||||
|
|
||||||
|
const { balanceInBaseCurrency } = await this.accountService.getCashDetails({
|
||||||
|
userId,
|
||||||
|
currency: userCurrency
|
||||||
|
});
|
||||||
|
const orders = await this.orderService.getOrders({
|
||||||
|
userCurrency,
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
const excludedActivities = (
|
||||||
|
await this.orderService.getOrders({
|
||||||
|
userCurrency,
|
||||||
|
userId,
|
||||||
|
withExcludedAccounts: true
|
||||||
|
})
|
||||||
|
).filter(({ Account: account }) => {
|
||||||
|
return account?.isExcluded ?? false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dividend = this.getDividend(orders).toNumber();
|
||||||
|
const emergencyFund = new Big(
|
||||||
|
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||||
|
);
|
||||||
|
const fees = this.getFees(orders).toNumber();
|
||||||
|
const firstOrderDate = orders[0]?.date;
|
||||||
|
const items = this.getItems(orders).toNumber();
|
||||||
|
|
||||||
|
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
||||||
|
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
||||||
|
|
||||||
|
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
|
||||||
|
const committedFunds = new Big(totalBuy).minus(totalSell);
|
||||||
|
const totalOfExcludedActivities = new Big(
|
||||||
|
this.getTotalByType(excludedActivities, userCurrency, 'BUY')
|
||||||
|
).minus(this.getTotalByType(excludedActivities, userCurrency, 'SELL'));
|
||||||
|
|
||||||
|
const cashDetailsWithExcludedAccounts =
|
||||||
|
await this.accountService.getCashDetails({
|
||||||
|
userId,
|
||||||
|
currency: userCurrency,
|
||||||
|
withExcludedAccounts: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const excludedBalanceInBaseCurrency = new Big(
|
||||||
|
cashDetailsWithExcludedAccounts.balanceInBaseCurrency
|
||||||
|
).minus(balanceInBaseCurrency);
|
||||||
|
|
||||||
|
const excludedAccountsAndActivities = excludedBalanceInBaseCurrency
|
||||||
|
.plus(totalOfExcludedActivities)
|
||||||
|
.toNumber();
|
||||||
|
|
||||||
|
const netWorth = new Big(balanceInBaseCurrency)
|
||||||
|
.plus(performanceInformation.performance.currentValue)
|
||||||
|
.plus(items)
|
||||||
|
.plus(excludedAccountsAndActivities)
|
||||||
|
.toNumber();
|
||||||
|
|
||||||
|
const daysInMarket = differenceInDays(new Date(), firstOrderDate);
|
||||||
|
|
||||||
|
const annualizedPerformancePercent = new PortfolioCalculator({
|
||||||
|
currency: userCurrency,
|
||||||
|
currentRateService: this.currentRateService,
|
||||||
|
orders: []
|
||||||
|
})
|
||||||
|
.getAnnualizedPerformancePercent({
|
||||||
|
daysInMarket,
|
||||||
|
netPerformancePercent: new Big(
|
||||||
|
performanceInformation.performance.currentNetPerformancePercent
|
||||||
|
)
|
||||||
|
})
|
||||||
|
?.toNumber();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...performanceInformation.performance,
|
||||||
|
annualizedPerformancePercent,
|
||||||
|
cash,
|
||||||
|
dividend,
|
||||||
|
excludedAccountsAndActivities,
|
||||||
|
fees,
|
||||||
|
firstOrderDate,
|
||||||
|
items,
|
||||||
|
netWorth,
|
||||||
|
totalBuy,
|
||||||
|
totalSell,
|
||||||
|
committedFunds: committedFunds.toNumber(),
|
||||||
|
emergencyFund: emergencyFund.toNumber(),
|
||||||
|
ordersCount: orders.filter((order) => {
|
||||||
|
return order.type === 'BUY' || order.type === 'SELL';
|
||||||
|
}).length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async getTransactionPoints({
|
private async getTransactionPoints({
|
||||||
filters,
|
filters,
|
||||||
includeDrafts = false,
|
includeDrafts = false,
|
||||||
userId
|
userId,
|
||||||
|
withExcludedAccounts
|
||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
includeDrafts?: boolean;
|
includeDrafts?: boolean;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
transactionPoints: TransactionPoint[];
|
transactionPoints: TransactionPoint[];
|
||||||
orders: OrderWithAccount[];
|
orders: OrderWithAccount[];
|
||||||
@ -1445,6 +1503,7 @@ export class PortfolioService {
|
|||||||
includeDrafts,
|
includeDrafts,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId,
|
userId,
|
||||||
|
withExcludedAccounts,
|
||||||
types: ['BUY', 'SELL']
|
types: ['BUY', 'SELL']
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1496,17 +1555,22 @@ export class PortfolioService {
|
|||||||
orders,
|
orders,
|
||||||
portfolioItemsNow,
|
portfolioItemsNow,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId
|
userId,
|
||||||
|
withExcludedAccounts
|
||||||
}: {
|
}: {
|
||||||
filters?: Filter[];
|
filters?: Filter[];
|
||||||
orders: OrderWithAccount[];
|
orders: OrderWithAccount[];
|
||||||
portfolioItemsNow: { [p: string]: TimelinePosition };
|
portfolioItemsNow: { [p: string]: TimelinePosition };
|
||||||
userCurrency: string;
|
userCurrency: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
withExcludedAccounts?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const accounts: PortfolioDetails['accounts'] = {};
|
const accounts: PortfolioDetails['accounts'] = {};
|
||||||
|
|
||||||
let currentAccounts = [];
|
let currentAccounts: (Account & {
|
||||||
|
Order?: Order[];
|
||||||
|
Platform?: Platform;
|
||||||
|
})[] = [];
|
||||||
|
|
||||||
if (filters.length === 0) {
|
if (filters.length === 0) {
|
||||||
currentAccounts = await this.accountService.getAccounts(userId);
|
currentAccounts = await this.accountService.getAccounts(userId);
|
||||||
@ -1526,6 +1590,10 @@ export class PortfolioService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentAccounts = currentAccounts.filter((account) => {
|
||||||
|
return withExcludedAccounts || account.isExcluded === false;
|
||||||
|
});
|
||||||
|
|
||||||
for (const account of currentAccounts) {
|
for (const account of currentAccounts) {
|
||||||
const ordersByAccount = orders.filter(({ accountId }) => {
|
const ordersByAccount = orders.filter(({ accountId }) => {
|
||||||
return accountId === account.id;
|
return accountId === account.id;
|
||||||
|
@ -61,7 +61,7 @@ export class AccountDetailDialog implements OnDestroy, OnInit {
|
|||||||
.subscribe(({ accountType, name, Platform, valueInBaseCurrency }) => {
|
.subscribe(({ accountType, name, Platform, valueInBaseCurrency }) => {
|
||||||
this.accountType = accountType;
|
this.accountType = accountType;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.platformName = Platform?.name;
|
this.platformName = Platform?.name ?? '-';
|
||||||
this.valueInBaseCurrency = valueInBaseCurrency;
|
this.valueInBaseCurrency = valueInBaseCurrency;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
|
@ -21,10 +21,12 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 mb-3">
|
<div class="col-6 mb-3">
|
||||||
<gf-value size="medium" [value]="accountType">Account Type</gf-value>
|
<gf-value i18n size="medium" [value]="accountType"
|
||||||
|
>Account Type</gf-value
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 mb-3">
|
<div class="col-6 mb-3">
|
||||||
<gf-value size="medium" [value]="platformName">Platform</gf-value>
|
<gf-value i18n size="medium" [value]="platformName">Platform</gf-value>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import {
|
||||||
|
MatSnackBar,
|
||||||
|
MatSnackBarRef,
|
||||||
|
TextOnlySnackBar
|
||||||
|
} from '@angular/material/snack-bar';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { PortfolioSummary, User } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
InfoItem,
|
||||||
|
PortfolioSummary,
|
||||||
|
User
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
@ -14,8 +24,11 @@ import { takeUntil } from 'rxjs/operators';
|
|||||||
})
|
})
|
||||||
export class HomeSummaryComponent implements OnDestroy, OnInit {
|
export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
|
public hasPermissionForSubscription: boolean;
|
||||||
public hasPermissionToUpdateUserSettings: boolean;
|
public hasPermissionToUpdateUserSettings: boolean;
|
||||||
|
public info: InfoItem;
|
||||||
public isLoading = true;
|
public isLoading = true;
|
||||||
|
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;
|
||||||
public summary: PortfolioSummary;
|
public summary: PortfolioSummary;
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -25,8 +38,17 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
|||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
|
private router: Router,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {
|
||||||
|
this.info = this.dataService.fetchInfo();
|
||||||
|
|
||||||
|
this.hasPermissionForSubscription = hasPermission(
|
||||||
|
this.info?.globalPermissions,
|
||||||
|
permissions.enableSubscription
|
||||||
|
);
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
@ -50,8 +72,6 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
|||||||
.subscribe((aId) => {
|
.subscribe((aId) => {
|
||||||
this.hasImpersonationId = !!aId;
|
this.hasImpersonationId = !!aId;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeEmergencyFund(emergencyFund: number) {
|
public onChangeEmergencyFund(emergencyFund: number) {
|
||||||
@ -81,12 +101,30 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
|||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioSummary()
|
.fetchPortfolioDetails({})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((response) => {
|
.subscribe(({ summary }) => {
|
||||||
this.summary = response;
|
this.summary = summary;
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
|
||||||
|
if (!this.summary) {
|
||||||
|
this.snackBarRef = this.snackBar.open(
|
||||||
|
$localize`This feature requires a subscription.`,
|
||||||
|
this.hasPermissionForSubscription
|
||||||
|
? $localize`Upgrade Plan`
|
||||||
|
: undefined,
|
||||||
|
{ duration: 6000 }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.snackBarRef.afterDismissed().subscribe(() => {
|
||||||
|
this.snackBarRef = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.snackBarRef.onAction().subscribe(() => {
|
||||||
|
this.router.navigate(['/pricing']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,6 +172,17 @@
|
|||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row px-3 py-1">
|
||||||
|
<div class="d-flex flex-grow-1" i18n>Excluded from Analysis</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
[currency]="baseCurrency"
|
||||||
|
[locale]="locale"
|
||||||
|
[value]="isLoading ? undefined : summary?.excludedAccountsAndActivities"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><hr /></div>
|
<div class="col"><hr /></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,8 +59,8 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
|
|||||||
this.openCreateAccountDialog();
|
this.openCreateAccountDialog();
|
||||||
} else if (params['editDialog']) {
|
} else if (params['editDialog']) {
|
||||||
if (this.accounts) {
|
if (this.accounts) {
|
||||||
const account = this.accounts.find((account) => {
|
const account = this.accounts.find(({ id }) => {
|
||||||
return account.id === params['accountId'];
|
return id === params['accountId'];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.openUpdateAccountDialog(account);
|
this.openUpdateAccountDialog(account);
|
||||||
@ -155,6 +155,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
|
|||||||
balance,
|
balance,
|
||||||
currency,
|
currency,
|
||||||
id,
|
id,
|
||||||
|
isExcluded,
|
||||||
name,
|
name,
|
||||||
platformId
|
platformId
|
||||||
}: AccountModel): void {
|
}: AccountModel): void {
|
||||||
@ -165,6 +166,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
|
|||||||
balance,
|
balance,
|
||||||
currency,
|
currency,
|
||||||
id,
|
id,
|
||||||
|
isExcluded,
|
||||||
name,
|
name,
|
||||||
platformId
|
platformId
|
||||||
}
|
}
|
||||||
@ -231,6 +233,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
|
|||||||
accountType: AccountType.SECURITIES,
|
accountType: AccountType.SECURITIES,
|
||||||
balance: 0,
|
balance: 0,
|
||||||
currency: this.user?.settings?.baseCurrency,
|
currency: this.user?.settings?.baseCurrency,
|
||||||
|
isExcluded: false,
|
||||||
name: null,
|
name: null,
|
||||||
platformId: null
|
platformId: null
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,14 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3 px-2">
|
||||||
|
<mat-checkbox
|
||||||
|
color="primary"
|
||||||
|
name="isExcluded"
|
||||||
|
[(ngModel)]="data.account.isExcluded"
|
||||||
|
>Exclude from Analysis</mat-checkbox
|
||||||
|
>
|
||||||
|
</div>
|
||||||
<div *ngIf="data.account.id">
|
<div *ngIf="data.account.id">
|
||||||
<mat-form-field appearance="outline" class="w-100">
|
<mat-form-field appearance="outline" class="w-100">
|
||||||
<mat-label i18n>Account ID</mat-label>
|
<mat-label i18n>Account ID</mat-label>
|
||||||
|
@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
@ -15,6 +16,7 @@ import { CreateOrUpdateAccountDialog } from './create-or-update-account-dialog.c
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
MatCheckboxModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
|
@ -37,14 +37,14 @@ export class FirePageComponent implements OnDestroy, OnInit {
|
|||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioSummary()
|
.fetchPortfolioDetails({})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ cash, currentValue }) => {
|
.subscribe(({ summary }) => {
|
||||||
if (cash === null || currentValue === null) {
|
if (summary.cash === null || summary.currentValue === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fireWealth = new Big(currentValue);
|
this.fireWealth = new Big(summary.currentValue);
|
||||||
this.withdrawalRatePerYear = this.fireWealth.mul(4).div(100);
|
this.withdrawalRatePerYear = this.fireWealth.mul(4).div(100);
|
||||||
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);
|
this.withdrawalRatePerMonth = this.withdrawalRatePerYear.div(12);
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ import {
|
|||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioPublicDetails,
|
PortfolioPublicDetails,
|
||||||
PortfolioReport,
|
PortfolioReport,
|
||||||
PortfolioSummary,
|
|
||||||
UniqueAsset,
|
UniqueAsset,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
@ -302,7 +301,11 @@ export class DataService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioDetails({ filters }: { filters?: Filter[] }) {
|
public fetchPortfolioDetails({
|
||||||
|
filters
|
||||||
|
}: {
|
||||||
|
filters?: Filter[];
|
||||||
|
}): Observable<PortfolioDetails> {
|
||||||
let params = new HttpParams();
|
let params = new HttpParams();
|
||||||
|
|
||||||
if (filters?.length > 0) {
|
if (filters?.length > 0) {
|
||||||
@ -348,9 +351,20 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.http.get<PortfolioDetails>('/api/v1/portfolio/details', {
|
return this.http
|
||||||
params
|
.get<any>('/api/v1/portfolio/details', {
|
||||||
});
|
params
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
map((response) => {
|
||||||
|
if (response.summary?.firstOrderDate) {
|
||||||
|
response.summary.firstOrderDate = parseISO(
|
||||||
|
response.summary.firstOrderDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioPerformance({
|
public fetchPortfolioPerformance({
|
||||||
@ -376,18 +390,6 @@ export class DataService {
|
|||||||
return this.http.get<PortfolioReport>('/api/v1/portfolio/report');
|
return this.http.get<PortfolioReport>('/api/v1/portfolio/report');
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioSummary(): Observable<PortfolioSummary> {
|
|
||||||
return this.http.get<any>('/api/v1/portfolio/summary').pipe(
|
|
||||||
map((summary) => {
|
|
||||||
if (summary.firstOrderDate) {
|
|
||||||
summary.firstOrderDate = parseISO(summary.firstOrderDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return summary;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public fetchPositionDetail({
|
public fetchPositionDetail({
|
||||||
dataSource,
|
dataSource,
|
||||||
symbol
|
symbol
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
PortfolioPosition,
|
||||||
|
PortfolioSummary
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
export interface PortfolioDetails {
|
export interface PortfolioDetails {
|
||||||
accounts: {
|
accounts: {
|
||||||
@ -13,5 +16,6 @@ export interface PortfolioDetails {
|
|||||||
filteredValueInBaseCurrency?: number;
|
filteredValueInBaseCurrency?: number;
|
||||||
filteredValueInPercentage: number;
|
filteredValueInPercentage: number;
|
||||||
holdings: { [symbol: string]: PortfolioPosition };
|
holdings: { [symbol: string]: PortfolioPosition };
|
||||||
|
summary: PortfolioSummary;
|
||||||
totalValueInBaseCurrency?: number;
|
totalValueInBaseCurrency?: number;
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ import { PortfolioPerformance } from './portfolio-performance.interface';
|
|||||||
export interface PortfolioSummary extends PortfolioPerformance {
|
export interface PortfolioSummary extends PortfolioPerformance {
|
||||||
annualizedPerformancePercent: number;
|
annualizedPerformancePercent: number;
|
||||||
cash: number;
|
cash: number;
|
||||||
dividend: number;
|
|
||||||
committedFunds: number;
|
committedFunds: number;
|
||||||
|
dividend: number;
|
||||||
emergencyFund: number;
|
emergencyFund: number;
|
||||||
|
excludedAccountsAndActivities: number;
|
||||||
fees: number;
|
fees: number;
|
||||||
firstOrderDate: Date;
|
firstOrderDate: Date;
|
||||||
items: number;
|
items: number;
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Account" ADD COLUMN "isExcluded" BOOLEAN NOT NULL DEFAULT false;
|
@ -27,6 +27,7 @@ model Account {
|
|||||||
currency String?
|
currency String?
|
||||||
id String @default(uuid())
|
id String @default(uuid())
|
||||||
isDefault Boolean @default(false)
|
isDefault Boolean @default(false)
|
||||||
|
isExcluded Boolean @default(false)
|
||||||
name String?
|
name String?
|
||||||
platformId String?
|
platformId String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
Loading…
x
Reference in New Issue
Block a user