def test_recommend_risk_fully_answered_bad_questions(self): # Fully populate the answers, but no range in the available question responses, we should get 0.5 goal = Fixture1.goal1() settings = Fixture1.settings1() account = settings.goal.account Fixture1.populate_risk_profile_questions() # Also populates all possible answers. Fixture1.populate_risk_profile_responses() Fixture1.risk_profile_question3() # Add a question we don't have an answer for self.assertEqual(recommend_risk(settings), MINIMUM_RISK) # Now answer the question, we shouldn't get MINIMUM_RISK account.primary_owner.risk_profile_responses.add(Fixture1.risk_profile_answer3a()) self.assertNotEqual(recommend_risk(settings), MINIMUM_RISK)
def test_recommend_risk_fully_answered(self): # Fully populate the answers, we should get 0.5 goal = Fixture1.goal1() settings = Fixture1.settings1() Fixture1.populate_risk_profile_questions() # Also populates all possible answers. Fixture1.populate_risk_profile_responses() self.assertEqual(recommend_risk(settings), 1.0)
def test_recommend_risk_fully_unanswered(self): # Populate the questions, we should still get 0.5 goal = Fixture1.goal1() settings = Fixture1.settings1() account = settings.goal.account Fixture1.populate_risk_profile_questions() self.assertEqual(recommend_risk(settings), MINIMUM_RISK)
def test_max_risk(self): goal = Fixture1.goal1() settings = Fixture1.settings1() client = goal.account.primary_owner # Add the weights for the risk factors Fixture1.populate_risk_profile_questions() # Also populates all possible answers. Fixture1.populate_risk_profile_responses() # we haven't set a net worth or a target, so worth_score isn't a factor # An all-9s account will have a max_risk of 1 self.assertEqual(max_risk(settings), 1.0) # and if they are low risk behavior, high Ability + Sophistication # the max risk is still 1 client.risk_profile_responses.clear() client.risk_profile_responses.add(Fixture1.risk_profile_answer1c()) client.risk_profile_responses.add(Fixture1.risk_profile_answer2c()) self.assertEqual(max_risk(settings), 1.0) # but if they are risky, new and unskilled, recommend no risk client.risk_profile_responses.clear() client.risk_profile_responses.add(Fixture1.risk_profile_answer1d()) client.risk_profile_responses.add(Fixture1.risk_profile_answer2d()) self.assertAlmostEqual(recommend_risk(settings), 0.1, 1) self.assertAlmostEqual(max_risk(settings), 0.1, 1)
def test_recommend_risk(self): goal = Fixture1.goal1() settings = Fixture1.settings1() client = goal.account.primary_owner # Add the weights for the risk factors Fixture1.populate_risk_profile_questions() # Also populates all possible answers. Fixture1.populate_risk_profile_responses() # First lets start with the test_client, who scored 9 for all B,A,S # A goal of 80% of the value on a all-9s account is a bad idea # It's the lowest possible score settings.goal.account.primary_owner.net_worth = 100 settings.goal.cash_balance = 80 self.assertAlmostEqual(recommend_risk(settings), 0.10, 2) # A goal of 50% of the value is just as bad settings.goal.account.primary_owner.net_worth = 100 settings.goal.cash_balance = 50 self.assertAlmostEqual(recommend_risk(settings), 0.10, 2) # A goal of 10% of the value on a all-9s account is 1.0 # meaning this is the safest possible bet settings.goal.account.primary_owner.net_worth = 100 settings.goal.cash_balance = 10 self.assertAlmostEqual(recommend_risk(settings), 1.0, 2) # A goal of 33% of the value on a all-9s account is about 0.5 # Even if you are risky, sophisticated and rich, 30% is a lot settings.goal.account.primary_owner.net_worth = 100 settings.goal.cash_balance = 33 self.assertAlmostEqual(recommend_risk(settings), 0.5, 1) # For a new investor, the best possible suggestion is 10% or less settings.goal.account.primary_owner.net_worth = 100 settings.goal.cash_balance = 10 client.risk_profile_responses.clear() client.risk_profile_responses.add(Fixture1.risk_profile_answer1b()) client.risk_profile_responses.add(Fixture1.risk_profile_answer2b()) self.assertAlmostEqual(recommend_risk(settings), 0.2, 1)
def test_fully_answered_zero_max(self): goal = Fixture1.goal1() setting = Fixture1.settings1() risk_metric = setting.metric_group.metrics.get(type=GoalMetric.METRIC_TYPE_RISK_SCORE) q = RiskProfileQuestionFactory.create(group=goal.account.primary_owner.risk_profile_group) a = RiskProfileAnswerFactory.create(b_score=0, a_score=0, s_score=0, question=q) client = goal.account.primary_owner client.risk_profile_responses.add(a) self.assertGreater(risk_metric.configured_val, 0.01) with self.assertRaises(ValidationError) as ex: validate_risk_score(setting) risk_metric.configured_val = 0 risk_metric.save() self.assertEqual(validate_risk_score(setting), None) # Should now complete OK. self.assertEqual(max_risk(setting), 0.0) self.assertEqual(recommend_risk(setting), 0.0)
def create(self, validated_data): """ Override the default create because we need to generate a portfolio. :param validated_data: :return: The created Goal """ account = validated_data['account'] with transaction.atomic(): metric_group = GoalMetricGroup.objects.create( type=GoalMetricGroup.TYPE_CUSTOM) settings = GoalSetting.objects.create( target=validated_data['target'], completion=validated_data['completion'], hedge_fx=False, metric_group=metric_group, ) portfolio_set = validated_data[ 'portfolio_set'] if 'portfolio_set' in validated_data else account.default_portfolio_set goal = Goal.objects.create( account=account, name=validated_data['name'], type=validated_data['type'], portfolio_set=portfolio_set, selected_settings=settings, ) # Based on the risk profile, and whether an ethical profile was specified on creation, set up Metrics. recommended_risk = recommend_risk(settings) GoalMetric.objects.create( group=metric_group, type=GoalMetric.METRIC_TYPE_RISK_SCORE, comparison=GoalMetric.METRIC_COMPARISON_EXACTLY, rebalance_type=GoalMetric.REBALANCE_TYPE_ABSOLUTE, rebalance_thr=0.05, configured_val=recommended_risk) if validated_data['ethical']: GoalMetric.objects.create( group=metric_group, type=GoalMetric.METRIC_TYPE_PORTFOLIO_MIX, feature=AssetFeatureValue.Standard.SRI_OTHER.get_object(), comparison=GoalMetric.METRIC_COMPARISON_EXACTLY, rebalance_type=GoalMetric.REBALANCE_TYPE_ABSOLUTE, rebalance_thr=0.05, configured_val=1 # Start with 100% ethical. ) # Make sure the risk score assigned is appropriate for the goal. firm_config = account.primary_owner.firm.config unlimited = firm_config.risk_score_unlimited try: validate_risk_score(settings, unlimited) except CVE as verr: raise ValidationError(verr.message) # Add the initial deposit if specified. initial_dep = validated_data.pop('initial_deposit', None) if initial_dep is not None: Transaction.objects.create(reason=Transaction.REASON_DEPOSIT, to_goal=goal, amount=initial_dep) # Calculate the optimised portfolio goal.calculate_portfolio_for_selected() return goal
def test_recommend_risk_no_weights(self): goal = Fixture1.goal1() settings = Fixture1.settings1() self.assertEqual(recommend_risk(settings), MINIMUM_RISK)
def test_recommend_risk_no_questions(self): goal = Fixture1.goal1() settings = Fixture1.settings1() account = settings.goal.account self.assertEqual(recommend_risk(settings), MINIMUM_RISK)
def create(self, validated_data): """ Override the default create because we need to generate a portfolio. :param validated_data: :return: The created Goal """ account = validated_data['account'] data_provider = DataProviderDjango() execution_provider = ExecutionProviderDjango() idata = get_instruments(data_provider) with transaction.atomic(): metric_group = GoalMetricGroup.objects.create( type=GoalMetricGroup.TYPE_CUSTOM) settings = GoalSetting.objects.create( target=validated_data['target'], completion=validated_data['completion'], hedge_fx=False, metric_group=metric_group, ) portfolio_provider_id = validated_data[ 'portfolio_provider'] if 'portfolio_provider' in validated_data else get_default_provider_id( ) portfolio_set_id = '' portfolio_providers = PortfolioProvider.objects.all() portfolio_provider = get_default_provider() for pp in portfolio_providers: if pp.id == portfolio_provider_id: portfolio_provider = pp if portfolio_provider.type == constants.PORTFOLIO_PROVIDER_TYPE_KRANE: portfolio_set_type = constants.PORTFOLIO_SET_TYPE_KRANE elif portfolio_provider.type == constants.PORTFOLIO_PROVIDER_TYPE_AON: portfolio_set_type = constants.PORTFOLIO_SET_TYPE_AON elif portfolio_provider.type == constants.PORTFOLIO_PROVIDER_TYPE_LEE: portfolio_set_type = constants.PORTFOLIO_SET_TYPE_LEE else: raise Exception('unhandled portfolio_provider_id') portfolio_sets = PortfolioSet.objects.all() portfolio_set = account.default_portfolio_set for ps in portfolio_sets: if ps.type == portfolio_set_type: portfolio_set = ps goal = Goal.objects.create( account=account, name=validated_data['name'], type=validated_data['type'], portfolio_set=portfolio_set, portfolio_provider=portfolio_provider, selected_settings=settings, ) # Based on the risk profile, and whether an ethical profile was specified on creation, set up Metrics. recommended_risk = recommend_risk(settings) GoalMetric.objects.create( group=metric_group, type=GoalMetric.METRIC_TYPE_RISK_SCORE, comparison=GoalMetric.METRIC_COMPARISON_EXACTLY, rebalance_type=GoalMetric.REBALANCE_TYPE_ABSOLUTE, rebalance_thr=0.05, configured_val=recommended_risk) if validated_data['ethical']: GoalMetric.objects.create( group=metric_group, type=GoalMetric.METRIC_TYPE_PORTFOLIO_MIX, feature=AssetFeatureValue.Standard.SRI_OTHER.get_object(), comparison=GoalMetric.METRIC_COMPARISON_EXACTLY, rebalance_type=GoalMetric.REBALANCE_TYPE_ABSOLUTE, rebalance_thr=0.05, configured_val=1 # Start with 100% ethical. ) # Make sure the risk score assigned is appropriate for the goal. try: validate_risk_score(settings) except CVE as verr: raise ValidationError(verr.message) # Add the initial deposit if specified. initial_dep = validated_data.pop('initial_deposit', None) if initial_dep is not None: Transaction.objects.create(reason=Transaction.REASON_DEPOSIT, to_goal=goal, amount=initial_dep) # Calculate the optimised portfolio try: weights, er, stdev = calculate_portfolio( settings, data_provider, execution_provider, True, idata) portfolio = Portfolio.objects.create( setting=settings, stdev=stdev, er=er, ) items = [ PortfolioItem(portfolio=portfolio, asset=Ticker.objects.get(id=tid), weight=weight, volatility=idata[0].loc[tid, tid]) for tid, weight in weights.iteritems() ] PortfolioItem.objects.bulk_create(items) except Unsatisfiable: # We detect when loading a goal in the allocation screen if there has been no portfolio created # and return a message to the user. It it perfectly reasonable for a goal to be created without a # portfolio. logger.exception( "No suitable portfolio could be found. Leaving empty.") return goal