Feature/clean up initial values from x ray (#1914)
* Clean up initial (original) values from X-Ray * Refactor current to valueInBaseCurrency * Update changelog
This commit is contained in:
parent
90fe467114
commit
1ca3792a4b
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
|
||||
- Deprecated the use of the environment variable `BASE_CURRENCY`
|
||||
- Cleaned up initial values from the _X-ray_ section
|
||||
|
||||
## 1.263.0 - 2023-04-30
|
||||
|
||||
|
@ -136,9 +136,8 @@ export class PortfolioController {
|
||||
portfolioPosition.value / totalValue;
|
||||
}
|
||||
|
||||
for (const [name, { current, original }] of Object.entries(accounts)) {
|
||||
accounts[name].current = current / totalValue;
|
||||
accounts[name].original = original / totalInvestment;
|
||||
for (const [name, { valueInBaseCurrency }] of Object.entries(accounts)) {
|
||||
accounts[name].valueInPercentage = valueInBaseCurrency / totalValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||
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 { 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 { CurrencyClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/initial-investment';
|
||||
import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.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 = {
|
||||
...account,
|
||||
@ -618,9 +616,8 @@ export class PortfolioService {
|
||||
accounts[UNKNOWN_KEY] = {
|
||||
balance: 0,
|
||||
currency: userCurrency,
|
||||
current: emergencyFundInCash,
|
||||
name: UNKNOWN_KEY,
|
||||
original: emergencyFundInCash
|
||||
valueInBaseCurrency: emergencyFundInCash
|
||||
};
|
||||
|
||||
holdings[userCurrency] = {
|
||||
@ -1185,10 +1182,6 @@ export class PortfolioService {
|
||||
rules: {
|
||||
accountClusterRisk: await this.rulesService.evaluate(
|
||||
[
|
||||
new AccountClusterRiskInitialInvestment(
|
||||
this.exchangeRateDataService,
|
||||
accounts
|
||||
),
|
||||
new AccountClusterRiskCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
accounts
|
||||
@ -1202,18 +1195,10 @@ export class PortfolioService {
|
||||
),
|
||||
currencyClusterRisk: await this.rulesService.evaluate(
|
||||
[
|
||||
new CurrencyClusterRiskBaseCurrencyInitialInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
),
|
||||
new CurrencyClusterRiskBaseCurrencyCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
),
|
||||
new CurrencyClusterRiskInitialInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
),
|
||||
new CurrencyClusterRiskCurrentInvestment(
|
||||
this.exchangeRateDataService,
|
||||
positions
|
||||
@ -1774,13 +1759,8 @@ export class PortfolioService {
|
||||
accounts[account.id] = {
|
||||
balance: account.balance,
|
||||
currency: account.currency,
|
||||
current: this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
userCurrency
|
||||
),
|
||||
name: account.name,
|
||||
original: this.exchangeRateDataService.toCurrency(
|
||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
userCurrency
|
||||
@ -1793,30 +1773,20 @@ export class PortfolioService {
|
||||
(portfolioItemsNow[order.SymbolProfile.symbol]?.marketPrice ??
|
||||
order.unitPrice ??
|
||||
0);
|
||||
let originalValueOfSymbolInBaseCurrency =
|
||||
this.exchangeRateDataService.toCurrency(
|
||||
order.quantity * order.unitPrice,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
);
|
||||
|
||||
if (order.type === 'SELL') {
|
||||
currentValueOfSymbolInBaseCurrency *= -1;
|
||||
originalValueOfSymbolInBaseCurrency *= -1;
|
||||
}
|
||||
|
||||
if (accounts[order.Account?.id || UNKNOWN_KEY]?.current) {
|
||||
accounts[order.Account?.id || UNKNOWN_KEY].current +=
|
||||
if (accounts[order.Account?.id || UNKNOWN_KEY]?.valueInBaseCurrency) {
|
||||
accounts[order.Account?.id || UNKNOWN_KEY].valueInBaseCurrency +=
|
||||
currentValueOfSymbolInBaseCurrency;
|
||||
accounts[order.Account?.id || UNKNOWN_KEY].original +=
|
||||
originalValueOfSymbolInBaseCurrency;
|
||||
} else {
|
||||
accounts[order.Account?.id || UNKNOWN_KEY] = {
|
||||
balance: 0,
|
||||
currency: order.Account?.currency,
|
||||
current: currentValueOfSymbolInBaseCurrency,
|
||||
name: account.name,
|
||||
original: originalValueOfSymbolInBaseCurrency
|
||||
valueInBaseCurrency: currentValueOfSymbolInBaseCurrency
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||
private accounts: PortfolioDetails['accounts']
|
||||
) {
|
||||
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)) {
|
||||
accounts[accountId] = {
|
||||
name: account.name,
|
||||
investment: account.current
|
||||
investment: account.valueInBaseCurrency
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -10,7 +10,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
|
||||
private positions: TimelinePosition[]
|
||||
) {
|
||||
super(exchangeRateDataService, {
|
||||
name: 'Current Investment: Base Currency'
|
||||
name: 'Investment: Base Currency'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -10,7 +10,7 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
|
||||
private positions: TimelinePosition[]
|
||||
) {
|
||||
super(exchangeRateDataService, {
|
||||
name: 'Current Investment'
|
||||
name: 'Investment'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -11,7 +11,7 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
|
||||
private fees: number
|
||||
) {
|
||||
super(exchangeRateDataService, {
|
||||
name: 'Initial Investment'
|
||||
name: 'Investment'
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -249,13 +249,22 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
public initializeAnalysisData() {
|
||||
this.initialize();
|
||||
|
||||
for (const [id, { current, name }] of Object.entries(
|
||||
this.portfolioDetails.accounts
|
||||
)) {
|
||||
for (const [
|
||||
id,
|
||||
{ name, valueInBaseCurrency, valueInPercentage }
|
||||
] of Object.entries(this.portfolioDetails.accounts)) {
|
||||
let value = 0;
|
||||
|
||||
if (this.hasImpersonationId) {
|
||||
value = valueInPercentage;
|
||||
} else {
|
||||
value = valueInBaseCurrency;
|
||||
}
|
||||
|
||||
this.accounts[id] = {
|
||||
id,
|
||||
name,
|
||||
value: current
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,9 @@ export interface PortfolioDetails {
|
||||
[id: string]: {
|
||||
balance: number;
|
||||
currency: string;
|
||||
current: number;
|
||||
name: string;
|
||||
original: number;
|
||||
valueInBaseCurrency: number;
|
||||
valueInPercentage?: number;
|
||||
};
|
||||
};
|
||||
filteredValueInBaseCurrency?: number;
|
||||
|
Loading…
x
Reference in New Issue
Block a user