Beispiel #1
0
    def test_recommendation_set_price_date_during_analysisperiod(self):
        with patch.object(PriceDispersionStrategy,
                          '_load_financial_data',
                          return_value=self.financial_data):

            price_date = date(2020, 6, 10)

            strategy = PriceDispersionStrategy(
                TickerList.from_dict({
                    "list_name": "DOW30",
                    "list_type": "US_EQUITIES",
                    "comparison_symbol": "DIA",
                    "ticker_symbols": ['AAPL', 'V']
                }), '2020-06', price_date, 3)

            strategy.generate_recommendation()

            recommendation_set = strategy.recommendation_set

            self.assertEqual(recommendation_set.model['valid_from'],
                             str(date(2020, 7, 1)))
            self.assertEqual(recommendation_set.model['valid_to'],
                             str(date(2020, 7, 31)))
            self.assertEqual(recommendation_set.model['price_date'],
                             str(date(2020, 6, 10)))
    def from_configuration(cls, configuration: object, app_ns: str):
        '''
            See BaseStrategy.from_configuration for documentation
        '''
        today = pd.to_datetime('today').date()

        try:
            config_params = dict(configuration.config[cls.CONFIG_SECTION])

            ticker_file_name = config_params['ticker_list_file_name']
            output_size = int(config_params['output_size'])
        except Exception as e:
            raise ValidationError(
                "Could not read MACD Crossover Strategy configuration parameters",
                e)

        ticker_list = TickerList.try_from_s3(app_ns, ticker_file_name)
        analysis_period = (pd.Period(today, 'M') - 1).strftime("%Y-%m")

        current_price_date = util.get_business_date(
            constants.BUSINESS_DATE_DAYS_LOOKBACK,
            constants.BUSINESS_DATE_CUTOVER_TIME)

        return cls(ticker_list, analysis_period, current_price_date,
                   output_size)
def main():
    '''
        Main testing script
    '''
    try:

        ticker_list = TickerList.from_local_file("%s/djia30.json" %
                                                 (constants.APP_DATA_DIR))

        config = Configuration.try_from_s3(constants.STRATEGY_CONFIG_FILE_NAME,
                                           'sa')

        #macd_strategy = MACDCrossoverStrategy.from_configuration(config, 'sa')
        macd_strategy = MACDCrossoverStrategy(ticker_list, date(2020, 6, 16),
                                              0.0016, 12, 16, 9)
        macd_strategy.generate_recommendation()
        macd_strategy.display_results()
        '''
        #pd_strategy = PriceDispersionStrategy.from_configuration(config, 'sa')
        pd_strategy = PriceDispersionStrategy(
            ticker_list, '2020-06', date(2020, 5, 16), 3)
        pd_strategy.generate_recommendation()
        pd_strategy.display_results()
        '''

    except Exception as e:
        log.error("Could run script, because, %s" % (str(e)))
    def from_configuration(cls, configuration: object, app_ns: str):
        '''
            See BaseStrategy.from_configuration for documentation
        '''

        analysis_date = util.get_business_date(
            constants.BUSINESS_DATE_DAYS_LOOKBACK, constants.BUSINESS_DATE_CUTOVER_TIME)

        try:
            config_params = dict(configuration.config[cls.CONFIG_SECTION])

            ticker_file_name = config_params['ticker_list_file_name']
            divergence_factor_threshold = float(
                config_params['divergence_factor_threshold'])
            macd_fast_period = int(config_params['macd_fast_period'])
            macd_slow_period = int(config_params['macd_slow_period'])
            macd_signal_period = int(config_params['macd_signal_period'])
        except Exception as e:
            raise ValidationError(
                "Could not read MACD Crossover Strategy configuration parameters", e)
        finally:
            configuration.close()

        ticker_list = TickerList.try_from_s3(app_ns, ticker_file_name)

        return cls(ticker_list, analysis_date, divergence_factor_threshold, macd_fast_period, macd_slow_period, macd_signal_period)
