Feature/extend X-ray page by summary (#4107)
* Add summary to X-ray page * Update changelog
This commit is contained in:
parent
d6357487ea
commit
291be3e605
@ -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
|
||||||
|
|
||||||
|
- Extended the _X-ray_ page by a summary
|
||||||
|
|
||||||
## 2.126.1 - 2024-12-07
|
## 2.126.1 - 2024-12-07
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
PortfolioHoldingsResponse,
|
PortfolioHoldingsResponse,
|
||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioReport
|
PortfolioReportResponse
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import {
|
import {
|
||||||
hasReadRestrictedAccessPermission,
|
hasReadRestrictedAccessPermission,
|
||||||
@ -611,7 +611,7 @@ export class PortfolioController {
|
|||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
public async getReport(
|
public async getReport(
|
||||||
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string
|
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string
|
||||||
): Promise<PortfolioReport> {
|
): Promise<PortfolioReportResponse> {
|
||||||
const report = await this.portfolioService.getReport(impersonationId);
|
const report = await this.portfolioService.getReport(impersonationId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -37,7 +37,7 @@ import {
|
|||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
PortfolioReport,
|
PortfolioReportResponse,
|
||||||
PortfolioSummary,
|
PortfolioSummary,
|
||||||
Position,
|
Position,
|
||||||
UserSettings
|
UserSettings
|
||||||
@ -1162,7 +1162,9 @@ export class PortfolioService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getReport(impersonationId: string): Promise<PortfolioReport> {
|
public async getReport(
|
||||||
|
impersonationId: string
|
||||||
|
): Promise<PortfolioReportResponse> {
|
||||||
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
const userId = await this.getUserId(impersonationId, this.request.user.id);
|
||||||
const userSettings = this.request.user.Settings.settings as UserSettings;
|
const userSettings = this.request.user.Settings.settings as UserSettings;
|
||||||
|
|
||||||
@ -1179,79 +1181,79 @@ export class PortfolioService {
|
|||||||
})
|
})
|
||||||
).toNumber();
|
).toNumber();
|
||||||
|
|
||||||
return {
|
const rules: PortfolioReportResponse['rules'] = {
|
||||||
rules: {
|
accountClusterRisk:
|
||||||
accountClusterRisk:
|
summary.ordersCount > 0
|
||||||
summary.ordersCount > 0
|
? await this.rulesService.evaluate(
|
||||||
? await this.rulesService.evaluate(
|
[
|
||||||
[
|
new AccountClusterRiskCurrentInvestment(
|
||||||
new AccountClusterRiskCurrentInvestment(
|
this.exchangeRateDataService,
|
||||||
this.exchangeRateDataService,
|
accounts
|
||||||
accounts
|
),
|
||||||
),
|
new AccountClusterRiskSingleAccount(
|
||||||
new AccountClusterRiskSingleAccount(
|
this.exchangeRateDataService,
|
||||||
this.exchangeRateDataService,
|
accounts
|
||||||
accounts
|
)
|
||||||
)
|
],
|
||||||
],
|
userSettings
|
||||||
userSettings
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
economicMarketClusterRisk:
|
|
||||||
summary.ordersCount > 0
|
|
||||||
? await this.rulesService.evaluate(
|
|
||||||
[
|
|
||||||
new EconomicMarketClusterRiskDevelopedMarkets(
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
marketsTotalInBaseCurrency,
|
|
||||||
markets.developedMarkets.valueInBaseCurrency
|
|
||||||
),
|
|
||||||
new EconomicMarketClusterRiskEmergingMarkets(
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
marketsTotalInBaseCurrency,
|
|
||||||
markets.emergingMarkets.valueInBaseCurrency
|
|
||||||
)
|
|
||||||
],
|
|
||||||
userSettings
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
currencyClusterRisk:
|
|
||||||
summary.ordersCount > 0
|
|
||||||
? await this.rulesService.evaluate(
|
|
||||||
[
|
|
||||||
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
Object.values(holdings)
|
|
||||||
),
|
|
||||||
new CurrencyClusterRiskCurrentInvestment(
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
Object.values(holdings)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
userSettings
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
emergencyFund: await this.rulesService.evaluate(
|
|
||||||
[
|
|
||||||
new EmergencyFundSetup(
|
|
||||||
this.exchangeRateDataService,
|
|
||||||
userSettings.emergencyFund
|
|
||||||
)
|
)
|
||||||
],
|
: undefined,
|
||||||
userSettings
|
economicMarketClusterRisk:
|
||||||
),
|
summary.ordersCount > 0
|
||||||
fees: await this.rulesService.evaluate(
|
? await this.rulesService.evaluate(
|
||||||
[
|
[
|
||||||
new FeeRatioInitialInvestment(
|
new EconomicMarketClusterRiskDevelopedMarkets(
|
||||||
this.exchangeRateDataService,
|
this.exchangeRateDataService,
|
||||||
summary.committedFunds,
|
marketsTotalInBaseCurrency,
|
||||||
summary.fees
|
markets.developedMarkets.valueInBaseCurrency
|
||||||
|
),
|
||||||
|
new EconomicMarketClusterRiskEmergingMarkets(
|
||||||
|
this.exchangeRateDataService,
|
||||||
|
marketsTotalInBaseCurrency,
|
||||||
|
markets.emergingMarkets.valueInBaseCurrency
|
||||||
|
)
|
||||||
|
],
|
||||||
|
userSettings
|
||||||
)
|
)
|
||||||
],
|
: undefined,
|
||||||
userSettings
|
currencyClusterRisk:
|
||||||
)
|
summary.ordersCount > 0
|
||||||
}
|
? await this.rulesService.evaluate(
|
||||||
|
[
|
||||||
|
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
||||||
|
this.exchangeRateDataService,
|
||||||
|
Object.values(holdings)
|
||||||
|
),
|
||||||
|
new CurrencyClusterRiskCurrentInvestment(
|
||||||
|
this.exchangeRateDataService,
|
||||||
|
Object.values(holdings)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
userSettings
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
emergencyFund: await this.rulesService.evaluate(
|
||||||
|
[
|
||||||
|
new EmergencyFundSetup(
|
||||||
|
this.exchangeRateDataService,
|
||||||
|
userSettings.emergencyFund
|
||||||
|
)
|
||||||
|
],
|
||||||
|
userSettings
|
||||||
|
),
|
||||||
|
fees: await this.rulesService.evaluate(
|
||||||
|
[
|
||||||
|
new FeeRatioInitialInvestment(
|
||||||
|
this.exchangeRateDataService,
|
||||||
|
summary.committedFunds,
|
||||||
|
summary.fees
|
||||||
|
)
|
||||||
|
],
|
||||||
|
userSettings
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return { rules, statistics: this.getReportStatistics(rules) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateTags({
|
public async updateTags({
|
||||||
@ -1670,6 +1672,24 @@ export class PortfolioService {
|
|||||||
return { markets, marketsAdvanced };
|
return { markets, marketsAdvanced };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getReportStatistics(
|
||||||
|
evaluatedRules: PortfolioReportResponse['rules']
|
||||||
|
): PortfolioReportResponse['statistics'] {
|
||||||
|
const rulesActiveCount = Object.values(evaluatedRules)
|
||||||
|
.flat()
|
||||||
|
.filter(({ isActive }) => {
|
||||||
|
return isActive === true;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
const rulesFulfilledCount = Object.values(evaluatedRules)
|
||||||
|
.flat()
|
||||||
|
.filter(({ value }) => {
|
||||||
|
return value === true;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
return { rulesActiveCount, rulesFulfilledCount };
|
||||||
|
}
|
||||||
|
|
||||||
private getStreaks({
|
private getStreaks({
|
||||||
investments,
|
investments,
|
||||||
savingsRate
|
savingsRate
|
||||||
|
@ -2,11 +2,28 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h2 class="d-none d-sm-block h3 mb-3 text-center">X-ray</h2>
|
<h2 class="d-none d-sm-block h3 mb-3 text-center">X-ray</h2>
|
||||||
<p class="mb-4" i18n>
|
<p i18n>
|
||||||
Ghostfolio X-ray uses static analysis to uncover potential issues and
|
Ghostfolio X-ray uses static analysis to uncover potential issues and
|
||||||
risks in your portfolio. Adjust the rules below and set custom
|
risks in your portfolio. Adjust the rules below and set custom
|
||||||
thresholds to align with your personal investment strategy.
|
thresholds to align with your personal investment strategy.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mb-4">
|
||||||
|
@if (isLoading) {
|
||||||
|
<ngx-skeleton-loader
|
||||||
|
animation="pulse"
|
||||||
|
class="w-100"
|
||||||
|
[theme]="{
|
||||||
|
height: '1rem',
|
||||||
|
width: '100%'
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
} @else {
|
||||||
|
{{ statistics?.rulesFulfilledCount }}
|
||||||
|
<ng-container i18n>of</ng-container>
|
||||||
|
{{ statistics?.rulesActiveCount }}
|
||||||
|
<ng-container i18n>rules are currently fulfilled.</ng-container>
|
||||||
|
}
|
||||||
|
</p>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h4 class="align-items-center d-flex m-0">
|
<h4 class="align-items-center d-flex m-0">
|
||||||
<span i18n>Emergency Fund</span>
|
<span i18n>Emergency Fund</span>
|
||||||
@ -20,7 +37,7 @@
|
|||||||
hasPermissionToUpdateUserSettings &&
|
hasPermissionToUpdateUserSettings &&
|
||||||
user?.settings?.isExperimentalFeatures
|
user?.settings?.isExperimentalFeatures
|
||||||
"
|
"
|
||||||
[isLoading]="isLoadingPortfolioReport"
|
[isLoading]="isLoading"
|
||||||
[rules]="emergencyFundRules"
|
[rules]="emergencyFundRules"
|
||||||
[settings]="user?.settings?.xRayRules"
|
[settings]="user?.settings?.xRayRules"
|
||||||
(rulesUpdated)="onRulesUpdated($event)"
|
(rulesUpdated)="onRulesUpdated($event)"
|
||||||
@ -39,7 +56,7 @@
|
|||||||
hasPermissionToUpdateUserSettings &&
|
hasPermissionToUpdateUserSettings &&
|
||||||
user?.settings?.isExperimentalFeatures
|
user?.settings?.isExperimentalFeatures
|
||||||
"
|
"
|
||||||
[isLoading]="isLoadingPortfolioReport"
|
[isLoading]="isLoading"
|
||||||
[rules]="currencyClusterRiskRules"
|
[rules]="currencyClusterRiskRules"
|
||||||
[settings]="user?.settings?.xRayRules"
|
[settings]="user?.settings?.xRayRules"
|
||||||
(rulesUpdated)="onRulesUpdated($event)"
|
(rulesUpdated)="onRulesUpdated($event)"
|
||||||
@ -58,7 +75,7 @@
|
|||||||
hasPermissionToUpdateUserSettings &&
|
hasPermissionToUpdateUserSettings &&
|
||||||
user?.settings?.isExperimentalFeatures
|
user?.settings?.isExperimentalFeatures
|
||||||
"
|
"
|
||||||
[isLoading]="isLoadingPortfolioReport"
|
[isLoading]="isLoading"
|
||||||
[rules]="accountClusterRiskRules"
|
[rules]="accountClusterRiskRules"
|
||||||
[settings]="user?.settings?.xRayRules"
|
[settings]="user?.settings?.xRayRules"
|
||||||
(rulesUpdated)="onRulesUpdated($event)"
|
(rulesUpdated)="onRulesUpdated($event)"
|
||||||
@ -77,7 +94,7 @@
|
|||||||
hasPermissionToUpdateUserSettings &&
|
hasPermissionToUpdateUserSettings &&
|
||||||
user?.settings?.isExperimentalFeatures
|
user?.settings?.isExperimentalFeatures
|
||||||
"
|
"
|
||||||
[isLoading]="isLoadingPortfolioReport"
|
[isLoading]="isLoading"
|
||||||
[rules]="economicMarketClusterRiskRules"
|
[rules]="economicMarketClusterRiskRules"
|
||||||
[settings]="user?.settings?.xRayRules"
|
[settings]="user?.settings?.xRayRules"
|
||||||
(rulesUpdated)="onRulesUpdated($event)"
|
(rulesUpdated)="onRulesUpdated($event)"
|
||||||
@ -96,7 +113,7 @@
|
|||||||
hasPermissionToUpdateUserSettings &&
|
hasPermissionToUpdateUserSettings &&
|
||||||
user?.settings?.isExperimentalFeatures
|
user?.settings?.isExperimentalFeatures
|
||||||
"
|
"
|
||||||
[isLoading]="isLoadingPortfolioReport"
|
[isLoading]="isLoading"
|
||||||
[rules]="feeRules"
|
[rules]="feeRules"
|
||||||
[settings]="user?.settings?.xRayRules"
|
[settings]="user?.settings?.xRayRules"
|
||||||
(rulesUpdated)="onRulesUpdated($event)"
|
(rulesUpdated)="onRulesUpdated($event)"
|
||||||
@ -111,7 +128,7 @@
|
|||||||
hasPermissionToUpdateUserSettings &&
|
hasPermissionToUpdateUserSettings &&
|
||||||
user?.settings?.isExperimentalFeatures
|
user?.settings?.isExperimentalFeatures
|
||||||
"
|
"
|
||||||
[isLoading]="isLoadingPortfolioReport"
|
[isLoading]="isLoading"
|
||||||
[rules]="inactiveRules"
|
[rules]="inactiveRules"
|
||||||
[settings]="user?.settings?.xRayRules"
|
[settings]="user?.settings?.xRayRules"
|
||||||
(rulesUpdated)="onRulesUpdated($event)"
|
(rulesUpdated)="onRulesUpdated($event)"
|
||||||
|
@ -3,8 +3,8 @@ 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 {
|
import {
|
||||||
PortfolioReportRule,
|
PortfolioReportResponse,
|
||||||
PortfolioReport
|
PortfolioReportRule
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { User } from '@ghostfolio/common/interfaces/user.interface';
|
import { User } from '@ghostfolio/common/interfaces/user.interface';
|
||||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
@ -26,7 +26,8 @@ export class XRayPageComponent {
|
|||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
public hasPermissionToUpdateUserSettings: boolean;
|
public hasPermissionToUpdateUserSettings: boolean;
|
||||||
public inactiveRules: PortfolioReportRule[];
|
public inactiveRules: PortfolioReportRule[];
|
||||||
public isLoadingPortfolioReport = false;
|
public isLoading = false;
|
||||||
|
public statistics: PortfolioReportResponse['statistics'];
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -87,56 +88,53 @@ export class XRayPageComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private initializePortfolioReport() {
|
private initializePortfolioReport() {
|
||||||
this.isLoadingPortfolioReport = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
this.dataService
|
this.dataService
|
||||||
.fetchPortfolioReport()
|
.fetchPortfolioReport()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((portfolioReport) => {
|
.subscribe(({ rules, statistics }) => {
|
||||||
this.inactiveRules = this.mergeInactiveRules(portfolioReport);
|
this.inactiveRules = this.mergeInactiveRules(rules);
|
||||||
|
this.statistics = statistics;
|
||||||
|
|
||||||
this.accountClusterRiskRules =
|
this.accountClusterRiskRules =
|
||||||
portfolioReport.rules['accountClusterRisk']?.filter(
|
rules['accountClusterRisk']?.filter(({ isActive }) => {
|
||||||
({ isActive }) => {
|
return isActive;
|
||||||
return isActive;
|
}) ?? null;
|
||||||
}
|
|
||||||
) ?? null;
|
|
||||||
|
|
||||||
this.currencyClusterRiskRules =
|
this.currencyClusterRiskRules =
|
||||||
portfolioReport.rules['currencyClusterRisk']?.filter(
|
rules['currencyClusterRisk']?.filter(({ isActive }) => {
|
||||||
({ isActive }) => {
|
return isActive;
|
||||||
return isActive;
|
}) ?? null;
|
||||||
}
|
|
||||||
) ?? null;
|
|
||||||
|
|
||||||
this.economicMarketClusterRiskRules =
|
this.economicMarketClusterRiskRules =
|
||||||
portfolioReport.rules['economicMarketClusterRisk']?.filter(
|
rules['economicMarketClusterRisk']?.filter(({ isActive }) => {
|
||||||
({ isActive }) => {
|
return isActive;
|
||||||
return isActive;
|
}) ?? null;
|
||||||
}
|
|
||||||
) ?? null;
|
|
||||||
|
|
||||||
this.emergencyFundRules =
|
this.emergencyFundRules =
|
||||||
portfolioReport.rules['emergencyFund']?.filter(({ isActive }) => {
|
rules['emergencyFund']?.filter(({ isActive }) => {
|
||||||
return isActive;
|
return isActive;
|
||||||
}) ?? null;
|
}) ?? null;
|
||||||
|
|
||||||
this.feeRules =
|
this.feeRules =
|
||||||
portfolioReport.rules['fees']?.filter(({ isActive }) => {
|
rules['fees']?.filter(({ isActive }) => {
|
||||||
return isActive;
|
return isActive;
|
||||||
}) ?? null;
|
}) ?? null;
|
||||||
|
|
||||||
this.isLoadingPortfolioReport = false;
|
this.isLoading = false;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private mergeInactiveRules(report: PortfolioReport): PortfolioReportRule[] {
|
private mergeInactiveRules(
|
||||||
|
rules: PortfolioReportResponse['rules']
|
||||||
|
): PortfolioReportRule[] {
|
||||||
let inactiveRules: PortfolioReportRule[] = [];
|
let inactiveRules: PortfolioReportRule[] = [];
|
||||||
|
|
||||||
for (const category in report.rules) {
|
for (const category in rules) {
|
||||||
const rulesArray = report.rules[category];
|
const rulesArray = rules[category];
|
||||||
|
|
||||||
inactiveRules = inactiveRules.concat(
|
inactiveRules = inactiveRules.concat(
|
||||||
rulesArray.filter(({ isActive }) => {
|
rulesArray.filter(({ isActive }) => {
|
||||||
|
@ -37,7 +37,7 @@ import {
|
|||||||
PortfolioHoldingsResponse,
|
PortfolioHoldingsResponse,
|
||||||
PortfolioInvestments,
|
PortfolioInvestments,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioReport,
|
PortfolioReportResponse,
|
||||||
PublicPortfolioResponse,
|
PublicPortfolioResponse,
|
||||||
User
|
User
|
||||||
} from '@ghostfolio/common/interfaces';
|
} from '@ghostfolio/common/interfaces';
|
||||||
@ -613,7 +613,7 @@ export class DataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fetchPortfolioReport() {
|
public fetchPortfolioReport() {
|
||||||
return this.http.get<PortfolioReport>('/api/v1/portfolio/report');
|
return this.http.get<PortfolioReportResponse>('/api/v1/portfolio/report');
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetchPublicPortfolio(aAccessId: string) {
|
public fetchPublicPortfolio(aAccessId: string) {
|
||||||
|
@ -34,7 +34,6 @@ import type { PortfolioOverview } from './portfolio-overview.interface';
|
|||||||
import type { PortfolioPerformance } from './portfolio-performance.interface';
|
import type { PortfolioPerformance } from './portfolio-performance.interface';
|
||||||
import type { PortfolioPosition } from './portfolio-position.interface';
|
import type { PortfolioPosition } from './portfolio-position.interface';
|
||||||
import type { PortfolioReportRule } from './portfolio-report-rule.interface';
|
import type { PortfolioReportRule } from './portfolio-report-rule.interface';
|
||||||
import type { PortfolioReport } from './portfolio-report.interface';
|
|
||||||
import type { PortfolioSummary } from './portfolio-summary.interface';
|
import type { PortfolioSummary } from './portfolio-summary.interface';
|
||||||
import type { Position } from './position.interface';
|
import type { Position } from './position.interface';
|
||||||
import type { Product } from './product';
|
import type { Product } from './product';
|
||||||
@ -50,6 +49,7 @@ import type { LookupResponse } from './responses/lookup-response.interface';
|
|||||||
import type { OAuthResponse } from './responses/oauth-response.interface';
|
import type { OAuthResponse } from './responses/oauth-response.interface';
|
||||||
import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface';
|
import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface';
|
||||||
import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
|
import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
|
||||||
|
import type { PortfolioReportResponse } from './responses/portfolio-report.interface';
|
||||||
import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface';
|
import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface';
|
||||||
import type { QuotesResponse } from './responses/quotes-response.interface';
|
import type { QuotesResponse } from './responses/quotes-response.interface';
|
||||||
import type { ScraperConfiguration } from './scraper-configuration.interface';
|
import type { ScraperConfiguration } from './scraper-configuration.interface';
|
||||||
@ -108,7 +108,7 @@ export {
|
|||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
PortfolioPerformanceResponse,
|
PortfolioPerformanceResponse,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
PortfolioReport,
|
PortfolioReportResponse,
|
||||||
PortfolioReportRule,
|
PortfolioReportRule,
|
||||||
PortfolioSummary,
|
PortfolioSummary,
|
||||||
Position,
|
Position,
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { PortfolioReportRule } from './portfolio-report-rule.interface';
|
|
||||||
|
|
||||||
export interface PortfolioReport {
|
|
||||||
rules: { [group: string]: PortfolioReportRule[] };
|
|
||||||
}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
import { PortfolioReportRule } from '../portfolio-report-rule.interface';
|
||||||
|
|
||||||
|
export interface PortfolioReportResponse {
|
||||||
|
rules: { [group: string]: PortfolioReportRule[] };
|
||||||
|
statistics: {
|
||||||
|
rulesActiveCount: number;
|
||||||
|
rulesFulfilledCount: number;
|
||||||
|
};
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user