Browse Source

Feature/clean up initial values from x ray (#1914)

* Clean up initial (original) values from X-Ray

* Refactor current to valueInBaseCurrency

* Update changelog
pull/1915/head
Thomas Kaul 2 years ago
committed by GitHub
parent
commit
1ca3792a4b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 5
      apps/api/src/app/portfolio/portfolio.controller.ts
  3. 44
      apps/api/src/app/portfolio/portfolio.service.ts
  4. 4
      apps/api/src/models/rules/account-cluster-risk/current-investment.ts
  5. 88
      apps/api/src/models/rules/account-cluster-risk/initial-investment.ts
  6. 2
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  7. 71
      apps/api/src/models/rules/currency-cluster-risk/base-currency-initial-investment.ts
  8. 2
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  9. 72
      apps/api/src/models/rules/currency-cluster-risk/initial-investment.ts
  10. 2
      apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
  11. 17
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  12. 4
      libs/common/src/lib/interfaces/portfolio-details.interface.ts

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Deprecated the use of the environment variable `BASE_CURRENCY` - Deprecated the use of the environment variable `BASE_CURRENCY`
- Cleaned up initial values from the _X-ray_ section
## 1.263.0 - 2023-04-30 ## 1.263.0 - 2023-04-30

5
apps/api/src/app/portfolio/portfolio.controller.ts

@ -136,9 +136,8 @@ export class PortfolioController {
portfolioPosition.value / totalValue; portfolioPosition.value / totalValue;
} }
for (const [name, { current, original }] of Object.entries(accounts)) { for (const [name, { valueInBaseCurrency }] of Object.entries(accounts)) {
accounts[name].current = current / totalValue; accounts[name].valueInPercentage = valueInBaseCurrency / totalValue;
accounts[name].original = original / totalInvestment;
} }
} }

44
apps/api/src/app/portfolio/portfolio.service.ts