Beispiel #5
0
 def test_init_enough_tickers(self):
     PriceDispersionStrategy(
         TickerList.from_dict({
             "list_name": "DOW30",
             "list_type": "US_EQUITIES",
             "comparison_symbol": "DIA",
             "ticker_symbols": ['AAPL', 'V']
         }), '2020-02', date(2020, 6, 10), 3)
Beispiel #6
0
 def test_init_no_price_date(self):
     PriceDispersionStrategy(
         TickerList.from_dict({
             "list_name": "DOW30",
             "list_type": "US_EQUITIES",
             "comparison_symbol": "DIA",
             "ticker_symbols": ['AAPL', 'V']
         }), '2020-05', None, 3)
Beispiel #7
0
 def test_init_output_size_too_small(self):
     with self.assertRaises(ValidationError):
         PriceDispersionStrategy(
             TickerList.from_dict({
                 "list_name": "DOW30",
                 "list_type": "US_EQUITIES",
                 "comparison_symbol": "DIA",
                 "ticker_symbols": ['AAPL', 'V']
             }), '2020-02', date(2020, 6, 10), 0)
Beispiel #8
0
 def test_init_empty_ticker_list(self):
     with self.assertRaises(ValidationError):
         PriceDispersionStrategy(
             TickerList.from_dict({
                 "list_name": "DOW30",
                 "list_type": "US_EQUITIES",
                 "comparison_symbol": "DIA",
                 "ticker_symbols": []
             }), '2020-02', date(2020, 6, 10), 3)
Beispiel #9
0
 def test_init_bad_period(self):
     with self.assertRaises(ValidationError):
         PriceDispersionStrategy(
             TickerList.from_dict({
                 "list_name": "DOW30",
                 "list_type": "US_EQUITIES",
                 "comparison_symbol": "DIA",
                 "ticker_symbols": ['AAPL', 'V']
             }), 'invalid_period', date(2020, 6, 10), 3)
Beispiel #10
0
    def test_from_s3_bucket_exception(self):
        '''
            Tests that if there was an error downloading the ticker file 
            from S3, it will thrown an exception
        '''

        with patch.object(aws_service_wrapper, 'cf_list_exports',
                          return_value={
                              constants.s3_data_bucket_export_name('sa'): "test-bucket"
                          }),\
            patch.object(aws_service_wrapper, 's3_download_object',
                         side_effect=AWSError(
                             "test", Exception("Download Exception")
                         )
                         ):

            try:
                TickerList.try_from_s3('sa', 'ticker-file')
            except AWSError as awe:
                self.assertTrue("Download Exception" in str(awe))
Beispiel #11
0
    def test_ticker_list_matches(self):
        ticker_list = TickerList.from_dict({
            "list_name":
            "DOW30",
            "list_type":
            "US_EQUITIES",
            "comparison_symbol":
            "DIA",
            "ticker_symbols": ["AAPL", "AXP", "BA"]
        })

        self.assertListEqual(ticker_list.ticker_symbols,
                             ticker_list.model['ticker_symbols'])
Beispiel #12
0
    def test_from_s3_bucket_exception_upload_local_file(self):
        '''
            Tests that if the file was not found in s3, and
            a local alternative is found, it will self heal by
            restoring the s3 file
        '''

        ticker_list = TickerList.from_dict({
            "list_name":
            "DOW30",
            "list_type":
            "US_EQUITIES",
            "comparison_symbol":
            "DIA",
            "ticker_symbols": ["AAPL", "AXP", "BA"]
        })

        with patch.object(TickerList, 'from_local_file',
                          return_value=ticker_list), \
            patch.object(aws_service_wrapper, 'cf_list_exports',
                         return_value={
                             constants.s3_data_bucket_export_name('sa'): "test-bucket"
                         }),\
            patch.object(TickerList, 'from_s3',
                         side_effect=AWSError(
                             "test", Exception(
                                 "An error occurred (404) when calling the HeadObject operation: Not Found")
                         )
                         ),\
            patch.object(aws_service_wrapper, 's3_upload_object',
                         return_value=None) as mock_s3_upload_object,\
            patch.object(os.path, 'isfile',
                         return_value=True):

            ticker_file = TickerList.try_from_s3('sa', 'ticker-file')

            # assert that s3_upload_object method was called once
            self.assertEqual(mock_s3_upload_object.call_count, 1)
