|
|
@ -288,7 +288,9 @@ export abstract class PortfolioCalculator { |
|
|
|
firstIndex--; |
|
|
|
} |
|
|
|
|
|
|
|
const positions: TimelinePosition[] = []; |
|
|
|
const positions: (TimelinePosition & { |
|
|
|
isInvestmentAssetProfilePosition: boolean; |
|
|
|
})[] = []; |
|
|
|
let hasAnySymbolMetricsErrors = false; |
|
|
|
|
|
|
|
const errors: ResponseError['errors'] = []; |
|
|
@ -412,6 +414,7 @@ export abstract class PortfolioCalculator { |
|
|
|
: null, |
|
|
|
investment: totalInvestment, |
|
|
|
investmentWithCurrencyEffect: totalInvestmentWithCurrencyEffect, |
|
|
|
isInvestmentAssetProfilePosition: item.isInvestmentAssetProfileItem, |
|
|
|
marketPrice: |
|
|
|
marketSymbolMap[endDateString]?.[item.symbol]?.toNumber() ?? null, |
|
|
|
marketPriceInBaseCurrency: |
|
|
@ -606,14 +609,23 @@ export abstract class PortfolioCalculator { |
|
|
|
|
|
|
|
const overall = this.calculateOverallPerformance(positions); |
|
|
|
|
|
|
|
const investmentPositions = positions |
|
|
|
.filter(({ isInvestmentAssetProfilePosition }) => { |
|
|
|
return isInvestmentAssetProfilePosition; |
|
|
|
}) |
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
.map(({ isInvestmentAssetProfilePosition, ...rest }) => { |
|
|
|
return rest; |
|
|
|
}); |
|
|
|
|
|
|
|
return { |
|
|
|
...overall, |
|
|
|
errors, |
|
|
|
historicalData, |
|
|
|
positions, |
|
|
|
totalInterestWithCurrencyEffect, |
|
|
|
totalLiabilitiesWithCurrencyEffect, |
|
|
|
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors |
|
|
|
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors, |
|
|
|
positions: investmentPositions |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
@ -899,82 +911,82 @@ export abstract class PortfolioCalculator { |
|
|
|
} of this.activities) { |
|
|
|
let currentTransactionPointItem: TransactionPointSymbol; |
|
|
|
|
|
|
|
if (INVESTMENT_ACTIVITY_TYPES.includes(type)) { |
|
|
|
const currency = SymbolProfile.currency; |
|
|
|
const dataSource = SymbolProfile.dataSource; |
|
|
|
const factor = getFactor(type); |
|
|
|
const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles
|
|
|
|
const symbol = SymbolProfile.symbol; |
|
|
|
|
|
|
|
const oldAccumulatedSymbol = symbols[symbol]; |
|
|
|
|
|
|
|
if (oldAccumulatedSymbol) { |
|
|
|
let investment = oldAccumulatedSymbol.investment; |
|
|
|
|
|
|
|
const newQuantity = quantity |
|
|
|
.mul(factor) |
|
|
|
.plus(oldAccumulatedSymbol.quantity); |
|
|
|
|
|
|
|
if (type === 'BUY') { |
|
|
|
investment = oldAccumulatedSymbol.investment.plus( |
|
|
|
quantity.mul(unitPrice) |
|
|
|
); |
|
|
|
} else if (type === 'SELL') { |
|
|
|
investment = oldAccumulatedSymbol.investment.minus( |
|
|
|
quantity.mul(oldAccumulatedSymbol.averagePrice) |
|
|
|
); |
|
|
|
} |
|
|
|
const currency = SymbolProfile.currency; |
|
|
|
const dataSource = SymbolProfile.dataSource; |
|
|
|
const factor = getFactor(type); |
|
|
|
const skipErrors = !!SymbolProfile.userId; // Skip errors for custom asset profiles
|
|
|
|
const symbol = SymbolProfile.symbol; |
|
|
|
|
|
|
|
currentTransactionPointItem = { |
|
|
|
currency, |
|
|
|
dataSource, |
|
|
|
investment, |
|
|
|
skipErrors, |
|
|
|
symbol, |
|
|
|
averagePrice: newQuantity.gt(0) |
|
|
|
? investment.div(newQuantity) |
|
|
|
: new Big(0), |
|
|
|
dividend: new Big(0), |
|
|
|
fee: oldAccumulatedSymbol.fee.plus(fee), |
|
|
|
firstBuyDate: oldAccumulatedSymbol.firstBuyDate, |
|
|
|
quantity: newQuantity, |
|
|
|
tags: oldAccumulatedSymbol.tags.concat(tags), |
|
|
|
transactionCount: oldAccumulatedSymbol.transactionCount + 1 |
|
|
|
}; |
|
|
|
} else { |
|
|
|
currentTransactionPointItem = { |
|
|
|
currency, |
|
|
|
dataSource, |
|
|
|
fee, |
|
|
|
skipErrors, |
|
|
|
symbol, |
|
|
|
tags, |
|
|
|
averagePrice: unitPrice, |
|
|
|
dividend: new Big(0), |
|
|
|
firstBuyDate: date, |
|
|
|
investment: unitPrice.mul(quantity).mul(factor), |
|
|
|
quantity: quantity.mul(factor), |
|
|
|
transactionCount: 1 |
|
|
|
}; |
|
|
|
} |
|
|
|
const oldAccumulatedSymbol = symbols[symbol]; |
|
|
|
|
|
|
|
currentTransactionPointItem.tags = uniqBy( |
|
|
|
currentTransactionPointItem.tags, |
|
|
|
'id' |
|
|
|
); |
|
|
|
if (oldAccumulatedSymbol) { |
|
|
|
let investment = oldAccumulatedSymbol.investment; |
|
|
|
|
|
|
|
symbols[SymbolProfile.symbol] = currentTransactionPointItem; |
|
|
|
const newQuantity = quantity |
|
|
|
.mul(factor) |
|
|
|
.plus(oldAccumulatedSymbol.quantity); |
|
|
|
|
|
|
|
if (type === 'BUY') { |
|
|
|
investment = oldAccumulatedSymbol.investment.plus( |
|
|
|
quantity.mul(unitPrice) |
|
|
|
); |
|
|
|
} else if (type === 'SELL') { |
|
|
|
investment = oldAccumulatedSymbol.investment.minus( |
|
|
|
quantity.mul(oldAccumulatedSymbol.averagePrice) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
currentTransactionPointItem = { |
|
|
|
currency, |
|
|
|
dataSource, |
|
|
|
investment, |
|
|
|
skipErrors, |
|
|
|
symbol, |
|
|
|
averagePrice: newQuantity.gt(0) |
|
|
|
? investment.div(newQuantity) |
|
|
|
: new Big(0), |
|
|
|
dividend: new Big(0), |
|
|
|
fee: oldAccumulatedSymbol.fee.plus(fee), |
|
|
|
firstBuyDate: oldAccumulatedSymbol.firstBuyDate, |
|
|
|
isInvestmentAssetProfileItem: |
|
|
|
oldAccumulatedSymbol.isInvestmentAssetProfileItem, |
|
|
|
quantity: newQuantity, |
|
|
|
tags: oldAccumulatedSymbol.tags.concat(tags), |
|
|
|
transactionCount: oldAccumulatedSymbol.transactionCount + 1 |
|
|
|
}; |
|
|
|
} else { |
|
|
|
currentTransactionPointItem = { |
|
|
|
currency, |
|
|
|
dataSource, |
|
|
|
fee, |
|
|
|
skipErrors, |
|
|
|
symbol, |
|
|
|
tags, |
|
|
|
averagePrice: unitPrice, |
|
|
|
dividend: new Big(0), |
|
|
|
firstBuyDate: date, |
|
|
|
investment: unitPrice.mul(quantity).mul(factor), |
|
|
|
isInvestmentAssetProfileItem: |
|
|
|
INVESTMENT_ACTIVITY_TYPES.includes(type), |
|
|
|
quantity: quantity.mul(factor), |
|
|
|
transactionCount: 1 |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
currentTransactionPointItem.tags = uniqBy( |
|
|
|
currentTransactionPointItem.tags, |
|
|
|
'id' |
|
|
|
); |
|
|
|
|
|
|
|
symbols[SymbolProfile.symbol] = currentTransactionPointItem; |
|
|
|
|
|
|
|
const items = lastTransactionPoint?.items ?? []; |
|
|
|
|
|
|
|
const newItems = items.filter(({ symbol }) => { |
|
|
|
return symbol !== SymbolProfile.symbol; |
|
|
|
}); |
|
|
|
|
|
|
|
if (currentTransactionPointItem) { |
|
|
|
newItems.push(currentTransactionPointItem); |
|
|
|
} |
|
|
|
newItems.push(currentTransactionPointItem); |
|
|
|
|
|
|
|
newItems.sort((a, b) => { |
|
|
|
return a.symbol?.localeCompare(b.symbol); |
|
|
|