def test_update_portfolio_too_small(self): security_recommendation = SecurityRecommendationSet.from_dict( self.sr_dict) portfolio = Portfolio() portfolio.create_empty_portfolio(security_recommendation) with self.assertRaises(ValidationError): portfolio_mgr_svc.update_portfolio( portfolio, security_recommendation, 0)
def test_create_empty_portfolio_valid(self): with patch.object(intrinio_data, 'get_latest_close_price', return_value=('2019-08-31', 123.45)): recommendation_set = SecurityRecommendationSet.from_dict( self.sr_dict) portfolio = Portfolio() portfolio.create_empty_portfolio(recommendation_set)
def test_create_empty_portfolio_invalid_intrinio_response(self): with patch.object(intrinio_data, 'get_latest_close_price', return_value=('aaaa', 123.45)): recommendation_set = SecurityRecommendationSet.from_dict( self.sr_dict) with self.assertRaises(ValidationError): portfolio = Portfolio() portfolio.create_empty_portfolio(recommendation_set)
def test_create_empty_portfolio_no_prices(self): with patch.object(intrinio_data, 'get_latest_close_price', side_effect=DataError("test exception", None)): recommendation_set = SecurityRecommendationSet.from_dict( self.sr_dict) with self.assertRaises(DataError): portfolio = Portfolio() portfolio.create_empty_portfolio(recommendation_set)
def test_publish_current_returns(self): security_recommendation = SecurityRecommendationSet.from_dict( self.sr_dict) portfolio = Portfolio() portfolio.create_empty_portfolio(security_recommendation) (new_p, updated) = portfolio_mgr_svc.update_portfolio( portfolio, security_recommendation, 1) with patch.object(aws_service_wrapper, 'sns_publish_notification', side_effect=AWSError("", None)), \ patch.object(aws_service_wrapper, 'cf_read_export_value', return_value="some_value"): with self.assertRaises(AWSError): portfolio_mgr_svc.publish_current_returns(new_p, updated, 'sa')
def test_update_portfolio_too_big(self): security_recommendation = SecurityRecommendationSet.from_dict( self.sr_dict) portfolio = Portfolio() portfolio.create_empty_portfolio(security_recommendation) (new_p, updated) = portfolio_mgr_svc.update_portfolio( portfolio, security_recommendation, 100) ''' ensure that portfolio contains all securitie from the recommendation set ''' self.assertTrue(updated) self.assertEqual(len(new_p.model['current_portfolio'][ 'securities']), len(security_recommendation.model['securities_set'])) self.assertEqual(len(new_p.model['securities_set']), 0)
def test_update_portfolio_empty_portfolio(self): security_recommendation = SecurityRecommendationSet.from_dict( self.sr_dict) portfolio = Portfolio() portfolio.create_empty_portfolio(security_recommendation) (new_p, updated) = portfolio_mgr_svc.update_portfolio( portfolio, security_recommendation, 1) ''' ensure that 1) portfolio is updated 2) portfolio contains 1 security 3) portfolio contains nothing in the security set ''' self.assertTrue(updated) self.assertEqual( len(new_p.model['current_portfolio']['securities']), 1) self.assertEqual(len(new_p.model['securities_set']), len( security_recommendation.model['securities_set']) - 1)
def test_portfolio_not_empty(self): with patch.object(intrinio_data, 'get_latest_close_price', return_value=('2019-08-31', 123.45)): recommendation_set = SecurityRecommendationSet.from_dict( self.sr_dict) portfolio = Portfolio() portfolio.create_empty_portfolio(recommendation_set) portfolio.model['current_portfolio'] = { 'securities': [{ "ticker_symbol": "ABC", "purchase_date": "2019-09-01T02:34:12.876012+00:00", "purchase_price": 100, "current_price": 200, "trade_state": "FILLED", "order_id": None }] } self.assertFalse(portfolio.is_empty())
def main(): """ Main function of this script """ try: #(app_ns, portfolio_size) = parse_params() (app_ns, portfolio_size) = ('sa', 3) log.info("Application Parameters") log.info("-app_namespace: %s" % app_ns) log.info("-portfolio_size: %d" % portfolio_size) # test all connectivity upfront, so if there any issues # the problem becomes more apparent connector_test.test_all_connectivity() (current_portfolio, security_recommendation) = portfolio_mgr_svc.get_service_inputs(app_ns) log.info("Loaded recommendation set id: %s" % security_recommendation.model['set_id']) if current_portfolio is None: log.info("Creating new portfolio") current_portfolio = Portfolio(None) current_portfolio.create_empty_portfolio(security_recommendation) else: log.info("Repricing portfolio") current_portfolio.reprice(datetime.now()) (updated_portfolio, updated) = portfolio_mgr_svc.update_portfolio( current_portfolio, security_recommendation, portfolio_size) # See if there is anything that needs to be traded market_open = td_ameritrade.equity_market_open(datetime.now()) if market_open == True: broker = Broker() broker.cancel_all_open_orders() log.info("Market is open. Looking for trading opportunities") current_positions = td_ameritrade.positions_summary() try: if broker.reconcile_portfolio(current_positions, updated_portfolio) == False: log.info( "Portfolio is not in sync with brokerage account positions. Positions will be rebalanced") broker.materialize_portfolio( current_positions, updated_portfolio) finally: updated_positions = td_ameritrade.positions_summary() broker.synchronize_portfolio( updated_positions, updated_portfolio) updated_portfolio.recalc_returns() broker.cancel_all_open_orders() else: log.info("Market is closed. Nothing to trade") log.info("updated portfolio: %s" % util.format_dict(updated_portfolio.to_dict())) log.info("Saving updated portfolio") updated_portfolio.save_to_s3( app_ns, constants.S3_PORTFOLIO_OBJECT_NAME) portfolio_mgr_svc.publish_current_returns( updated_portfolio, updated, app_ns) except Exception as e: stack_trace = traceback.format_exc() log.error("Could run script, because: %s" % (str(e))) log.error(stack_trace) aws_service_wrapper.notify_error(e, "Portfolio Manager Service", stack_trace, app_ns)
def update_portfolio(current_portfolio: object, recommendation_set: object, portfolio_size: int): ''' Updates the portolio based on the recommendation set. 1) When a portfolio is empty, then create a new one 2) When a portfolio is stale (based on an old recommendation), rebalance it. 3) Else, do nothing. Returns ------- A tuple containing the updated portfolio (copy) and a boolean flag indicating whether positions were updated (and should be traded) ''' def select_random_portfolio(portfolio_size: int): ''' selects a randmon subset of the security set and creates a portfolio out out it. ''' security_set = updated_portfolio.model['securities_set'] # portfolio size cannot be larger than the available securitues # in the recommendation if len(security_set) < portfolio_size: portfolio_size = len(security_set) if not 'current_portfolio' in updated_portfolio.model: updated_portfolio.model['current_portfolio'] = {} updated_portfolio.model['current_portfolio']['securities'] = [] for _ in range(0, portfolio_size): random_security = random.choice(security_set) updated_portfolio.model['current_portfolio']['securities'].append({ "ticker_symbol": random_security['ticker_symbol'], "quantity": 0, "purchase_date": None, "purchase_price": 0, "current_price": random_security['current_price'], "current_returns": 0, "trade_state": "UNFILLED", "order_id": None } ) security_set.remove(random_security) if portfolio_size <= 0: raise ValidationError("Portfolio Size must be a positive number", None) updated_portfolio = current_portfolio.copy() updated = False pfolio_set_id = current_portfolio.model['set_id'] rec_set_id = recommendation_set.model['set_id'] if current_portfolio.is_empty(): log.info("Portfolio is empty, selecting a new one") select_random_portfolio(portfolio_size) updated = True elif pfolio_set_id != rec_set_id: log.info("Recommendation set has changed, rebalancing portfolio") updated_portfolio = Portfolio() updated_portfolio.create_empty_portfolio(recommendation_set) select_random_portfolio(portfolio_size) updated = True else: log.info("Portfolio is still current. No rebalancing necessary") updated_portfolio.validate_model() return (updated_portfolio, updated)