# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument from copy import deepcopy from pathlib import Path, PurePosixPath from unittest.mock import MagicMock, PropertyMock import pytest from freqtrade.commands.optimize_commands import start_lookahead_analysis from freqtrade.data.history import get_timerange from freqtrade.exceptions import OperationalException from freqtrade.optimize.lookahead_analysis import Analysis, LookaheadAnalysis from freqtrade.optimize.lookahead_analysis_helpers import LookaheadAnalysisSubFunctions from tests.conftest import EXMS, get_args, log_has_re, patch_exchange @pytest.fixture def lookahead_conf(default_conf_usdt): default_conf_usdt['minimum_trade_amount'] = 10 default_conf_usdt['targeted_trade_amount'] = 20 default_conf_usdt['strategy_path'] = str( Path(__file__).parent.parent / "strategy/strats/lookahead_bias") default_conf_usdt['strategy'] = 'strategy_test_v3_with_lookahead_bias' return default_conf_usdt def test_start_lookahead_analysis(mocker): single_mock = MagicMock() text_table_mock = MagicMock() mocker.patch.multiple( 'freqtrade.optimize.lookahead_analysis_helpers.LookaheadAnalysisSubFunctions', initialize_single_lookahead_analysis=single_mock, text_table_lookahead_analysis_instances=text_table_mock, ) args = [ "lookahead-analysis", "--strategy", "strategy_test_v3_with_lookahead_bias", "--strategy-path", str(Path(__file__).parent.parent / "strategy/strats/lookahead_bias"), ] pargs = get_args(args) pargs['config'] = None start_lookahead_analysis(pargs) assert single_mock.call_count == 1 assert text_table_mock.call_count == 1 single_mock.reset_mock() # Test invalid config args = [ "lookahead-analysis", "--strategy", "strategy_test_v3_with_lookahead_bias", "--strategy-path", str(Path(__file__).parent.parent / "strategy/strats/lookahead_bias"), "--targeted-trade-amount", "10", "--minimum-trade-amount", "20", ] pargs = get_args(args) pargs['config'] = None with pytest.raises(OperationalException, match=r"targeted trade amount can't be smaller than .*"): start_lookahead_analysis(pargs) def test_lookahead_helper_invalid_config(lookahead_conf, mocker, caplog) -> None: conf = deepcopy(lookahead_conf) conf['targeted_trade_amount'] = 10 conf['minimum_trade_amount'] = 40 with pytest.raises(OperationalException, match=r"targeted trade amount can't be smaller than .*"): LookaheadAnalysisSubFunctions.start(conf) conf = deepcopy(lookahead_conf) del conf['strategy'] with pytest.raises(OperationalException, match=r"No Strategy specified"): LookaheadAnalysisSubFunctions.start(conf) def test_lookahead_helper_start(lookahead_conf, mocker, caplog) -> None: single_mock = MagicMock() text_table_mock = MagicMock() mocker.patch.multiple( 'freqtrade.optimize.lookahead_analysis_helpers.LookaheadAnalysisSubFunctions', initialize_single_lookahead_analysis=single_mock, text_table_lookahead_analysis_instances=text_table_mock, ) LookaheadAnalysisSubFunctions.start(lookahead_conf) assert single_mock.call_count == 1 assert text_table_mock.call_count == 1 single_mock.reset_mock() text_table_mock.reset_mock() def test_lookahead_helper_text_table_lookahead_analysis_instances(lookahead_conf, caplog): analysis = Analysis() analysis.has_bias = True analysis.total_signals = 5 analysis.false_entry_signals = 4 analysis.false_exit_signals = 3 strategy_obj = \ { 'name': "strategy_test_v3_with_lookahead_bias", 'location': PurePosixPath(lookahead_conf['strategy_path'], f"{lookahead_conf['strategy']}.py") } instance = LookaheadAnalysis(lookahead_conf, strategy_obj) instance.current_analysis = analysis table, headers, data = (LookaheadAnalysisSubFunctions. text_table_lookahead_analysis_instances([instance])) # check row contents for a try that errored out assert data[0][0] == 'strategy_test_v3_with_lookahead_bias.py' assert data[0][1] == 'strategy_test_v3_with_lookahead_bias' assert data[0][2].__contains__('error') assert len(data[0]) == 3 # edit it into not showing an error instance.failed_bias_check = False table, headers, data = (LookaheadAnalysisSubFunctions. text_table_lookahead_analysis_instances([instance])) assert data[0][0] == 'strategy_test_v3_with_lookahead_bias.py' assert data[0][1] == 'strategy_test_v3_with_lookahead_bias' assert data[0][2] # True assert data[0][3] == 5 assert data[0][4] == 4 assert data[0][5] == 3 assert data[0][6] == '' analysis.false_indicators.append('falseIndicator1') analysis.false_indicators.append('falseIndicator2') table, headers, data = (LookaheadAnalysisSubFunctions. text_table_lookahead_analysis_instances([instance])) assert data[0][6] == 'falseIndicator1, falseIndicator2' # check amount of returning rows assert len(data) == 1 # check amount of multiple rows table, headers, data = (LookaheadAnalysisSubFunctions. text_table_lookahead_analysis_instances([instance, instance, instance])) assert len(data) == 3 def test_lookahead_helper_export_to_csv(lookahead_conf): import pandas as pd lookahead_conf['lookahead_analysis_exportfilename'] = "temp_csv_lookahead_analysis.csv" # just to be sure the test won't fail: remove file if exists for some reason # (repeat this at the end once again to clean up) if Path(lookahead_conf['lookahead_analysis_exportfilename']).exists(): Path(lookahead_conf['lookahead_analysis_exportfilename']).unlink() # before we can start we have to delete the # 1st check: create a new file and verify its contents analysis1 = Analysis() analysis1.has_bias = True analysis1.total_signals = 5 analysis1.false_entry_signals = 4 analysis1.false_exit_signals = 3 analysis1.false_indicators.append('falseIndicator1') analysis1.false_indicators.append('falseIndicator2') lookahead_conf['lookahead_analysis_exportfilename'] = "temp_csv_lookahead_analysis.csv" strategy_obj1 = { 'name': "strat1", 'location': PurePosixPath("file1.py"), } instance1 = LookaheadAnalysis(lookahead_conf, strategy_obj1) instance1.current_analysis = analysis1 LookaheadAnalysisSubFunctions.export_to_csv(lookahead_conf, [instance1]) saved_data1 = pd.read_csv(lookahead_conf['lookahead_analysis_exportfilename']) expected_values1 = [ [ 'file1.py', 'strat1', True, 5, 4, 3, "falseIndicator1,falseIndicator2" ], ] expected_columns = ['filename', 'strategy', 'has_bias', 'total_signals', 'biased_entry_signals', 'biased_exit_signals', 'biased_indicators'] expected_data1 = pd.DataFrame(expected_values1, columns=expected_columns) assert Path(lookahead_conf['lookahead_analysis_exportfilename']).exists() assert expected_data1.equals(saved_data1) # 2nd check: update the same strategy (which internally changed or is being retested) expected_values2 = [ [ 'file1.py', 'strat1', False, 10, 11, 12, "falseIndicator3,falseIndicator4" ], ] expected_data2 = pd.DataFrame(expected_values2, columns=expected_columns) analysis2 = Analysis() analysis2.has_bias = False analysis2.total_signals = 10 analysis2.false_entry_signals = 11 analysis2.false_exit_signals = 12 analysis2.false_indicators.append('falseIndicator3') analysis2.false_indicators.append('falseIndicator4') strategy_obj2 = { 'name': "strat1", 'location': PurePosixPath("file1.py"), } instance2 = LookaheadAnalysis(lookahead_conf, strategy_obj2) instance2.current_analysis = analysis2 LookaheadAnalysisSubFunctions.export_to_csv(lookahead_conf, [instance2]) saved_data2 = pd.read_csv(lookahead_conf['lookahead_analysis_exportfilename']) assert expected_data2.equals(saved_data2) # 3rd check: now we add a new row to an already existing file expected_values3 = [ [ 'file1.py', 'strat1', False, 10, 11, 12, "falseIndicator3,falseIndicator4" ], [ 'file3.py', 'strat3', True, 20, 21, 22, "falseIndicator5,falseIndicator6" ], ] expected_data3 = pd.DataFrame(expected_values3, columns=expected_columns) analysis3 = Analysis() analysis3.has_bias = True analysis3.total_signals = 20 analysis3.false_entry_signals = 21 analysis3.false_exit_signals = 22 analysis3.false_indicators.append('falseIndicator5') analysis3.false_indicators.append('falseIndicator6') lookahead_conf['lookahead_analysis_exportfilename'] = "temp_csv_lookahead_analysis.csv" strategy_obj3 = { 'name': "strat3", 'location': PurePosixPath("file3.py"), } instance3 = LookaheadAnalysis(lookahead_conf, strategy_obj3) instance3.current_analysis = analysis3 LookaheadAnalysisSubFunctions.export_to_csv(lookahead_conf, [instance3]) saved_data3 = pd.read_csv(lookahead_conf['lookahead_analysis_exportfilename']) assert expected_data3.equals(saved_data3) # remove csv file after the test is done if Path(lookahead_conf['lookahead_analysis_exportfilename']).exists(): Path(lookahead_conf['lookahead_analysis_exportfilename']).unlink() def test_initialize_single_lookahead_analysis(): # TODO pytest.skip("TODO") @pytest.mark.parametrize('scenario', [ 'no_bias', 'bias1' ]) def test_biased_strategy(lookahead_conf, mocker, caplog, scenario) -> None: mocker.patch('freqtrade.data.history.get_timerange', get_timerange) mocker.patch(f'{EXMS}.get_fee', return_value=0.0) mocker.patch(f'{EXMS}.get_min_pair_stake_amount', return_value=0.00001) mocker.patch(f'{EXMS}.get_max_pair_stake_amount', return_value=float('inf')) patch_exchange(mocker) mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) lookahead_conf['pairs'] = ['UNITTEST/USDT'] lookahead_conf['timeframe'] = '5m' lookahead_conf['timerange'] = '20180119-20180122' # Patch scenario Parameter to allow for easy selection mocker.patch('freqtrade.strategy.hyper.HyperStrategyMixin.load_params_from_file', return_value={ 'params': { "buy": { "scenario": scenario } } }) strategy_obj = {} strategy_obj['name'] = "strategy_test_v3_with_lookahead_bias" instance = LookaheadAnalysis(lookahead_conf, strategy_obj) instance.start() # Assert init correct assert log_has_re(f"Strategy Parameter: scenario = {scenario}", caplog) # Assert bias detected assert log_has_re(r".*bias detected.*", caplog) # TODO: assert something ... most likely output (?) or instance state? # Assert False to see full logs in output # assert False # Run with `pytest tests/optimize/test_lookahead_analysis.py -k test_biased_strategy`