Beispiel #13
0
    def test_api_exception(self):
        with patch.object(intrinio_data.COMPANY_API,
                          'get_company_historical_data',
                          side_effect=ApiException("Not Found")):

            strategy = PriceDispersionStrategy(
                TickerList.from_dict({
                    "list_name": "DOW30",
                    "list_type": "US_EQUITIES",
                    "comparison_symbol": "DIA",
                    "ticker_symbols": ['AAPL', 'V']
                }), '2000-05', date(2000, 6, 10), 3)

            with self.assertRaises(DataError):
                strategy._load_financial_data()
Beispiel #14
0
    def test_recommendation_set_price_date_before_analysisperiod(self):
        with patch.object(PriceDispersionStrategy,
                          '_load_financial_data',
                          return_value=self.financial_data):

            price_date = date(2020, 5, 10)

            with self.assertRaises(ValidationError):
                strategy = PriceDispersionStrategy(
                    TickerList.from_dict({
                        "list_name": "DOW30",
                        "list_type": "US_EQUITIES",
                        "comparison_symbol": "DIA",
                        "ticker_symbols": ['AAPL', 'V']
                    }), '2020-06', price_date, 3)
Beispiel #15
0
    def test_from_s3_bucket_exception_no_local_file(self):
        '''
            Tests that if the file was not found in s3, and
            a local alternative is found, it will self heal by
            restoring the s3 file
        '''

        with patch.object(aws_service_wrapper, 'cf_list_exports',
                          return_value={
                              constants.s3_data_bucket_export_name('sa'): "test-bucket"
                          }),\
            patch.object(TickerList, 'from_s3',
                         side_effect=AWSError(
                             "test", Exception(
                                 "An error occurred (404) when calling the HeadObject operation: Not Found")
                         )
                         ),\
            patch.object(aws_service_wrapper, 's3_upload_object',
                         return_value=None) as mock_s3_upload_object,\
            patch.object(os.path, 'isfile',
                         return_value=False):

            with self.assertRaises(AWSError):
                TickerList.try_from_s3('sa', 'ticker-file')
    def test_from_configuration_valid(self):

        ticker_list = TickerList.from_local_file("%s/djia30.json" %
                                                 (constants.APP_DATA_DIR))

        price_date = date(2020, 6, 3)

        with patch.object(util, 'get_business_date',
                          return_value=price_date), \
            patch.object(TickerList, 'from_s3',
                         return_value=ticker_list):

            strategy = MACDCrossoverStrategy.from_configuration(
                Configuration.from_local_config(
                    constants.STRATEGY_CONFIG_FILE_NAME), 'sa')

            self.assertEqual(strategy.analysis_date, price_date)
