net performance for current positions (#330)
* implement fees for transaction points #324 * add net performance to current positions #324 * add net performance to calculate timeline #324 * make timeline fee accumulated by default #324 * Update changelog Co-authored-by: Valentin Zickner <github@zickner.ch> Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com>
This commit is contained in:
parent
ba234a470e
commit
48ab862bb6
@ -16,6 +16,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Added a story for the trend indicator component
|
- Added a story for the trend indicator component
|
||||||
- Added a story for the value component
|
- Added a story for the value component
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Switched from gross to net performance
|
||||||
|
- Restructured the portfolio summary tab on the home page (fees and net performance)
|
||||||
|
|
||||||
## 1.45.0 - 04.09.2021
|
## 1.45.0 - 04.09.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -6,6 +6,8 @@ export interface CurrentPositions {
|
|||||||
positions: TimelinePosition[];
|
positions: TimelinePosition[];
|
||||||
grossPerformance: Big;
|
grossPerformance: Big;
|
||||||
grossPerformancePercentage: Big;
|
grossPerformancePercentage: Big;
|
||||||
|
netPerformance: Big;
|
||||||
|
netPerformancePercentage: Big;
|
||||||
currentValue: Big;
|
currentValue: Big;
|
||||||
totalInvestment: Big;
|
totalInvestment: Big;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import Big from 'big.js';
|
|||||||
export interface PortfolioOrder {
|
export interface PortfolioOrder {
|
||||||
currency: Currency;
|
currency: Currency;
|
||||||
date: string;
|
date: string;
|
||||||
|
fee: Big;
|
||||||
name: string;
|
name: string;
|
||||||
quantity: Big;
|
quantity: Big;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
|
@ -11,6 +11,8 @@ export interface PortfolioPositionDetail {
|
|||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
maxPrice: number;
|
maxPrice: number;
|
||||||
minPrice: number;
|
minPrice: number;
|
||||||
|
netPerformance: number;
|
||||||
|
netPerformancePercent: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
@ -4,5 +4,6 @@ export interface TimelinePeriod {
|
|||||||
date: string;
|
date: string;
|
||||||
grossPerformance: Big;
|
grossPerformance: Big;
|
||||||
investment: Big;
|
investment: Big;
|
||||||
|
netPerformance: Big;
|
||||||
value: Big;
|
value: Big;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import Big from 'big.js';
|
|||||||
|
|
||||||
export interface TransactionPointSymbol {
|
export interface TransactionPointSymbol {
|
||||||
currency: Currency;
|
currency: Currency;
|
||||||
|
fee: Big;
|
||||||
firstBuyDate: string;
|
firstBuyDate: string;
|
||||||
investment: Big;
|
investment: Big;
|
||||||
quantity: Big;
|
quantity: Big;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -58,6 +58,7 @@ export class PortfolioCalculator {
|
|||||||
.plus(oldAccumulatedSymbol.quantity);
|
.plus(oldAccumulatedSymbol.quantity);
|
||||||
currentTransactionPointItem = {
|
currentTransactionPointItem = {
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
|
fee: order.fee.plus(oldAccumulatedSymbol.fee),
|
||||||
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
|
firstBuyDate: oldAccumulatedSymbol.firstBuyDate,
|
||||||
investment: newQuantity.eq(0)
|
investment: newQuantity.eq(0)
|
||||||
? new Big(0)
|
? new Big(0)
|
||||||
@ -72,6 +73,7 @@ export class PortfolioCalculator {
|
|||||||
} else {
|
} else {
|
||||||
currentTransactionPointItem = {
|
currentTransactionPointItem = {
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
|
fee: order.fee,
|
||||||
firstBuyDate: order.date,
|
firstBuyDate: order.date,
|
||||||
investment: unitPrice.mul(order.quantity).mul(factor),
|
investment: unitPrice.mul(order.quantity).mul(factor),
|
||||||
quantity: order.quantity.mul(factor),
|
quantity: order.quantity.mul(factor),
|
||||||
@ -112,11 +114,13 @@ export class PortfolioCalculator {
|
|||||||
public async getCurrentPositions(start: Date): Promise<CurrentPositions> {
|
public async getCurrentPositions(start: Date): Promise<CurrentPositions> {
|
||||||
if (!this.transactionPoints?.length) {
|
if (!this.transactionPoints?.length) {
|
||||||
return {
|
return {
|
||||||
|
currentValue: new Big(0),
|
||||||
hasErrors: false,
|
hasErrors: false,
|
||||||
positions: [],
|
|
||||||
grossPerformance: new Big(0),
|
grossPerformance: new Big(0),
|
||||||
grossPerformancePercentage: new Big(0),
|
grossPerformancePercentage: new Big(0),
|
||||||
currentValue: new Big(0),
|
netPerformance: new Big(0),
|
||||||
|
netPerformancePercentage: new Big(0),
|
||||||
|
positions: [],
|
||||||
totalInvestment: new Big(0)
|
totalInvestment: new Big(0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -181,7 +185,9 @@ export class PortfolioCalculator {
|
|||||||
const startString = format(start, DATE_FORMAT);
|
const startString = format(start, DATE_FORMAT);
|
||||||
|
|
||||||
const holdingPeriodReturns: { [symbol: string]: Big } = {};
|
const holdingPeriodReturns: { [symbol: string]: Big } = {};
|
||||||
|
const netHoldingPeriodReturns: { [symbol: string]: Big } = {};
|
||||||
const grossPerformance: { [symbol: string]: Big } = {};
|
const grossPerformance: { [symbol: string]: Big } = {};
|
||||||
|
const netPerformance: { [symbol: string]: Big } = {};
|
||||||
const todayString = format(today, DATE_FORMAT);
|
const todayString = format(today, DATE_FORMAT);
|
||||||
|
|
||||||
if (firstIndex > 0) {
|
if (firstIndex > 0) {
|
||||||
@ -190,6 +196,7 @@ export class PortfolioCalculator {
|
|||||||
const invalidSymbols = [];
|
const invalidSymbols = [];
|
||||||
const lastInvestments: { [symbol: string]: Big } = {};
|
const lastInvestments: { [symbol: string]: Big } = {};
|
||||||
const lastQuantities: { [symbol: string]: Big } = {};
|
const lastQuantities: { [symbol: string]: Big } = {};
|
||||||
|
const lastFees: { [symbol: string]: Big } = {};
|
||||||
const initialValues: { [symbol: string]: Big } = {};
|
const initialValues: { [symbol: string]: Big } = {};
|
||||||
|
|
||||||
for (let i = firstIndex; i < this.transactionPoints.length; i++) {
|
for (let i = firstIndex; i < this.transactionPoints.length; i++) {
|
||||||
@ -202,10 +209,6 @@ export class PortfolioCalculator {
|
|||||||
|
|
||||||
const items = this.transactionPoints[i].items;
|
const items = this.transactionPoints[i].items;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
let oldHoldingPeriodReturn = holdingPeriodReturns[item.symbol];
|
|
||||||
if (!oldHoldingPeriodReturn) {
|
|
||||||
oldHoldingPeriodReturn = new Big(1);
|
|
||||||
}
|
|
||||||
if (!marketSymbolMap[nextDate]?.[item.symbol]) {
|
if (!marketSymbolMap[nextDate]?.[item.symbol]) {
|
||||||
invalidSymbols.push(item.symbol);
|
invalidSymbols.push(item.symbol);
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
@ -224,6 +227,13 @@ export class PortfolioCalculator {
|
|||||||
const itemValue = marketSymbolMap[currentDate]?.[item.symbol];
|
const itemValue = marketSymbolMap[currentDate]?.[item.symbol];
|
||||||
let initialValue = itemValue?.mul(lastQuantity);
|
let initialValue = itemValue?.mul(lastQuantity);
|
||||||
let investedValue = itemValue?.mul(item.quantity);
|
let investedValue = itemValue?.mul(item.quantity);
|
||||||
|
const isFirstOrderAndIsStartBeforeCurrentDate =
|
||||||
|
i === firstIndex &&
|
||||||
|
isBefore(parseDate(this.transactionPoints[i].date), start);
|
||||||
|
const lastFee: Big = lastFees[item.symbol] ?? new Big(0);
|
||||||
|
const fee = isFirstOrderAndIsStartBeforeCurrentDate
|
||||||
|
? new Big(0)
|
||||||
|
: item.fee.minus(lastFee);
|
||||||
if (!isAfter(parseDate(currentDate), parseDate(item.firstBuyDate))) {
|
if (!isAfter(parseDate(currentDate), parseDate(item.firstBuyDate))) {
|
||||||
initialValue = item.investment;
|
initialValue = item.investment;
|
||||||
investedValue = item.investment;
|
investedValue = item.investment;
|
||||||
@ -247,18 +257,26 @@ export class PortfolioCalculator {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const holdingPeriodReturn = endValue.div(initialValue.plus(cashFlow));
|
const holdingPeriodReturn = endValue.div(initialValue.plus(cashFlow));
|
||||||
holdingPeriodReturns[item.symbol] =
|
holdingPeriodReturns[item.symbol] = (
|
||||||
oldHoldingPeriodReturn.mul(holdingPeriodReturn);
|
holdingPeriodReturns[item.symbol] ?? new Big(1)
|
||||||
let oldGrossPerformance = grossPerformance[item.symbol];
|
).mul(holdingPeriodReturn);
|
||||||
if (!oldGrossPerformance) {
|
grossPerformance[item.symbol] = (
|
||||||
oldGrossPerformance = new Big(0);
|
grossPerformance[item.symbol] ?? new Big(0)
|
||||||
}
|
).plus(endValue.minus(investedValue));
|
||||||
const currentPerformance = endValue.minus(investedValue);
|
|
||||||
grossPerformance[item.symbol] =
|
const netHoldingPeriodReturn = endValue.div(
|
||||||
oldGrossPerformance.plus(currentPerformance);
|
initialValue.plus(cashFlow).plus(fee)
|
||||||
|
);
|
||||||
|
netHoldingPeriodReturns[item.symbol] = (
|
||||||
|
netHoldingPeriodReturns[item.symbol] ?? new Big(1)
|
||||||
|
).mul(netHoldingPeriodReturn);
|
||||||
|
netPerformance[item.symbol] = (
|
||||||
|
netPerformance[item.symbol] ?? new Big(0)
|
||||||
|
).plus(endValue.minus(investedValue).minus(fee));
|
||||||
}
|
}
|
||||||
lastInvestments[item.symbol] = item.investment;
|
lastInvestments[item.symbol] = item.investment;
|
||||||
lastQuantities[item.symbol] = item.quantity;
|
lastQuantities[item.symbol] = item.quantity;
|
||||||
|
lastFees[item.symbol] = item.fee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,15 +300,17 @@ export class PortfolioCalculator {
|
|||||||
: null,
|
: null,
|
||||||
investment: item.investment,
|
investment: item.investment,
|
||||||
marketPrice: marketValue?.toNumber() ?? null,
|
marketPrice: marketValue?.toNumber() ?? null,
|
||||||
|
netPerformance: isValid ? netPerformance[item.symbol] ?? null : null,
|
||||||
|
netPerformancePercentage:
|
||||||
|
isValid && netHoldingPeriodReturns[item.symbol]
|
||||||
|
? netHoldingPeriodReturns[item.symbol].minus(1)
|
||||||
|
: null,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
transactionCount: item.transactionCount
|
transactionCount: item.transactionCount
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const overall = this.calculateOverallGrossPerformance(
|
const overall = this.calculateOverallPerformance(positions, initialValues);
|
||||||
positions,
|
|
||||||
initialValues
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...overall,
|
...overall,
|
||||||
@ -378,7 +398,7 @@ export class PortfolioCalculator {
|
|||||||
return flatten(timelinePeriods);
|
return flatten(timelinePeriods);
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateOverallGrossPerformance(
|
private calculateOverallPerformance(
|
||||||
positions: TimelinePosition[],
|
positions: TimelinePosition[],
|
||||||
initialValues: { [p: string]: Big }
|
initialValues: { [p: string]: Big }
|
||||||
) {
|
) {
|
||||||
@ -387,6 +407,8 @@ export class PortfolioCalculator {
|
|||||||
let totalInvestment = new Big(0);
|
let totalInvestment = new Big(0);
|
||||||
let grossPerformance = new Big(0);
|
let grossPerformance = new Big(0);
|
||||||
let grossPerformancePercentage = new Big(0);
|
let grossPerformancePercentage = new Big(0);
|
||||||
|
let netPerformance = new Big(0);
|
||||||
|
let netPerformancePercentage = new Big(0);
|
||||||
let completeInitialValue = new Big(0);
|
let completeInitialValue = new Big(0);
|
||||||
for (const currentPosition of positions) {
|
for (const currentPosition of positions) {
|
||||||
if (currentPosition.marketPrice) {
|
if (currentPosition.marketPrice) {
|
||||||
@ -401,6 +423,7 @@ export class PortfolioCalculator {
|
|||||||
grossPerformance = grossPerformance.plus(
|
grossPerformance = grossPerformance.plus(
|
||||||
currentPosition.grossPerformance
|
currentPosition.grossPerformance
|
||||||
);
|
);
|
||||||
|
netPerformance = netPerformance.plus(currentPosition.netPerformance);
|
||||||
} else if (!currentPosition.quantity.eq(0)) {
|
} else if (!currentPosition.quantity.eq(0)) {
|
||||||
hasErrors = true;
|
hasErrors = true;
|
||||||
}
|
}
|
||||||
@ -414,6 +437,9 @@ export class PortfolioCalculator {
|
|||||||
grossPerformancePercentage = grossPerformancePercentage.plus(
|
grossPerformancePercentage = grossPerformancePercentage.plus(
|
||||||
currentPosition.grossPerformancePercentage.mul(currentInitialValue)
|
currentPosition.grossPerformancePercentage.mul(currentInitialValue)
|
||||||
);
|
);
|
||||||
|
netPerformancePercentage = netPerformancePercentage.plus(
|
||||||
|
currentPosition.netPerformancePercentage.mul(currentInitialValue)
|
||||||
|
);
|
||||||
} else if (!currentPosition.quantity.eq(0)) {
|
} else if (!currentPosition.quantity.eq(0)) {
|
||||||
console.error(
|
console.error(
|
||||||
`Initial value is missing for symbol ${currentPosition.symbol}`
|
`Initial value is missing for symbol ${currentPosition.symbol}`
|
||||||
@ -425,6 +451,8 @@ export class PortfolioCalculator {
|
|||||||
if (!completeInitialValue.eq(0)) {
|
if (!completeInitialValue.eq(0)) {
|
||||||
grossPerformancePercentage =
|
grossPerformancePercentage =
|
||||||
grossPerformancePercentage.div(completeInitialValue);
|
grossPerformancePercentage.div(completeInitialValue);
|
||||||
|
netPerformancePercentage =
|
||||||
|
netPerformancePercentage.div(completeInitialValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -432,6 +460,8 @@ export class PortfolioCalculator {
|
|||||||
grossPerformance,
|
grossPerformance,
|
||||||
grossPerformancePercentage,
|
grossPerformancePercentage,
|
||||||
hasErrors,
|
hasErrors,
|
||||||
|
netPerformance,
|
||||||
|
netPerformancePercentage,
|
||||||
totalInvestment
|
totalInvestment
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -442,6 +472,7 @@ export class PortfolioCalculator {
|
|||||||
endDate: Date
|
endDate: Date
|
||||||
): Promise<TimelinePeriod[]> {
|
): Promise<TimelinePeriod[]> {
|
||||||
let investment: Big = new Big(0);
|
let investment: Big = new Big(0);
|
||||||
|
let fees: Big = new Big(0);
|
||||||
|
|
||||||
const marketSymbolMap: {
|
const marketSymbolMap: {
|
||||||
[date: string]: { [symbol: string]: Big };
|
[date: string]: { [symbol: string]: Big };
|
||||||
@ -454,6 +485,7 @@ export class PortfolioCalculator {
|
|||||||
currencies[item.symbol] = item.currency;
|
currencies[item.symbol] = item.currency;
|
||||||
symbols.push(item.symbol);
|
symbols.push(item.symbol);
|
||||||
investment = investment.add(item.investment);
|
investment = investment.add(item.investment);
|
||||||
|
fees = fees.add(item.fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
let marketSymbols: GetValueObject[] = [];
|
let marketSymbols: GetValueObject[] = [];
|
||||||
@ -490,7 +522,7 @@ export class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = [];
|
const results: TimelinePeriod[] = [];
|
||||||
for (
|
for (
|
||||||
let currentDate = startDate;
|
let currentDate = startDate;
|
||||||
isBefore(currentDate, endDate);
|
isBefore(currentDate, endDate);
|
||||||
@ -513,11 +545,13 @@ export class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!invalid) {
|
if (!invalid) {
|
||||||
|
const grossPerformance = value.minus(investment);
|
||||||
const result = {
|
const result = {
|
||||||
date: currentDateAsString,
|
grossPerformance,
|
||||||
grossPerformance: value.minus(investment),
|
|
||||||
investment,
|
investment,
|
||||||
value
|
value,
|
||||||
|
date: currentDateAsString,
|
||||||
|
netPerformance: grossPerformance.minus(fees)
|
||||||
};
|
};
|
||||||
results.push(result);
|
results.push(result);
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ export class PortfolioService {
|
|||||||
.map((timelineItem) => ({
|
.map((timelineItem) => ({
|
||||||
date: timelineItem.date,
|
date: timelineItem.date,
|
||||||
marketPrice: timelineItem.value,
|
marketPrice: timelineItem.value,
|
||||||
value: timelineItem.grossPerformance.toNumber()
|
value: timelineItem.netPerformance.toNumber()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,6 +233,8 @@ export class PortfolioService {
|
|||||||
marketPrice: item.marketPrice,
|
marketPrice: item.marketPrice,
|
||||||
marketState: dataProviderResponse.marketState,
|
marketState: dataProviderResponse.marketState,
|
||||||
name: symbolProfile.name,
|
name: symbolProfile.name,
|
||||||
|
netPerformance: item.netPerformance?.toNumber() ?? 0,
|
||||||
|
netPerformancePercent: item.netPerformancePercentage?.toNumber() ?? 0,
|
||||||
quantity: item.quantity.toNumber(),
|
quantity: item.quantity.toNumber(),
|
||||||
sectors: symbolProfile.sectors,
|
sectors: symbolProfile.sectors,
|
||||||
symbol: item.symbol,
|
symbol: item.symbol,
|
||||||
@ -280,6 +282,8 @@ export class PortfolioService {
|
|||||||
marketPrice: undefined,
|
marketPrice: undefined,
|
||||||
maxPrice: undefined,
|
maxPrice: undefined,
|
||||||
minPrice: undefined,
|
minPrice: undefined,
|
||||||
|
netPerformance: undefined,
|
||||||
|
netPerformancePercent: undefined,
|
||||||
quantity: undefined,
|
quantity: undefined,
|
||||||
symbol: aSymbol,
|
symbol: aSymbol,
|
||||||
transactionCount: undefined
|
transactionCount: undefined
|
||||||
@ -291,6 +295,7 @@ export class PortfolioService {
|
|||||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
date: format(order.date, DATE_FORMAT),
|
date: format(order.date, DATE_FORMAT),
|
||||||
|
fee: new Big(order.fee),
|
||||||
name: order.SymbolProfile?.name,
|
name: order.SymbolProfile?.name,
|
||||||
quantity: new Big(order.quantity),
|
quantity: new Big(order.quantity),
|
||||||
symbol: order.symbol,
|
symbol: order.symbol,
|
||||||
@ -324,7 +329,7 @@ export class PortfolioService {
|
|||||||
transactionCount
|
transactionCount
|
||||||
} = position;
|
} = position;
|
||||||
|
|
||||||
// Convert investment and gross performance to currency of user
|
// Convert investment, gross and net performance to currency of user
|
||||||
const userCurrency = this.request.user.Settings.currency;
|
const userCurrency = this.request.user.Settings.currency;
|
||||||
const investment = this.exchangeRateDataService.toCurrency(
|
const investment = this.exchangeRateDataService.toCurrency(
|
||||||
position.investment.toNumber(),
|
position.investment.toNumber(),
|
||||||
@ -336,6 +341,11 @@ export class PortfolioService {
|
|||||||
currency,
|
currency,
|
||||||
userCurrency
|
userCurrency
|
||||||
);
|
);
|
||||||
|
const netPerformance = this.exchangeRateDataService.toCurrency(
|
||||||
|
position.netPerformance.toNumber(),
|
||||||
|
currency,
|
||||||
|
userCurrency
|
||||||
|
);
|
||||||
|
|
||||||
const historicalData = await this.dataProviderService.getHistorical(
|
const historicalData = await this.dataProviderService.getHistorical(
|
||||||
[aSymbol],
|
[aSymbol],
|
||||||
@ -397,10 +407,12 @@ export class PortfolioService {
|
|||||||
marketPrice,
|
marketPrice,
|
||||||
maxPrice,
|
maxPrice,
|
||||||
minPrice,
|
minPrice,
|
||||||
|
netPerformance,
|
||||||
transactionCount,
|
transactionCount,
|
||||||
averagePrice: averagePrice.toNumber(),
|
averagePrice: averagePrice.toNumber(),
|
||||||
grossPerformancePercent: position.grossPerformancePercentage.toNumber(),
|
grossPerformancePercent: position.grossPerformancePercentage.toNumber(),
|
||||||
historicalData: historicalDataArray,
|
historicalData: historicalDataArray,
|
||||||
|
netPerformancePercent: position.netPerformancePercentage.toNumber(),
|
||||||
quantity: quantity.toNumber(),
|
quantity: quantity.toNumber(),
|
||||||
symbol: aSymbol
|
symbol: aSymbol
|
||||||
};
|
};
|
||||||
@ -450,6 +462,8 @@ export class PortfolioService {
|
|||||||
grossPerformancePercent: undefined,
|
grossPerformancePercent: undefined,
|
||||||
historicalData: historicalDataArray,
|
historicalData: historicalDataArray,
|
||||||
investment: 0,
|
investment: 0,
|
||||||
|
netPerformance: undefined,
|
||||||
|
netPerformancePercent: undefined,
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
symbol: aSymbol,
|
symbol: aSymbol,
|
||||||
transactionCount: undefined
|
transactionCount: undefined
|
||||||
@ -513,6 +527,9 @@ export class PortfolioService {
|
|||||||
investment: new Big(position.investment).toNumber(),
|
investment: new Big(position.investment).toNumber(),
|
||||||
marketState: dataProviderResponses[position.symbol].marketState,
|
marketState: dataProviderResponses[position.symbol].marketState,
|
||||||
name: symbolProfileMap[position.symbol].name,
|
name: symbolProfileMap[position.symbol].name,
|
||||||
|
netPerformance: position.netPerformance?.toNumber() ?? null,
|
||||||
|
netPerformancePercentage:
|
||||||
|
position.netPerformancePercentage?.toNumber() ?? null,
|
||||||
quantity: new Big(position.quantity).toNumber()
|
quantity: new Big(position.quantity).toNumber()
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -538,6 +555,8 @@ export class PortfolioService {
|
|||||||
performance: {
|
performance: {
|
||||||
currentGrossPerformance: 0,
|
currentGrossPerformance: 0,
|
||||||
currentGrossPerformancePercent: 0,
|
currentGrossPerformancePercent: 0,
|
||||||
|
currentNetPerformance: 0,
|
||||||
|
currentNetPerformancePercent: 0,
|
||||||
currentValue: 0
|
currentValue: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -557,11 +576,17 @@ export class PortfolioService {
|
|||||||
currentPositions.grossPerformance.toNumber();
|
currentPositions.grossPerformance.toNumber();
|
||||||
const currentGrossPerformancePercent =
|
const currentGrossPerformancePercent =
|
||||||
currentPositions.grossPerformancePercentage.toNumber();
|
currentPositions.grossPerformancePercentage.toNumber();
|
||||||
|
const currentNetPerformance = currentPositions.netPerformance.toNumber();
|
||||||
|
const currentNetPerformancePercent =
|
||||||
|
currentPositions.netPerformancePercentage.toNumber();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasErrors: currentPositions.hasErrors || hasErrors,
|
hasErrors: currentPositions.hasErrors || hasErrors,
|
||||||
performance: {
|
performance: {
|
||||||
currentGrossPerformance,
|
currentGrossPerformance,
|
||||||
currentGrossPerformancePercent,
|
currentGrossPerformancePercent,
|
||||||
|
currentNetPerformance,
|
||||||
|
currentNetPerformancePercent,
|
||||||
currentValue: currentValue
|
currentValue: currentValue
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -732,6 +757,8 @@ export class PortfolioService {
|
|||||||
marketPrice: 0,
|
marketPrice: 0,
|
||||||
marketState: MarketState.open,
|
marketState: MarketState.open,
|
||||||
name: 'Cash',
|
name: 'Cash',
|
||||||
|
netPerformance: 0,
|
||||||
|
netPerformancePercent: 0,
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
sectors: [],
|
sectors: [],
|
||||||
symbol: ghostfolioCashSymbol,
|
symbol: ghostfolioCashSymbol,
|
||||||
@ -778,6 +805,13 @@ export class PortfolioService {
|
|||||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
date: format(order.date, DATE_FORMAT),
|
date: format(order.date, DATE_FORMAT),
|
||||||
|
fee: new Big(
|
||||||
|
this.exchangeRateDataService.toCurrency(
|
||||||
|
order.fee,
|
||||||
|
order.currency,
|
||||||
|
userCurrency
|
||||||
|
)
|
||||||
|
),
|
||||||
name: order.SymbolProfile?.name,
|
name: order.SymbolProfile?.name,
|
||||||
quantity: new Big(order.quantity),
|
quantity: new Big(order.quantity),
|
||||||
symbol: order.symbol,
|
symbol: order.symbol,
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isCurrency]="true"
|
[isCurrency]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="isLoading ? undefined : performance?.currentGrossPerformance"
|
[value]="isLoading ? undefined : performance?.currentNetPerformance"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@ -46,7 +46,7 @@
|
|||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="
|
[value]="
|
||||||
isLoading ? undefined : performance?.currentGrossPerformancePercent
|
isLoading ? undefined : performance?.currentNetPerformancePercent
|
||||||
"
|
"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,7 +52,7 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
|||||||
|
|
||||||
new CountUp(
|
new CountUp(
|
||||||
'value',
|
'value',
|
||||||
this.performance?.currentGrossPerformancePercent * 100,
|
this.performance?.currentNetPerformancePercent * 100,
|
||||||
{
|
{
|
||||||
decimalPlaces: 2,
|
decimalPlaces: 2,
|
||||||
duration: 0.75,
|
duration: 0.75,
|
||||||
|
@ -9,23 +9,6 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><hr /></div>
|
<div class="col"><hr /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row px-3">
|
|
||||||
<div class="d-flex flex-grow-1" i18n>
|
|
||||||
Fees for {{ summary?.ordersCount }} {summary?.ordersCount, plural, =1
|
|
||||||
{order} other {orders}}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-end">
|
|
||||||
<gf-value
|
|
||||||
class="justify-content-end"
|
|
||||||
[currency]="baseCurrency"
|
|
||||||
[locale]="locale"
|
|
||||||
[value]="isLoading ? undefined : summary?.fees"
|
|
||||||
></gf-value>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col"><hr /></div>
|
|
||||||
</div>
|
|
||||||
<div class="row px-3 py-1">
|
<div class="row px-3 py-1">
|
||||||
<div class="d-flex flex-grow-1" i18n>Buy</div>
|
<div class="d-flex flex-grow-1" i18n>Buy</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
@ -66,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row px-3 py-1">
|
<div class="row px-3 py-1">
|
||||||
<div class="d-flex flex-grow-1" i18n>Absolute Performance</div>
|
<div class="d-flex flex-grow-1" i18n>Absolute Gross Performance</div>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<gf-value
|
<gf-value
|
||||||
class="justify-content-end"
|
class="justify-content-end"
|
||||||
@ -77,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row px-3 py-1">
|
<div class="row px-3 py-1">
|
||||||
<div class="d-flex flex-grow-1 ml-3" i18n>Performance (TWR)</div>
|
<div class="d-flex flex-grow-1 ml-3" i18n>Gross Performance (TWR)</div>
|
||||||
<div class="d-flex flex-column flex-wrap justify-content-end">
|
<div class="d-flex flex-column flex-wrap justify-content-end">
|
||||||
<gf-value
|
<gf-value
|
||||||
class="justify-content-end"
|
class="justify-content-end"
|
||||||
@ -91,6 +74,48 @@
|
|||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row px-3 py-1">
|
||||||
|
<div class="d-flex flex-grow-1" i18n>
|
||||||
|
Fees for {{ summary?.ordersCount }} {summary?.ordersCount, plural, =1
|
||||||
|
{order} other {orders}}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<span *ngIf="summary?.fees || summary?.fees === 0" class="mr-1">-</span>
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
[currency]="baseCurrency"
|
||||||
|
[locale]="locale"
|
||||||
|
[value]="isLoading ? undefined : summary?.fees"
|
||||||
|
></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>Absolute Net Performance</div>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
[currency]="baseCurrency"
|
||||||
|
[locale]="locale"
|
||||||
|
[value]="isLoading ? undefined : summary?.currentNetPerformance"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row px-3 py-1">
|
||||||
|
<div class="d-flex flex-grow-1 ml-3" i18n>Net Performance (TWR)</div>
|
||||||
|
<div class="d-flex flex-column flex-wrap justify-content-end">
|
||||||
|
<gf-value
|
||||||
|
class="justify-content-end"
|
||||||
|
position="end"
|
||||||
|
[colorizeSign]="true"
|
||||||
|
[isPercent]="true"
|
||||||
|
[locale]="locale"
|
||||||
|
[value]="isLoading ? undefined : summary?.currentNetPerformancePercent"
|
||||||
|
></gf-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><hr /></div>
|
<div class="col"><hr /></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,6 +34,8 @@ export class PositionDetailDialog implements OnDestroy {
|
|||||||
public marketPrice: number;
|
public marketPrice: number;
|
||||||
public maxPrice: number;
|
public maxPrice: number;
|
||||||
public minPrice: number;
|
public minPrice: number;
|
||||||
|
public netPerformance: number;
|
||||||
|
public netPerformancePercent: number;
|
||||||
public quantity: number;
|
public quantity: number;
|
||||||
public transactionCount: number;
|
public transactionCount: number;
|
||||||
|
|
||||||
@ -60,6 +62,8 @@ export class PositionDetailDialog implements OnDestroy {
|
|||||||
marketPrice,
|
marketPrice,
|
||||||
maxPrice,
|
maxPrice,
|
||||||
minPrice,
|
minPrice,
|
||||||
|
netPerformance,
|
||||||
|
netPerformancePercent,
|
||||||
quantity,
|
quantity,
|
||||||
transactionCount
|
transactionCount
|
||||||
}) => {
|
}) => {
|
||||||
@ -86,6 +90,8 @@ export class PositionDetailDialog implements OnDestroy {
|
|||||||
this.marketPrice = marketPrice;
|
this.marketPrice = marketPrice;
|
||||||
this.maxPrice = maxPrice;
|
this.maxPrice = maxPrice;
|
||||||
this.minPrice = minPrice;
|
this.minPrice = minPrice;
|
||||||
|
this.netPerformance = netPerformance;
|
||||||
|
this.netPerformancePercent = netPerformancePercent;
|
||||||
this.quantity = quantity;
|
this.quantity = quantity;
|
||||||
this.transactionCount = transactionCount;
|
this.transactionCount = transactionCount;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[currency]="data.baseCurrency"
|
[currency]="data.baseCurrency"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[value]="grossPerformance"
|
[value]="netPerformance"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 mb-3">
|
<div class="col-6 mb-3">
|
||||||
@ -35,7 +35,7 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="data.locale"
|
[locale]="data.locale"
|
||||||
[value]="grossPerformancePercent"
|
[value]="netPerformancePercent"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 mb-3">
|
<div class="col-6 mb-3">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
[isLoading]="isLoading"
|
[isLoading]="isLoading"
|
||||||
[marketState]="position?.marketState"
|
[marketState]="position?.marketState"
|
||||||
[range]="range"
|
[range]="range"
|
||||||
[value]="position?.grossPerformancePercentage"
|
[value]="position?.netPerformancePercentage"
|
||||||
></gf-trend-indicator>
|
></gf-trend-indicator>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="isLoading" class="flex-grow-1">
|
<div *ngIf="isLoading" class="flex-grow-1">
|
||||||
@ -47,13 +47,13 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[currency]="baseCurrency"
|
[currency]="baseCurrency"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="position?.grossPerformance"
|
[value]="position?.netPerformance"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
<gf-value
|
<gf-value
|
||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="position?.grossPerformancePercentage"
|
[value]="position?.netPerformancePercentage"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
[colorizeSign]="true"
|
[colorizeSign]="true"
|
||||||
[isPercent]="true"
|
[isPercent]="true"
|
||||||
[locale]="locale"
|
[locale]="locale"
|
||||||
[value]="isLoading ? undefined : element.grossPerformancePercent"
|
[value]="isLoading ? undefined : element.netPerformancePercent"
|
||||||
></gf-value>
|
></gf-value>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
export interface PortfolioPerformance {
|
export interface PortfolioPerformance {
|
||||||
currentGrossPerformance: number;
|
currentGrossPerformance: number;
|
||||||
currentGrossPerformancePercent: number;
|
currentGrossPerformancePercent: number;
|
||||||
|
currentNetPerformance: number;
|
||||||
|
currentNetPerformancePercent: number;
|
||||||
currentValue: number;
|
currentValue: number;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ export interface PortfolioPosition {
|
|||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
marketState: MarketState;
|
marketState: MarketState;
|
||||||
name: string;
|
name: string;
|
||||||
|
netPerformance: number;
|
||||||
|
netPerformancePercent: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
sectors: Sector[];
|
sectors: Sector[];
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
@ -13,6 +13,8 @@ export interface Position {
|
|||||||
marketPrice?: number;
|
marketPrice?: number;
|
||||||
marketState?: MarketState;
|
marketState?: MarketState;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
netPerformance?: number;
|
||||||
|
netPerformancePercentage?: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
@ -9,6 +9,8 @@ export interface TimelinePosition {
|
|||||||
grossPerformancePercentage: Big;
|
grossPerformancePercentage: Big;
|
||||||
investment: Big;
|
investment: Big;
|
||||||
marketPrice: number;
|
marketPrice: number;
|
||||||
|
netPerformance: Big;
|
||||||
|
netPerformancePercentage: Big;
|
||||||
quantity: Big;
|
quantity: Big;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
transactionCount: number;
|
transactionCount: number;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user