@ -7,12 +7,9 @@ import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfol
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface'; import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
import { UserService } from '@ghostfolio/api/app/user/user.service'; import { UserService } from '@ghostfolio/api/app/user/user.service';
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment';
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment'; import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
import { CurrencyClusterRiskBaseCurrencyInitialInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-initial-investment';
import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment'; import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment';
import { CurrencyClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/initial-investment';
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment'; import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
@ -149,7 +146,8 @@ export class PortfolioService {
} }
} }
const valueInBaseCurrency = details.accounts[account.id]?.current ?? 0; const valueInBaseCurrency =
details.accounts[account.id]?.valueInBaseCurrency ?? 0;
const result = { const result = {
...account, ...account,
@ -618,9 +616,8 @@ export class PortfolioService {
accounts[UNKNOWN_KEY] = { accounts[UNKNOWN_KEY] = {
balance: 0, balance: 0,
currency: userCurrency, currency: userCurrency,
current: emergencyFundInCash,
name: UNKNOWN_KEY, name: UNKNOWN_KEY,
original: emergencyFundInCash valueInBaseCurrency: emergencyFundInCash
}; };
holdings[userCurrency] = { holdings[userCurrency] = {
@ -1185,10 +1182,6 @@ export class PortfolioService {
rules: { rules: {
accountClusterRisk: await this.rulesService.evaluate( accountClusterRisk: await this.rulesService.evaluate(
[ [
new AccountClusterRiskInitialInvestment(
this.exchangeRateDataService,
accounts
),
new AccountClusterRiskCurrentInvestment( new AccountClusterRiskCurrentInvestment(
this.exchangeRateDataService, this.exchangeRateDataService,
accounts accounts
@ -1202,18 +1195,10 @@ export class PortfolioService {
), ),
currencyClusterRisk: await this.rulesService.evaluate( currencyClusterRisk: await this.rulesService.evaluate(
[ [
new CurrencyClusterRiskBaseCurrencyInitialInvestment(
this.exchangeRateDataService,
positions
),
new CurrencyClusterRiskBaseCurrencyCurrentInvestment( new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
this.exchangeRateDataService, this.exchangeRateDataService,
positions positions
), ),
new CurrencyClusterRiskInitialInvestment(
this.exchangeRateDataService,
positions
),
new CurrencyClusterRiskCurrentInvestment( new CurrencyClusterRiskCurrentInvestment(
this.exchangeRateDataService, this.exchangeRateDataService,
positions positions
@ -1774,13 +1759,8 @@ export class PortfolioService {
accounts[account.id] = { accounts[account.id] = {
balance: account.balance, balance: account.balance,
currency: account.currency, currency: account.currency,
current: this.exchangeRateDataService.toCurrency(
account.balance,
account.currency,
userCurrency
),
name: account.name, name: account.name,
original: this.exchangeRateDataService.toCurrency( valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
account.balance, account.balance,
account.currency, account.currency,
userCurrency userCurrency
@ -1793,30 +1773,20 @@ export class PortfolioService {
(portfolioItemsNow[order.SymbolProfile.symbol]?.marketPrice ?? (portfolioItemsNow[order.SymbolProfile.symbol]?.marketPrice ??
order.unitPrice ?? order.unitPrice ??
0); 0);
let originalValueOfSymbolInBaseCurrency =
this.exchangeRateDataService.toCurrency(
order.quantity * order.unitPrice,
order.SymbolProfile.currency,
userCurrency
);
if (order.type === 'SELL') { if (order.type === 'SELL') {
currentValueOfSymbolInBaseCurrency *= -1; currentValueOfSymbolInBaseCurrency *= -1;
originalValueOfSymbolInBaseCurrency *= -1;
} }
if (accounts[order.Account?.id || UNKNOWN_KEY]?.current) { if (accounts[order.Account?.id || UNKNOWN_KEY]?.valueInBaseCurrency) {
accounts[order.Account?.id || UNKNOWN_KEY].current += accounts[order.Account?.id || UNKNOWN_KEY].valueInBaseCurrency +=
currentValueOfSymbolInBaseCurrency; currentValueOfSymbolInBaseCurrency;
accounts[order.Account?.id || UNKNOWN_KEY].original +=
originalValueOfSymbolInBaseCurrency;
} else { } else {
accounts[order.Account?.id || UNKNOWN_KEY] = { accounts[order.Account?.id || UNKNOWN_KEY] = {
balance: 0, balance: 0,
currency: order.Account?.currency, currency: order.Account?.currency,
current: currentValueOfSymbolInBaseCurrency,
name: account.name, name: account.name,
original: originalValueOfSymbolInBaseCurrency valueInBaseCurrency: currentValueOfSymbolInBaseCurrency
}; };
} }
} }

4
apps/api/src/models/rules/account-cluster-risk/current-investment.ts

@ -14,7 +14,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
private accounts: PortfolioDetails['accounts'] private accounts: PortfolioDetails['accounts']
) { ) {
super(exchangeRateDataService, { super(exchangeRateDataService, {
name: 'Current Investment' name: 'Investment'
}); });
} }
@ -28,7 +28,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
for (const [accountId, account] of Object.entries(this.accounts)) { for (const [accountId, account] of Object.entries(this.accounts)) {
accounts[accountId] = { accounts[accountId] = {
name: account.name, name: account.name,
investment: account.current investment: account.valueInBaseCurrency
}; };
} }

88
apps/api/src/models/rules/account-cluster-risk/initial-investment.ts

@ -1,88 +0,0 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import {
PortfolioDetails,
PortfolioPosition,
UserSettings
} from '@ghostfolio/common/interfaces';
import { Rule } from '../../rule';
export class AccountClusterRiskInitialInvestment extends Rule<Settings> {
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
private accounts: PortfolioDetails['accounts']
) {
super(exchangeRateDataService, {
name: 'Initial Investment'
});
}
public evaluate(ruleSettings?: Settings) {
const accounts: {
[symbol: string]: Pick<PortfolioPosition, 'name'> & {
investment: number;
};
} = {};
for (const [accountId, account] of Object.entries(this.accounts)) {
accounts[accountId] = {
name: account.name,
investment: account.original
};
}
let maxItem;
let totalInvestment = 0;
for (const account of Object.values(accounts)) {
if (!maxItem) {
maxItem = account;
}
// Calculate total investment
totalInvestment += account.investment;
// Find maximum
if (account.investment > maxItem?.investment) {
maxItem = account;
}
}
const maxInvestmentRatio = maxItem.investment / totalInvestment;
if (maxInvestmentRatio > ruleSettings.threshold) {
return {
evaluation: `Over ${
ruleSettings.threshold * 100
}% of your initial investment is at ${maxItem.name} (${(
maxInvestmentRatio * 100
).toPrecision(3)}%)`,
value: false
};
}
return {
evaluation: `The major part of your initial investment is at ${
maxItem.name
} (${(maxInvestmentRatio * 100).toPrecision(3)}%) and does not exceed ${
ruleSettings.threshold * 100
}%`,
value: true
};
}
public getSettings(aUserSettings: UserSettings): Settings {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true,
threshold: 0.5
};
}
}
interface Settings extends RuleSettings {
baseCurrency: string;
isActive: boolean;
threshold: number;
}

2
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -10,7 +10,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
private positions: TimelinePosition[] private positions: TimelinePosition[]
) { ) {
super(exchangeRateDataService, { super(exchangeRateDataService, {
name: 'Current Investment: Base Currency' name: 'Investment: Base Currency'
}); });
} }

71
apps/api/src/models/rules/currency-cluster-risk/base-currency-initial-investment.ts

@ -1,71 +0,0 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces';
import { Rule } from '../../rule';
export class CurrencyClusterRiskBaseCurrencyInitialInvestment extends Rule<Settings> {
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
private positions: TimelinePosition[]
) {
super(exchangeRateDataService, {
name: 'Initial Investment: Base Currency'
});
}
public evaluate(ruleSettings: Settings) {
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
this.positions,
'currency',
ruleSettings.baseCurrency
);
let maxItem = positionsGroupedByCurrency[0];
let totalInvestment = 0;
positionsGroupedByCurrency.forEach((groupItem) => {
// Calculate total investment
totalInvestment += groupItem.investment;
// Find maximum
if (groupItem.investment > maxItem.investment) {
maxItem = groupItem;
}
});
const baseCurrencyItem = positionsGroupedByCurrency.find((item) => {
return item.groupKey === ruleSettings.baseCurrency;
});
const baseCurrencyInvestmentRatio =
baseCurrencyItem?.investment / totalInvestment || 0;
if (maxItem.groupKey !== ruleSettings.baseCurrency) {
return {
evaluation: `The major part of your initial investment is not in your base currency (${(
baseCurrencyInvestmentRatio * 100
).toPrecision(3)}% in ${ruleSettings.baseCurrency})`,
value: false
};
}
return {
evaluation: `The major part of your initial investment is in your base currency (${(
baseCurrencyInvestmentRatio * 100
).toPrecision(3)}% in ${ruleSettings.baseCurrency})`,
value: true
};
}
public getSettings(aUserSettings: UserSettings): Settings {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true
};
}
}
interface Settings extends RuleSettings {
baseCurrency: string;
}

2
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -10,7 +10,7 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
private positions: TimelinePosition[] private positions: TimelinePosition[]
) { ) {
super(exchangeRateDataService, { super(exchangeRateDataService, {
name: 'Current Investment' name: 'Investment'
}); });
} }

72
apps/api/src/models/rules/currency-cluster-risk/initial-investment.ts

@ -1,72 +0,0 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { TimelinePosition, UserSettings } from '@ghostfolio/common/interfaces';
import { Rule } from '../../rule';
export class CurrencyClusterRiskInitialInvestment extends Rule<Settings> {
public constructor(
protected exchangeRateDataService: ExchangeRateDataService,
private positions: TimelinePosition[]
) {
super(exchangeRateDataService, {
name: 'Initial Investment'
});
}
public evaluate(ruleSettings: Settings) {
const positionsGroupedByCurrency = this.groupCurrentPositionsByAttribute(
this.positions,
'currency',
ruleSettings.baseCurrency
);
let maxItem = positionsGroupedByCurrency[0];
let totalInvestment = 0;
positionsGroupedByCurrency.forEach((groupItem) => {
// Calculate total investment
totalInvestment += groupItem.investment;
// Find maximum
if (groupItem.investment > maxItem.investment) {
maxItem = groupItem;
}
});
const maxInvestmentRatio = maxItem.investment / totalInvestment;
if (maxInvestmentRatio > ruleSettings.threshold) {
return {
evaluation: `Over ${
ruleSettings.threshold * 100
}% of your initial investment is in ${maxItem.groupKey} (${(
maxInvestmentRatio * 100
).toPrecision(3)}%)`,
value: false
};
}
return {
evaluation: `The major part of your initial investment is in ${
maxItem.groupKey
} (${(maxInvestmentRatio * 100).toPrecision(3)}%) and does not exceed ${
ruleSettings.threshold * 100
}%`,
value: true
};
}
public getSettings(aUserSettings: UserSettings): Settings {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true,
threshold: 0.5
};
}
}
interface Settings extends RuleSettings {
baseCurrency: string;
threshold: number;
}

2
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts

@ -11,7 +11,7 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
private fees: number private fees: number
) { ) {
super(exchangeRateDataService, { super(exchangeRateDataService, {
name: 'Initial Investment' name: 'Investment'
}); });
} }

17
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -249,13 +249,22 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
public initializeAnalysisData() { public initializeAnalysisData() {
this.initialize(); this.initialize();
for (const [id, { current, name }] of Object.entries( for (const [
this.portfolioDetails.accounts id,
)) { { name, valueInBaseCurrency, valueInPercentage }
] of Object.entries(this.portfolioDetails.accounts)) {
let value = 0;
if (this.hasImpersonationId) {
value = valueInPercentage;
} else {
value = valueInBaseCurrency;
}
this.accounts[id] = { this.accounts[id] = {
id, id,
name, name,
value: current value
}; };
} }

4
libs/common/src/lib/interfaces/portfolio-details.interface.ts

@ -8,9 +8,9 @@ export interface PortfolioDetails {
[id: string]: { [id: string]: {
balance: number; balance: number;
currency: string; currency: string;
current: number;
name: string; name: string;
original: number; valueInBaseCurrency: number;
valueInPercentage?: number;
}; };
}; };
filteredValueInBaseCurrency?: number; filteredValueInBaseCurrency?: number;

Loading…
Cancel
Save