def test_valid_dict(self): recommendation_set_dict = { "set_id": "1430b59a-5b79-11ea-8e96-acbc329ef75f", "creation_date": "2020-09-01T04:56:57.612693+00:00", "valid_from": "2019-08-01", "valid_to": "2019-08-31", "price_date": "2019-09-01", "strategy_name": "PRICE_DISPERSION", "security_type": "US Equities", "securities_set": [{ "ticker_symbol": "GE", "price": 123.45 }, { "ticker_symbol": "INTC", "price": 123.45 }, { "ticker_symbol": "AAPL", "price": 123.45 }] } SecurityRecommendationSet.from_dict(recommendation_set_dict)
def test_invalid_dict_2(self): recommendation_set_dict = { "set_id": "1430b59a-5b79-11ea-8e96-acbc329ef75f", "creation_date": "2020-09-01T04:56:57.612693+00:00", "valid_from": "2019-08-01", "price_date": "2019-09-01", "strategy_name": "PRICE_DISPERSION", "security_type": "US Equities" } with self.assertRaises(ValidationError): SecurityRecommendationSet.from_dict(recommendation_set_dict)
def get_service_inputs(app_ns: str): ''' Returns the required inputs for the recommendation service given the application namespace used to identify the appropriate cloud resources. Returns ---------- A tuple containing the latest SecurityRecommendationSet and Portfolio objects. If a portfolio does not exist, it will create a new one. ''' log.info("Loading latest recommendations") recommendation_set = SecurityRecommendationSet.from_s3(app_ns) if not recommendation_set.is_current(datetime.now()): raise ValidationError("Current recommendation set is not valid", None) try: log.info("Loading current portfolio") pfolio = Portfolio.from_s3(app_ns) except AWSError as e: if e.resource_not_found(): pfolio = None else: raise e return (pfolio, recommendation_set)
def get_service_inputs(app_ns: str): ''' Returns the required inputs for the recommendation service given the application namespace used to identify the appropriate cloud resources. Returns ---------- A tuple containing the latest SecurityRecommendationSet and Portfolio objects. If a portfolio does not exist, it will create a new one. ''' log.info("Loading latest recommendations") recommendation_set = SecurityRecommendationSet.from_s3( app_ns, constants.S3_PRICE_DISPERSION_RECOMMENDATION_SET_OBJECT_NAME) business_date = util.get_business_date( constants.BUSINESS_DATE_DAYS_LOOKBACK, constants.BUSINESS_DATE_CUTOVER_TIME) if not recommendation_set.is_current(business_date): raise ValidationError("Current recommendation set is not valid", None) try: log.info("Loading current portfolio") pfolio = Portfolio.from_s3(app_ns, constants.S3_PORTFOLIO_OBJECT_NAME) except AWSError as e: if e.resource_not_found(): pfolio = None else: raise e return (pfolio, recommendation_set)
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 generate_recommendation(self): """ Applies the price dispersion algorithm and sets the following instance variables: self.recommendation_set A SecurityRecommendationSet object with the current recommendation self.raw_dataframe Dataframe with all stocks sorted into deciles. Useful for displaying intermediate results. self.recommendation_dataframe A Dataframe containing just the recommended stocks Returns ------------ None """ financial_data = self.__load_financial_data__() self.raw_dataframe = pd.DataFrame(financial_data) pd.options.display.float_format = '{:.3f}'.format # sort the dataframe into deciles self.raw_dataframe['decile'] = pd.qcut( financial_data['dispersion_stdev_pct'], 10, labels=False, duplicates='drop') self.raw_dataframe = self.raw_dataframe.sort_values( ['decile', 'analyst_expected_return'], ascending=(False, False)) self.recommendation_dataframe = self.raw_dataframe.head( self.output_size).drop([ 'decile', 'target_price_avg', 'dispersion_stdev_pct', 'analyst_expected_return' ], axis=1) # price the recommended securitues priced_securities = {} for row in self.recommendation_dataframe.itertuples(index=False): priced_securities[row.ticker] = row.analysis_price # determine the recommendation valid date range valid = self.analysis_end_date + timedelta(days=1) (valid_from, valid_to) = intrinio_util.get_month_date_range( valid.year, valid.month) self.recommendation_set = SecurityRecommendationSet.from_parameters( datetime.now(), valid_from, valid_to, self.analysis_end_date, self.STRATEGY_NAME, "US Equities", priced_securities)
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_is_current_current_date_single(self): # Create a recommendation set from the past (2019/8) recommendation_set = SecurityRecommendationSet.from_parameters( datetime(2020, 3, 1, 4, 56, 57, tzinfo=timezone.utc), date(2019, 8, 1), date(2019, 8, 1), date(2019, 8, 1), "PRICE_DISPERSION", "US Equities", { "GE": 123.45, "INTC": 123.45, "AAPL": 123.45 }) self.assertTrue(recommendation_set.is_current(date(2019, 8, 1)))
def test_notify_new_recommendation_with_boto_error(self): with patch.object(aws_service_wrapper, 'cf_read_export_value', return_value="some_sns_arn"), \ patch.object(aws_service_wrapper, 'sns_publish_notification', side_effect=AWSError("test exception", None)): notification_list = [] notification_list.append(SecurityRecommendationSet.from_parameters(datetime.now(), datetime.now( ), datetime.now(), datetime.now(), 'STRATEGY_NAME', 'US Equities', {'AAPL': 100})) with self.assertRaises(AWSError): recommendation_svc.notify_new_recommendation( notification_list, 'sa')
def generate_recommendation(self): ''' Analyzes all securitues supplied in the ticker list and returns a SecurityRecommendationSet object containing all stocks with a positive MACD crossover. These are stocks that are bullish and have positive momentum behind them. internally sets the self.recommendation_set object ''' analysis_data = { 'ticker_symbol': [], 'price': [], 'macd': [], 'signal': [], 'divergence': [], 'momentum': [] } recommended_securities = {} for ticker_symbol in self.ticker_list.ticker_symbols: (current_price, macd_line, signal_line) = self._read_price_metrics( ticker_symbol) buy_sell_indicator = self._analyze_security( current_price, macd_line, signal_line) analysis_data['ticker_symbol'].append(ticker_symbol) analysis_data['price'].append(current_price) analysis_data['macd'].append(macd_line) analysis_data['signal'].append(signal_line) analysis_data['divergence'].append(macd_line - signal_line) if buy_sell_indicator == True: analysis_data['momentum'].append("BULLISH") recommended_securities[ticker_symbol] = current_price else: analysis_data['momentum'].append("BEARISH") self.raw_dataframe = pd.DataFrame(analysis_data) self.raw_dataframe = self.raw_dataframe.sort_values( ['momentum', 'divergence'], ascending=(False, False)) valid_from = valid_to = self.analysis_date self.recommendation_set = SecurityRecommendationSet.from_parameters( datetime.now(), valid_from, valid_to, self.analysis_date, self.STRATEGY_NAME, "US_EQUITIES", recommended_securities )
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_update_portfolio_new_recommendation(self): sr_mod = deepcopy(self.sr_dict) sr_mod['set_id'] = 'different_set' security_recommendation = SecurityRecommendationSet.from_dict(sr_mod) portfolio = Portfolio.from_dict(self.portfolio_dict) (new_p, updated) = portfolio_mgr_svc.update_portfolio( portfolio, security_recommendation, 1) ''' ensure that 1) portfolio is updated 2) set id are being set properly ''' self.assertTrue(updated) self.assertEqual(new_p.model['set_id'], security_recommendation.model['set_id']) self.assertNotEqual(new_p.model['set_id'], self.sr_dict['set_id'])
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 for this script """ try: (environment, ticker_file_name, output_size, month, year, current_price_date, app_ns) = parse_params() log.info("Parameters:") log.info("Environment: %s" % environment) log.info("Ticker File: %s" % ticker_file_name) log.info("Output Size: %d" % output_size) log.info("Analysis Month: %d" % month) log.info("Analysis Year: %d" % year) if environment == "TEST": log.info("reading ticker file from local filesystem") ticker_list = TickerFile.from_local_file( constants.TICKER_DATA_DIR, ticker_file_name).ticker_list log.info("Performing Recommendation Algorithm") strategy = PriceDispersionStrategy(ticker_list, year, month, output_size) strategy.generate_recommendation() display_calculation_dataframe(month, year, strategy, current_price_date) else: # environment == "PRODUCTION" # test all connectivity upfront, so if there any issues # the problem becomes more apparent connector_test.test_aws_connectivity() connector_test.test_intrinio_connectivity() log.info("Reading ticker file from s3 bucket") ticker_list = TickerFile.from_s3_bucket(ticker_file_name, app_ns).ticker_list log.info("Loading existing recommendation set from S3") recommendation_set = None try: recommendation_set = SecurityRecommendationSet.from_s3(app_ns) except AWSError as awe: if not awe.resource_not_found(): raise awe log.info("No recommendation set was found in S3.") if recommendation_set == None \ or not recommendation_set.is_current(datetime.now()): log.info("Performing Recommendation Algorithm") strategy = PriceDispersionStrategy(ticker_list, year, month, output_size) strategy.generate_recommendation() recommendation_set = strategy.recommendation_set display_calculation_dataframe(month, year, strategy, current_price_date) recommendation_set.save_to_s3(app_ns) recommendation_svc.notify_new_recommendation( recommendation_set, app_ns) else: log.info( "Recommendation set is still valid. There is nothing to do" ) except Exception as e: stack_trace = traceback.format_exc() log.error("Could run script, because: %s" % (str(e))) log.error(stack_trace) if environment == "PRODUCTION": aws_service_wrapper.notify_error( e, "Securities Recommendation Service", stack_trace, app_ns)
def test_valid_parameters(self): SecurityRecommendationSet.from_parameters( datetime.now(), datetime.now(), datetime.now(), datetime.now(), 'STRATEGY_NAME', 'US Equities', {'AAPL': 100})
def test_invalid_parameters(self): ''' Combine these into a single test for brevity ''' with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(None, date.today(), date.today(), datetime.now(), 'STRATEGY_NAME', 'US Equities', {'AAPL': 100}) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(datetime.now(), None, date.today(), date.today(), 'STRATEGY_NAME', 'US Equities', {'AAPL': 100}) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(datetime.now(), date.today(), None, datetime.now(), 'STRATEGY_NAME', 'US Equities', {'AAPL': 100}) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(datetime.now(), date.today(), date.today(), None, 'STRATEGY_NAME', 'US Equities', {'AAPL': 100}) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(datetime.now(), date.today(), date.today(), date.today(), None, 'US Equities', {'AAPL': 100}) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(datetime.now(), date.today(), date.today(), date.today(), 'STRATEGY_NAME', None, {'AAPL': 100}) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters(datetime.now(), date.today(), date.today(), date.today(), 'STRATEGY_NAME', 'US Equities', None) with self.assertRaises(ValidationError): SecurityRecommendationSet.from_parameters( datetime.now(), date.today(), date.today(), date.today(), 'STRATEGY_NAME', 'US Equities', "Not A Dictionary")
def main(): """ Main function for this script """ try: app_ns = parse_params() log.info("Parameters:") log.info("Application Namespace: %s" % app_ns) business_date = util.get_business_date( constants.BUSINESS_DATE_DAYS_LOOKBACK, constants.BUSINESS_DATE_CUTOVER_TIME) log.info("Business Date is: %s" % business_date) # test all connectivity upfront, so if there any issues # the problem becomes more apparent connector_test.test_aws_connectivity() connector_test.test_intrinio_connectivity() log.info('Loading Strategy Configuration "%s" from S3' % constants.STRATEGY_CONFIG_FILE_NAME) configuration = Configuration.try_from_s3( constants.STRATEGY_CONFIG_FILE_NAME, app_ns) log.info("Initalizing Trading Strategies") strategies = [ PriceDispersionStrategy.from_configuration(configuration, app_ns), MACDCrossoverStrategy.from_configuration(configuration, app_ns) ] notification_list = [] for strategy in strategies: recommendation_set = None try: log.info("Executing %s strategy" % strategy.STRATEGY_NAME) recommendation_set = SecurityRecommendationSet.from_s3( app_ns, strategy.S3_RECOMMENDATION_SET_OBJECT_NAME) except AWSError as awe: if not awe.resource_not_found(): raise awe log.info("No recommendation set was found in S3.") if recommendation_set == None \ or not recommendation_set.is_current(business_date): strategy.generate_recommendation() strategy.display_results() recommendation_set = strategy.recommendation_set recommendation_set.save_to_s3( app_ns, strategy.S3_RECOMMENDATION_SET_OBJECT_NAME) notification_list.append(recommendation_set) else: log.info( "Recommendation set is still valid. There is nothing to do") recommendation_svc.notify_new_recommendation( notification_list, app_ns) except Exception as e: stack_trace = traceback.format_exc() log.error("Could run script, because: %s" % (str(e))) log.error(stack_trace)
def test_invalid_dict_1(self): with self.assertRaises(ValidationError): SecurityRecommendationSet.from_dict({'x': 'y'})