Beispiel #17
0
    def test_from_configuration_valid(self):

        ticker_list = TickerList.from_local_file("%s/djia30.json" %
                                                 (constants.APP_DATA_DIR))

        with patch.object(util, 'get_business_date',
                          return_value=date(2020, 6, 3)), \
            patch.object(pd, 'to_datetime',
                         return_value=datetime(2020, 6, 19)), \
            patch.object(TickerList, 'from_s3',
                         return_value=ticker_list):

            strategy = PriceDispersionStrategy.from_configuration(
                Configuration.from_local_config(
                    constants.STRATEGY_CONFIG_FILE_NAME), 'sa')

            self.assertEqual(strategy.analysis_start_date, date(2020, 5, 1))
            self.assertEqual(strategy.analysis_end_date, date(2020, 5, 31))
            self.assertEqual(strategy.current_price_date, date(2020, 6, 3))
        '''
Beispiel #18
0
def main():
    """
        Main Function for this script
    """

    description = """
                Backtest script for the PRICE_DISPERSION strategy.

                This script will execute the strategy for each month of available data,
                and compare the returns of the selected portfolio with the average of the 
                supplied ticker list.
                The script will show returns of a 1 month, 2 month and three month horizon
                displayed as a Pandad Dataframe

              """

    parser = argparse.ArgumentParser(description=description)
    parser.add_argument("-ticker_list", help="Ticker Symbol File",
                        type=str, required=True)
    parser.add_argument(
        "-output_size", help="Number of selected securities", type=int, required=True)

    args = parser.parse_args()

    ticker_file_name = args.ticker_list
    output_size = args.output_size

    log.info("Parameters:")
    log.info("Ticker File: %s" % ticker_file_name)
    log.info("Output Size: %d" % output_size)

    ticker_list = None

    backtest_report = {
        'investment_period': [],
        'ticker_sample_size': [],
        'avg_ret_1M': [],
        'sel_ret_1M': [],
        'avg_ret_2M': [],
        'sel_ret_2M': [],
        'avg_ret_3M': [],
        'sel_ret_3M': []
    }

    backtest_summary = {
        'investment_period': [],
        'ticker_sample_size': [],
        'avg_tot_1M': [],
        'sel_tot_1M': [],
        'avg_tot_2M': [],
        'sel_tot_2M': [],
        'avg_tot_3M': [],
        'sel_tot_3M': []
    }

    today = date.today()

    def backtest(analysis_period: str):
        log.info("Performing backtest for %s" % analysis_period)

        period = pd.Period(analysis_period)

        data_end_date = intrinio_util.get_month_period_range(period)[1]

        strategy = PriceDispersionStrategy(
            ticker_list, period, None, output_size)
        strategy.generate_recommendation()

        date_1m = get_nearest_business_date(data_end_date + timedelta(days=30))
        date_2m = get_nearest_business_date(data_end_date + timedelta(days=60))
        date_3m = get_nearest_business_date(data_end_date + timedelta(days=90))

        portfolio_1m = calculator.mark_to_market(
            strategy.recommendation_dataframe, 'ticker', 'analysis_price', date_1m)['actual_return'].mean() * 100
        portfolio_2m = calculator.mark_to_market(
            strategy.recommendation_dataframe, 'ticker', 'analysis_price', date_2m)['actual_return'].mean() * 100
        portfolio_3m = calculator.mark_to_market(
            strategy.recommendation_dataframe, 'ticker', 'analysis_price', date_3m)['actual_return'].mean() * 100

        all_stocks_1m = calculator.mark_to_market(strategy.raw_dataframe, 'ticker', 'analysis_price', date_1m)[
            'actual_return'].mean() * 100
        all_stocks_2m = calculator.mark_to_market(strategy.raw_dataframe, 'ticker', 'analysis_price', date_2m)[
            'actual_return'].mean() * 100
        all_stocks_3m = calculator.mark_to_market(strategy.raw_dataframe, 'ticker', 'analysis_price', date_3m)[
            'actual_return'].mean() * 100

        backtest_report['investment_period'].append(
            data_end_date.strftime('%Y/%m'))
        backtest_report['ticker_sample_size'].append(
            len(strategy.raw_dataframe))

        backtest_report['avg_ret_1M'].append(all_stocks_1m)
        backtest_report['sel_ret_1M'].append(portfolio_1m)
        backtest_report['avg_ret_2M'].append(all_stocks_2m)
        backtest_report['sel_ret_2M'].append(portfolio_2m)
        backtest_report['avg_ret_3M'].append(all_stocks_3m)
        backtest_report['sel_ret_3M'].append(portfolio_3m)

    try:

        ticker_list = TickerList.from_local_file("%s/%s" %
                                                 (constants.TICKER_DATA_DIR, ticker_file_name))

        backtest('2019-05')
        backtest('2019-06')
        backtest('2019-07')
        backtest('2019-08')
        backtest('2019-09')
        backtest('2019-10')
        backtest('2019-11')
        backtest('2019-12')
        backtest('2020-01')
        backtest('2020-02')

        backtest_dataframe = pd.DataFrame(backtest_report)
        pd.options.display.float_format = '{:.2f}%'.format
        print(backtest_dataframe.to_string(index=False))

        backtest_summary['investment_period'] = ['----/--']
        backtest_summary['ticker_sample_size'] = ['--']

        backtest_summary['avg_tot_1M'].append(
            backtest_dataframe['avg_ret_1M'].sum())
        backtest_summary['sel_tot_1M'].append(
            backtest_dataframe['sel_ret_1M'].sum())
        backtest_summary['avg_tot_2M'].append(
            backtest_dataframe['avg_ret_2M'].sum())
        backtest_summary['sel_tot_2M'].append(
            backtest_dataframe['sel_ret_2M'].sum())
        backtest_summary['avg_tot_3M'].append(
            backtest_dataframe['avg_ret_3M'].sum())
        backtest_summary['sel_tot_3M'].append(
            backtest_dataframe['sel_ret_3M'].sum())

        backtest_summary_dataframe = pd.DataFrame(backtest_summary)
        print(backtest_summary_dataframe.to_string(index=False))

    except Exception as e:
        log.error("Could run script, because, %s" % (str(e)))
        raise e
        exit(-1)
def main():
    """
        Main Function for this script
    """

    description = """
                Executes a backtest for the MACD_CROSSOVER strategy given a ticker list,
                start date, end date and threshold, e.g. -0.02, used to determine the maximum
                allowed loss of a trade before a stop loss takes effect.
              """

    parser = argparse.ArgumentParser(description=description)
    parser.add_argument("-ticker_list",
                        help="Ticker List File",
                        type=str,
                        required=True)
    date_parser = lambda s: datetime.strptime(s, '%Y/%m/%d')
    parser.add_argument("-start_date",
                        help="Backtest start date (YYY/MM/DD)",
                        type=date_parser,
                        required=True)
    parser.add_argument("-end_date",
                        help="Backtest end date (YYYY/MM/DD)",
                        type=date_parser,
                        required=True)
    parser.add_argument("-stop_loss_theshold",
                        help="Stop Loss Threshold factor, e.g. -0.02 (-2%%)",
                        type=float,
                        required=True)

    args = parser.parse_args()

    ticker_file_name = args.ticker_list
    start_date = args.start_date
    end_date = args.end_date
    stop_loss_theshold = args.stop_loss_theshold

    log.info("Parameters:")
    log.info("Ticker File: %s" % ticker_file_name)
    log.info("Start Date: %s" % start_date)
    log.info("End Date: %s" % end_date)
    log.info("Stop Loss Threshold: %.2f" % stop_loss_theshold)

    log.info("")
    log.info("MACD Configuration:")
    log.info("Divergence Tolerance Factor: %f" % DIVERGENCE_FACTOR_THRESHOLD)
    log.info("Slow Period: %d" % SLOW_PERIOD)
    log.info("Fast Period: %d" % FAST_PERIOD)
    log.info("Signal Period: %d" % SIGNAL_PERIOD)

    log.info("")

    ticker_list = TickerList.from_local_file(
        "%s/%s" % (constants.TICKER_DATA_DIR, ticker_file_name))

    trade_dict = {
        'ticker': [],
        'buy_date': [],
        'buy_price': [],
        'sell_date': [],
        'sell_price': [],
        'trade_pnl_factor': [],
        'false_signal': []
    }

    try:
        date_list = get_business_date_list(start_date, end_date)
        init_portfolio_dict(ticker_list)

        for i in range(0, len(date_list) - 1):
            recommendation_date = date_list[i]
            trade_date = date_list[i + 1]

            strategy = MACDCrossoverStrategy(ticker_list, recommendation_date,
                                             DIVERGENCE_FACTOR_THRESHOLD,
                                             FAST_PERIOD, SLOW_PERIOD,
                                             SIGNAL_PERIOD)
            strategy.generate_recommendation()

            print("processing: %s" % recommendation_date, end="\r")

            unwound_positions = trade(trade_date, strategy.recommendation_set)

            for position in unwound_positions:
                (ticker, buy_date, sell_date, buy_price) = position

                sell_price = get_close_price(ticker, sell_date)
                pnl = ((sell_price / buy_price) - 1)
                false_signal = 0

                if (pnl < stop_loss_theshold):
                    sell_price = 'STOP_LOSS'
                    false_signal = 1
                    pnl = stop_loss_theshold

                if (pnl < 0):
                    false_signal = 1

                trade_dict['ticker'].append(ticker)
                trade_dict['buy_date'].append(buy_date)
                trade_dict['buy_price'].append(buy_price)
                trade_dict['sell_date'].append(str(sell_date))
                trade_dict['sell_price'].append(sell_price)
                trade_dict['trade_pnl_factor'].append(pnl)
                trade_dict['false_signal'].append(false_signal)

        trades_by_ticker = []
        for ticker in ticker_list.model['ticker_symbols']:
            trades_by_ticker.append(calculate_returns(ticker, trade_dict))
        trade_dataframe = pd.concat(trades_by_ticker)

        display_results(trade_dataframe)

    except Exception as e:
        log.error("Could run script, because, %s" % (str(e)))
        raise e
class TestStrategiesMACDCrossover(unittest.TestCase):
    """
        Testing class for the strategies.macd_crossover_strategy module
    """
    '''
        Constructor Tests
    '''

    ticker_file_path = "%s/djia30.json" % constants.TICKER_DATA_DIR
    ticker_list = TickerList.from_local_file(ticker_file_path)

    def test_from_configuration_invalid(self):
        with patch('support.constants.CONFIG_FILE_PATH',
                   "./test/config-unittest-bad/"):
            bad_config = Configuration.from_local_config("bad-test-config.ini")
            with self.assertRaises(ValidationError):
                MACDCrossoverStrategy.from_configuration(bad_config, 'sa')

    def test_from_configuration_valid(self):

        ticker_list = TickerList.from_local_file("%s/djia30.json" %
                                                 (constants.APP_DATA_DIR))

        price_date = date(2020, 6, 3)

        with patch.object(util, 'get_business_date',
                          return_value=price_date), \
            patch.object(TickerList, 'from_s3',
                         return_value=ticker_list):

            strategy = MACDCrossoverStrategy.from_configuration(
                Configuration.from_local_config(
                    constants.STRATEGY_CONFIG_FILE_NAME), 'sa')

            self.assertEqual(strategy.analysis_date, price_date)

    '''
        _analyze_security tests
    '''

    def test_analyze_security_1(self):
        '''
            Significant divergence. Both MACD and histogram positive
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = 10
        signal_line = 9

        self.assertTrue(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_2(self):
        '''
            No significant divergence. Both MACD and histogram positive
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = 10
        signal_line = 9.9

        self.assertTrue(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_3(self):
        '''
            Significant divergence. both MACD and Signal negative 
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = -10
        signal_line = -9

        self.assertFalse(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_4(self):
        '''
            No significant divergence. both MACD and Signal negative 
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = -10
        signal_line = -9.9

        self.assertTrue(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_5(self):
        '''
            Significant divergence. both MACD positive, Signal negative 
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = 1
        signal_line = -1

        self.assertTrue(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_6(self):
        '''
            No Significant divergence. MACD positive, Signal negative 
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = 0.01
        signal_line = -0.01

        self.assertTrue(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_6(self):
        '''
            No Significant divergence. MACD negative, Signal positive 
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = -1
        signal_line = 1

        self.assertFalse(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    def test_analyze_security_7(self):
        '''
            No Significant divergence. MACD negative, Signal positive 
        '''

        macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                              date(2020, 6, 10), 0.0016, 12,
                                              26, 9)

        current_price = 100
        macd_line = -0.01
        signal_line = 0.01

        self.assertTrue(
            macd_strategy._analyze_security(current_price, macd_line,
                                            signal_line))

    price_dict = {"2020-06-08": 54.74}

    sma_dict = {"2020-06-08": 43.80299999999999}

    macd_dict = {
        "2020-06-08": {
            "macd_histogram": 0.9111766583116911,
            "macd_line": 2.085415403516656,
            "signal_line": 1.1742387452049647
        }
    }
    '''
        _read_price_metrics tests
    '''

    def test_read_price_metrics(self):
        with patch.object(intrinio_data, 'get_daily_stock_close_prices',
                          return_value=self.price_dict), \
            patch.object(intrinio_data, 'get_sma_indicator',
                         return_value=self.sma_dict), \
            patch.object(intrinio_data, 'get_macd_indicator',
                         return_value=self.macd_dict):

            macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                                  date(2020, 6, 8), 0.0016, 12,
                                                  26, 9)

            (current_price, macd_line,
             signal_line) = macd_strategy._read_price_metrics('AAPL')

            self.assertEqual(current_price, 54.74)
            self.assertEqual(macd_line, 2.085415403516656)
            self.assertEqual(signal_line, 1.1742387452049647)

    def test_read_price_metrics_with_exception(self):
        with patch.object(intrinio_data, 'get_daily_stock_close_prices',
                          return_value={
                "2020-06-09": 54.74
                          }), \
            patch.object(intrinio_data, 'get_sma_indicator',
                         return_value={
                "2020-06-09": 43.80299999999999
                             }), \
            patch.object(intrinio_data, 'get_macd_indicator',
                         return_value={
                "2020-06-09": {
                "macd_histogram": 9111766583116911,
                "macd_line": 2.085415403516656,
                "signal_line": 1.1742387452049647
                }
                             }):

            ticker_file_path = "%s/djia30.json" % constants.TICKER_DATA_DIR

            macd_strategy = MACDCrossoverStrategy(self.ticker_list,
                                                  date(2020, 6, 8), 0.0016, 12,
                                                  26, 9)

            with self.assertRaises(ValidationError):
                macd_strategy._read_price_metrics('AAPL')

    '''
        generate_recommendation tests
        Tests that the recommendation set is properly constructed, specifially
        in terms of using the correct dates
    '''

    def test_recommendation_set_dates(self):

        price_date = date(2020, 6, 8)
        with patch.object(intrinio_data, 'get_daily_stock_close_prices',
                          return_value=self.price_dict), \
            patch.object(intrinio_data, 'get_sma_indicator',
                         return_value=self.sma_dict), \
            patch.object(intrinio_data, 'get_macd_indicator',
                         return_value=self.macd_dict):

            strategy = MACDCrossoverStrategy(self.ticker_list, price_date,
                                             0.0016, 12, 26, 9)

            strategy.generate_recommendation()

            recommendation_set = strategy.recommendation_set

            self.assertEqual(recommendation_set.model['valid_from'],
                             str(date(2020, 6, 8)))
            self.assertEqual(recommendation_set.model['valid_to'],
                             str(date(2020, 6, 8)))
            self.assertEqual(recommendation_set.model['price_date'],
                             str(date(2020, 6, 8)))