mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
26 changed files with 319 additions and 487 deletions
@ -0,0 +1,5 @@ |
|||||
|
export interface RuleSettingsConfiguration { |
||||
|
thresholdMax?: number; |
||||
|
thresholdMin?: number; |
||||
|
withBaseCurrency?: boolean; |
||||
|
} |
||||
@ -1,9 +1,7 @@ |
|||||
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces'; |
import { RuleSettings } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
import { EvaluationResult } from './evaluation-result.interface'; |
import { EvaluationResult } from './evaluation-result.interface'; |
||||
|
|
||||
export interface RuleInterface<T extends RuleSettings> { |
export interface RuleInterface<T extends RuleSettings> { |
||||
evaluate(aRuleSettings: T): EvaluationResult; |
evaluate(aRuleSettings: T): EvaluationResult; |
||||
|
|
||||
getSettings(aUserSettings: UserSettings): T; |
|
||||
} |
} |
||||
|
|||||
@ -0,0 +1,129 @@ |
|||||
|
import { getRuleSettings, getXRayRulesSettings } from './rule-settings'; |
||||
|
|
||||
|
describe('Rule settings', () => { |
||||
|
it('gets default settings for a rule', () => { |
||||
|
expect( |
||||
|
getRuleSettings({ |
||||
|
key: 'AssetClassClusterRiskEquity', |
||||
|
userSettings: {} |
||||
|
}) |
||||
|
).toEqual({ |
||||
|
baseCurrency: 'USD', |
||||
|
isActive: true, |
||||
|
locale: 'en-US', |
||||
|
thresholdMax: 0.82, |
||||
|
thresholdMin: 0.78 |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('gets a default threshold of zero for a rule', () => { |
||||
|
expect( |
||||
|
getRuleSettings({ |
||||
|
key: 'BuyingPower', |
||||
|
userSettings: {} |
||||
|
}) |
||||
|
).toEqual({ |
||||
|
baseCurrency: 'USD', |
||||
|
isActive: true, |
||||
|
locale: 'en-US', |
||||
|
thresholdMin: 0 |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('applies the configured settings for a rule', () => { |
||||
|
expect( |
||||
|
getRuleSettings({ |
||||
|
key: 'AssetClassClusterRiskEquity', |
||||
|
userSettings: { |
||||
|
baseCurrency: 'CHF', |
||||
|
locale: 'de-CH', |
||||
|
xRayRules: { |
||||
|
AssetClassClusterRiskEquity: { |
||||
|
isActive: false, |
||||
|
thresholdMax: 0.9, |
||||
|
thresholdMin: 0.7 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
).toEqual({ |
||||
|
baseCurrency: 'CHF', |
||||
|
isActive: false, |
||||
|
locale: 'de-CH', |
||||
|
thresholdMax: 0.9, |
||||
|
thresholdMin: 0.7 |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('applies the default for a threshold which is not configured', () => { |
||||
|
expect( |
||||
|
getRuleSettings({ |
||||
|
key: 'AssetClassClusterRiskEquity', |
||||
|
userSettings: { |
||||
|
xRayRules: { |
||||
|
AssetClassClusterRiskEquity: { isActive: true, thresholdMax: 0.9 } |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
).toEqual({ |
||||
|
baseCurrency: 'USD', |
||||
|
isActive: true, |
||||
|
locale: 'en-US', |
||||
|
thresholdMax: 0.9, |
||||
|
thresholdMin: 0.78 |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('applies a configured threshold of zero', () => { |
||||
|
expect( |
||||
|
getRuleSettings({ |
||||
|
key: 'AssetClassClusterRiskEquity', |
||||
|
userSettings: { |
||||
|
xRayRules: { |
||||
|
AssetClassClusterRiskEquity: { isActive: true, thresholdMin: 0 } |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
).toEqual({ |
||||
|
baseCurrency: 'USD', |
||||
|
isActive: true, |
||||
|
locale: 'en-US', |
||||
|
thresholdMax: 0.82, |
||||
|
thresholdMin: 0 |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('omits the base currency when it is not used by a rule', () => { |
||||
|
expect( |
||||
|
getRuleSettings({ |
||||
|
key: 'AccountClusterRiskSingleAccount', |
||||
|
userSettings: {} |
||||
|
}) |
||||
|
).toEqual({ |
||||
|
isActive: true, |
||||
|
locale: 'en-US' |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
it('gets settings for all rules', () => { |
||||
|
expect(Object.keys(getXRayRulesSettings({})).sort()).toEqual([ |
||||
|
'AccountClusterRiskCurrentInvestment', |
||||
|
'AccountClusterRiskSingleAccount', |
||||
|
'AssetClassClusterRiskEquity', |
||||
|
'AssetClassClusterRiskFixedIncome', |
||||
|
'BuyingPower', |
||||
|
'CurrencyClusterRiskBaseCurrencyCurrentInvestment', |
||||
|
'CurrencyClusterRiskCurrentInvestment', |
||||
|
'EconomicMarketClusterRiskDevelopedMarkets', |
||||
|
'EconomicMarketClusterRiskEmergingMarkets', |
||||
|
'EmergencyFundCoverage', |
||||
|
'EmergencyFundSetup', |
||||
|
'FeeRatioTotalInvestmentVolume', |
||||
|
'RegionalMarketClusterRiskAsiaPacific', |
||||
|
'RegionalMarketClusterRiskEmergingMarkets', |
||||
|
'RegionalMarketClusterRiskEurope', |
||||
|
'RegionalMarketClusterRiskJapan', |
||||
|
'RegionalMarketClusterRiskNorthAmerica' |
||||
|
]); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,98 @@ |
|||||
|
import { RuleSettingsConfiguration } from '@ghostfolio/api/models/interfaces/rule-settings-configuration.interface'; |
||||
|
import { XRayRuleKey } from '@ghostfolio/api/models/types/x-ray-rule-key.type'; |
||||
|
import { DEFAULT_CURRENCY, DEFAULT_LOCALE } from '@ghostfolio/common/config'; |
||||
|
import { |
||||
|
RuleSettings, |
||||
|
UserSettings, |
||||
|
XRayRulesSettings |
||||
|
} from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
const RULE_SETTINGS_CONFIGURATIONS = { |
||||
|
AccountClusterRiskCurrentInvestment: { thresholdMax: 0.5 }, |
||||
|
AccountClusterRiskSingleAccount: { withBaseCurrency: false }, |
||||
|
AssetClassClusterRiskEquity: { |
||||
|
thresholdMax: 0.82, |
||||
|
thresholdMin: 0.78 |
||||
|
}, |
||||
|
AssetClassClusterRiskFixedIncome: { |
||||
|
thresholdMax: 0.22, |
||||
|
thresholdMin: 0.18 |
||||
|
}, |
||||
|
BuyingPower: { thresholdMin: 0 }, |
||||
|
CurrencyClusterRiskBaseCurrencyCurrentInvestment: {}, |
||||
|
CurrencyClusterRiskCurrentInvestment: { thresholdMax: 0.5 }, |
||||
|
EconomicMarketClusterRiskDevelopedMarkets: { |
||||
|
thresholdMax: 0.72, |
||||
|
thresholdMin: 0.68 |
||||
|
}, |
||||
|
EconomicMarketClusterRiskEmergingMarkets: { |
||||
|
thresholdMax: 0.32, |
||||
|
thresholdMin: 0.28 |
||||
|
}, |
||||
|
EmergencyFundCoverage: {}, |
||||
|
EmergencyFundSetup: {}, |
||||
|
FeeRatioTotalInvestmentVolume: { thresholdMax: 0.01 }, |
||||
|
RegionalMarketClusterRiskAsiaPacific: { |
||||
|
thresholdMax: 0.03, |
||||
|
thresholdMin: 0.02 |
||||
|
}, |
||||
|
RegionalMarketClusterRiskEmergingMarkets: { |
||||
|
thresholdMax: 0.12, |
||||
|
thresholdMin: 0.08 |
||||
|
}, |
||||
|
RegionalMarketClusterRiskEurope: { |
||||
|
thresholdMax: 0.15, |
||||
|
thresholdMin: 0.11 |
||||
|
}, |
||||
|
RegionalMarketClusterRiskJapan: { |
||||
|
thresholdMax: 0.06, |
||||
|
thresholdMin: 0.04 |
||||
|
}, |
||||
|
RegionalMarketClusterRiskNorthAmerica: { |
||||
|
thresholdMax: 0.69, |
||||
|
thresholdMin: 0.65 |
||||
|
} |
||||
|
} as const satisfies Record<XRayRuleKey, RuleSettingsConfiguration>; |
||||
|
|
||||
|
export function getRuleSettings<T extends RuleSettings>({ |
||||
|
key, |
||||
|
userSettings |
||||
|
}: { |
||||
|
key: XRayRuleKey; |
||||
|
userSettings: UserSettings; |
||||
|
}): T { |
||||
|
const configuration: RuleSettingsConfiguration = |
||||
|
RULE_SETTINGS_CONFIGURATIONS[key] ?? {}; |
||||
|
const configuredSettings = userSettings.xRayRules?.[key]; |
||||
|
|
||||
|
return { |
||||
|
...(configuration.withBaseCurrency === false |
||||
|
? {} |
||||
|
: { baseCurrency: userSettings.baseCurrency ?? DEFAULT_CURRENCY }), |
||||
|
isActive: configuredSettings?.isActive ?? true, |
||||
|
locale: userSettings.locale ?? DEFAULT_LOCALE, |
||||
|
...(configuration.thresholdMax === undefined |
||||
|
? {} |
||||
|
: { |
||||
|
thresholdMax: |
||||
|
configuredSettings?.thresholdMax ?? configuration.thresholdMax |
||||
|
}), |
||||
|
...(configuration.thresholdMin === undefined |
||||
|
? {} |
||||
|
: { |
||||
|
thresholdMin: |
||||
|
configuredSettings?.thresholdMin ?? configuration.thresholdMin |
||||
|
}) |
||||
|
} as T; |
||||
|
} |
||||
|
|
||||
|
export function getXRayRulesSettings( |
||||
|
userSettings: UserSettings |
||||
|
): XRayRulesSettings { |
||||
|
return Object.fromEntries( |
||||
|
(Object.keys(RULE_SETTINGS_CONFIGURATIONS) as XRayRuleKey[]).map((key) => [ |
||||
|
key, |
||||
|
getRuleSettings({ key, userSettings }) |
||||
|
]) |
||||
|
) as XRayRulesSettings; |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
import { XRayRulesSettings } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
export type XRayRuleKey = keyof XRayRulesSettings; |
||||
Loading…
Reference in new issue