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'; |
|||
|
|||
export interface RuleInterface<T extends RuleSettings> { |
|||
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