Feature/combine performance and chart calculation (#1285)
* Combine performance and chart calculation endpoints * Update changelog
This commit is contained in:
parent
1095b47f45
commit
7667af059c
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Combined the performance and chart calculation
|
||||||
- Improved the style of various selectors (density)
|
- Improved the style of various selectors (density)
|
||||||
|
|
||||||
## 1.196.0 - 22.09.2022
|
## 1.196.0 - 22.09.2022
|
||||||
|
@ -164,7 +164,7 @@ export class BenchmarkService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const marketPriceAtStartDate = marketDataItems?.[0]?.marketPrice ?? 0;
|
const marketPriceAtStartDate = marketDataItems?.[0]?.marketPrice ?? 0;
|
||||||
return {
|
const response = {
|
||||||
marketData: [
|
marketData: [
|
||||||
...marketDataItems
|
...marketDataItems
|
||||||
.filter((marketDataItem, index) => {
|
.filter((marketDataItem, index) => {
|
||||||
@ -181,17 +181,22 @@ export class BenchmarkService {
|
|||||||
marketDataItem.marketPrice
|
marketDataItem.marketPrice
|
||||||
) * 100
|
) * 100
|
||||||
};
|
};
|
||||||
}),
|
})
|
||||||
{
|
|
||||||
date: format(new Date(), DATE_FORMAT),
|
|
||||||
value:
|
|
||||||
this.calculateChangeInPercentage(
|
|
||||||
marketPriceAtStartDate,
|
|
||||||
currentSymbolItem.marketPrice
|
|
||||||
) * 100
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (currentSymbolItem?.marketPrice) {
|
||||||
|
response.marketData.push({
|
||||||
|
date: format(new Date(), DATE_FORMAT),
|
||||||
|
value:
|
||||||
|
this.calculateChangeInPercentage(
|
||||||
|
marketPriceAtStartDate,
|
||||||
|
currentSymbolItem.marketPrice
|
||||||
|
) * 100
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMarketCondition(aPerformanceInPercent: number) {
|
private getMarketCondition(aPerformanceInPercent: number) {
|
||||||
|
@ -272,23 +272,20 @@ export class PortfolioCalculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInPercentage = true;
|
|
||||||
|
|
||||||
return Object.keys(totalNetPerformanceValues).map((date) => {
|
return Object.keys(totalNetPerformanceValues).map((date) => {
|
||||||
return isInPercentage
|
const netPerformanceInPercentage = totalInvestmentValues[date].eq(0)
|
||||||
? {
|
? 0
|
||||||
date,
|
: totalNetPerformanceValues[date]
|
||||||
value: totalInvestmentValues[date].eq(0)
|
.div(totalInvestmentValues[date])
|
||||||
? 0
|
.mul(100)
|
||||||
: totalNetPerformanceValues[date]
|
.toNumber();
|
||||||
.div(totalInvestmentValues[date])
|
|
||||||
.mul(100)
|
return {
|
||||||
.toNumber()
|
date,
|
||||||
}
|
netPerformanceInPercentage,
|
||||||
: {
|
netPerformance: totalNetPerformanceValues[date].toNumber(),
|
||||||
date,
|
value: netPerformanceInPercentage
|
||||||
value: totalNetPerformanceValues[date].toNumber()
|
};
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,26 +110,6 @@ export class PortfolioController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('chart')
|
|
||||||
@UseGuards(AuthGuard('jwt'))
|
|
||||||
@Version('2')
|
|
||||||
public async getChartV2(
|
|
||||||
@Headers('impersonation-id') impersonationId: string,
|
|
||||||
@Query('range') range
|
|
||||||
): Promise<PortfolioChart> {
|
|
||||||
const historicalDataContainer = await this.portfolioService.getChartV2(
|
|
||||||
impersonationId,
|
|
||||||
range
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chart: historicalDataContainer.items,
|
|
||||||
hasError: false,
|
|
||||||
isAllTimeHigh: false,
|
|
||||||
isAllTimeLow: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('details')
|
@Get('details')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
@UseInterceptors(RedactValuesInResponseInterceptor)
|
@UseInterceptors(RedactValuesInResponseInterceptor)
|
||||||
@ -319,6 +299,35 @@ export class PortfolioController {
|
|||||||
return performanceInformation;
|
return performanceInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('performance')
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
|
@Version('2')
|
||||||
|
public async getPerformanceV2(
|
||||||
|
@Headers('impersonation-id') impersonationId: string,
|
||||||
|
@Query('range') dateRange
|
||||||
|
): Promise<PortfolioPerformanceResponse> {
|
||||||
|
const performanceInformation = await this.portfolioService.getPerformanceV2(
|
||||||
|
{
|
||||||
|
dateRange,
|
||||||
|
impersonationId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.request.user.Settings.settings.viewMode === 'ZEN' ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
|
performanceInformation.performance = nullifyValuesInObject(
|
||||||
|
performanceInformation.performance,
|
||||||
|
['currentGrossPerformance', 'currentNetPerformance', 'currentValue']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return performanceInformation;
|
||||||
|
}
|
||||||
|
|
||||||
@Get('positions')
|
@Get('positions')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||||
|
@ -355,11 +355,14 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChartV2(
|
public async getChartV2({
|
||||||
aImpersonationId: string,
|
dateRange = 'max',
|
||||||
aDateRange: DateRange = 'max'
|
impersonationId
|
||||||
): Promise<HistoricalDataContainer> {
|
}: {
|
||||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
dateRange?: DateRange;
|
||||||
|
impersonationId: string;
|
||||||
|
}): Promise<HistoricalDataContainer> {
|
||||||
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
|
|
||||||
const { portfolioOrders, transactionPoints } =
|
const { portfolioOrders, transactionPoints } =
|
||||||
await this.getTransactionPoints({
|
await this.getTransactionPoints({
|
||||||
@ -383,7 +386,7 @@ export class PortfolioService {
|
|||||||
const endDate = new Date();
|
const endDate = new Date();
|
||||||
|
|
||||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||||
const startDate = this.getStartDate(aDateRange, portfolioStart);
|
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||||
|
|
||||||
const daysInMarket = differenceInDays(new Date(), startDate);
|
const daysInMarket = differenceInDays(new Date(), startDate);
|
||||||
const step = Math.round(
|
const step = Math.round(
|
||||||
@ -987,6 +990,105 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getPerformanceV2({
|
||||||
|
dateRange = 'max',
|
||||||
|
impersonationId
|
||||||
|
}: {
|
||||||
|
dateRange?: DateRange;
|
||||||
|
impersonationId: string;
|
||||||
|
}): Promise<PortfolioPerformanceResponse> {
|
||||||
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
|
|
||||||
|
const { portfolioOrders, transactionPoints } =
|
||||||
|
await this.getTransactionPoints({
|
||||||
|
userId
|
||||||
|
});
|
||||||
|
|
||||||
|
const portfolioCalculator = new PortfolioCalculator({
|
||||||
|
currency: this.request.user.Settings.settings.baseCurrency,
|
||||||
|
currentRateService: this.currentRateService,
|
||||||
|
orders: portfolioOrders
|
||||||
|
});
|
||||||
|
|
||||||
|
if (transactionPoints?.length <= 0) {
|
||||||
|
return {
|
||||||
|
chart: [],
|
||||||
|
hasErrors: false,
|
||||||
|
performance: {
|
||||||
|
currentGrossPerformance: 0,
|
||||||
|
currentGrossPerformancePercent: 0,
|
||||||
|
currentNetPerformance: 0,
|
||||||
|
currentNetPerformancePercent: 0,
|
||||||
|
currentValue: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||||
|
|
||||||
|
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||||
|
const startDate = this.getStartDate(dateRange, portfolioStart);
|
||||||
|
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||||
|
startDate
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasErrors = currentPositions.hasErrors;
|
||||||
|
const currentValue = currentPositions.currentValue.toNumber();
|
||||||
|
const currentGrossPerformance = currentPositions.grossPerformance;
|
||||||
|
const currentGrossPerformancePercent =
|
||||||
|
currentPositions.grossPerformancePercentage;
|
||||||
|
let currentNetPerformance = currentPositions.netPerformance;
|
||||||
|
let currentNetPerformancePercent =
|
||||||
|
currentPositions.netPerformancePercentage;
|
||||||
|
|
||||||
|
// if (currentGrossPerformance.mul(currentGrossPerformancePercent).lt(0)) {
|
||||||
|
// // If algebraic sign is different, harmonize it
|
||||||
|
// currentGrossPerformancePercent = currentGrossPerformancePercent.mul(-1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (currentNetPerformance.mul(currentNetPerformancePercent).lt(0)) {
|
||||||
|
// // If algebraic sign is different, harmonize it
|
||||||
|
// currentNetPerformancePercent = currentNetPerformancePercent.mul(-1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const historicalDataContainer = await this.getChartV2({
|
||||||
|
dateRange,
|
||||||
|
impersonationId
|
||||||
|
});
|
||||||
|
|
||||||
|
const itemOfToday = historicalDataContainer.items.find((item) => {
|
||||||
|
return item.date === format(new Date(), DATE_FORMAT);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itemOfToday) {
|
||||||
|
currentNetPerformance = new Big(itemOfToday.netPerformance);
|
||||||
|
currentNetPerformancePercent = new Big(
|
||||||
|
itemOfToday.netPerformanceInPercentage
|
||||||
|
).div(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
chart: historicalDataContainer.items.map(
|
||||||
|
({ date, netPerformanceInPercentage }) => {
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
value: netPerformanceInPercentage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors: currentPositions.errors,
|
||||||
|
hasErrors: currentPositions.hasErrors || hasErrors,
|
||||||
|
performance: {
|
||||||
|
currentValue,
|
||||||
|
currentGrossPerformance: currentGrossPerformance.toNumber(),
|
||||||
|
currentGrossPerformancePercent:
|
||||||
|
currentGrossPerformancePercent.toNumber(),
|
||||||
|
currentNetPerformance: currentNetPerformance.toNumber(),
|
||||||
|
currentNetPerformancePercent: currentNetPerformancePercent.toNumber()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
||||||
const currency = this.request.user.Settings.settings.baseCurrency;
|
const currency = this.request.user.Settings.settings.baseCurrency;
|
||||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
|
@ -76,8 +76,6 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
!this.hasImpersonationId &&
|
!this.hasImpersonationId &&
|
||||||
!this.user.settings.isRestrictedView &&
|
!this.user.settings.isRestrictedView &&
|
||||||
this.user.settings.viewMode !== 'ZEN';
|
this.user.settings.viewMode !== 'ZEN';
|
||||||
|
|
||||||
this.update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChangeDateRange(dateRange: DateRange) {
|
public onChangeDateRange(dateRange: DateRange) {
|
||||||
@ -104,36 +102,51 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private update() {
|
private update() {
|
||||||
|
this.historicalDataItems = null;
|
||||||
this.isLoadingPerformance = true;
|
this.isLoadingPerformance = true;
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchChart({
|
.fetchPortfolioPerformance({
|
||||||
range: this.user?.settings?.dateRange,
|
range: this.user?.settings?.dateRange,
|
||||||
version: this.user?.settings?.isExperimentalFeatures ? 2 : 1
|
version: this.user?.settings?.isExperimentalFeatures ? 2 : 1
|
||||||
})
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((chartData) => {
|
|
||||||
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
|
|
||||||
return {
|
|
||||||
date: chartDataItem.date,
|
|
||||||
value: chartDataItem.value
|
|
||||||
};
|
|
||||||
});
|
|
||||||
this.isAllTimeHigh = chartData.isAllTimeHigh;
|
|
||||||
this.isAllTimeLow = chartData.isAllTimeLow;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.dataService
|
|
||||||
.fetchPortfolioPerformance({ range: this.user?.settings?.dateRange })
|
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
|
||||||
.subscribe((response) => {
|
.subscribe((response) => {
|
||||||
this.errors = response.errors;
|
this.errors = response.errors;
|
||||||
this.hasError = response.hasErrors;
|
this.hasError = response.hasErrors;
|
||||||
this.performance = response.performance;
|
this.performance = response.performance;
|
||||||
this.isLoadingPerformance = false;
|
this.isLoadingPerformance = false;
|
||||||
|
|
||||||
|
if (this.user?.settings?.isExperimentalFeatures) {
|
||||||
|
this.historicalDataItems = response.chart.map(({ date, value }) => {
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.dataService
|
||||||
|
.fetchChart({
|
||||||
|
range: this.user?.settings?.dateRange,
|
||||||
|
version: 1
|
||||||
|
})
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((chartData) => {
|
||||||
|
this.historicalDataItems = chartData.chart.map(
|
||||||
|
({ date, value }) => {
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.isAllTimeHigh = chartData.isAllTimeHigh;
|
||||||
|
this.isAllTimeLow = chartData.isAllTimeLow;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -126,7 +126,10 @@ export class AnalysisPageComponent implements OnDestroy, OnInit {
|
|||||||
this.isLoadingBenchmarkComparator = true;
|
this.isLoadingBenchmarkComparator = true;
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchChart({ range: this.user?.settings?.dateRange, version: 2 })
|
.fetchPortfolioPerformance({
|
||||||
|
range: this.user?.settings?.dateRange,
|
||||||
|
version: 2
|
||||||
|
})
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ chart }) => {
|
.subscribe(({ chart }) => {
|
||||||
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
|
this.firstOrderDate = new Date(chart?.[0]?.date ?? new Date());
|
||||||
|
@ -353,12 +353,16 @@ export class DataService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioPerformance(params: { [param: string]: any }) {
|
public fetchPortfolioPerformance({
|
||||||
|
range,
|
||||||
|
version
|
||||||
|
}: {
|
||||||
|
range: DateRange;
|
||||||
|
version: number;
|
||||||
|
}) {
|
||||||
return this.http.get<PortfolioPerformanceResponse>(
|
return this.http.get<PortfolioPerformanceResponse>(
|
||||||
'/api/v1/portfolio/performance',
|
`/api/v${version}/portfolio/performance`,
|
||||||
{
|
{ params: { range } }
|
||||||
params
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,5 +2,7 @@ export interface HistoricalDataItem {
|
|||||||
averagePrice?: number;
|
averagePrice?: number;
|
||||||
date: string;
|
date: string;
|
||||||
grossPerformancePercent?: number;
|
grossPerformancePercent?: number;
|
||||||
|
netPerformance?: number;
|
||||||
|
netPerformanceInPercentage?: number;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { HistoricalDataItem } from '../historical-data-item.interface';
|
||||||
import { PortfolioPerformance } from '../portfolio-performance.interface';
|
import { PortfolioPerformance } from '../portfolio-performance.interface';
|
||||||
import { ResponseError } from './errors.interface';
|
import { ResponseError } from './errors.interface';
|
||||||
|
|
||||||
export interface PortfolioPerformanceResponse extends ResponseError {
|
export interface PortfolioPerformanceResponse extends ResponseError {
|
||||||
|
chart?: HistoricalDataItem[];
|
||||||
performance: PortfolioPerformance;
|
performance: PortfolioPerformance;
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ngOnChanges() {
|
public ngOnChanges() {
|
||||||
if (this.historicalDataItems) {
|
if (this.historicalDataItems || this.historicalDataItems === null) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Wait for the chartCanvas
|
// Wait for the chartCanvas
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user