def __init__(self, config: dict) -> None: """ Init Analyze :param config: Bot configuration (use the one from Configuration()) """ self.config = config self.strategy = StrategyResolver(self.config).strategy
def test_load_not_found_strategy(): strategy = StrategyResolver() with pytest.raises( ImportError, match=r'Impossible to load Strategy \'NotFoundStrategy\'.' r' This class does not exist or contains Python code errors'): strategy._load_strategy('NotFoundStrategy')
def __init__(self, config: Dict[str, Any]) -> None: self.config = config # Reset keys for backtesting self.config['exchange']['key'] = '' self.config['exchange']['secret'] = '' self.config['exchange']['password'] = '' self.config['exchange']['uid'] = '' self.config['dry_run'] = True self.strategylist: List[IStrategy] = [] if self.config.get('strategy_list', None): # Force one interval self.ticker_interval = str(self.config.get('ticker_interval')) for strat in list(self.config['strategy_list']): stratconf = deepcopy(self.config) stratconf['strategy'] = strat self.strategylist.append(StrategyResolver(stratconf).strategy) else: # only one strategy strat = StrategyResolver(self.config).strategy self.strategylist.append(StrategyResolver(self.config).strategy) # Load one strategy self._set_strategy(self.strategylist[0]) self.exchange = Exchange(self.config) self.fee = self.exchange.get_fee()
def test_search_strategy(): default_location = os.path.join( os.path.dirname(os.path.realpath(__file__)), '..', '..', 'strategy') assert isinstance( StrategyResolver._search_strategy(default_location, 'DefaultStrategy'), IStrategy) assert StrategyResolver._search_strategy(default_location, 'NotFoundStrategy') is None
def test_load_strategy_custom_directory(result): resolver = StrategyResolver() extra_dir = os.path.join('some', 'path') with pytest.raises( FileNotFoundError, match=r".*No such file or directory: '{}'".format(extra_dir)): resolver._load_strategy('TestStrategy', extra_dir) assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result)
def test_load_strategy_invalid_directory(result, caplog): resolver = StrategyResolver() extra_dir = path.join('some', 'path') resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) assert ( 'freqtrade.strategy.resolver', logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples assert 'adx' in resolver.strategy.advise_indicators( result, {'pair': 'ETH/BTC'})
def test_search_strategy(): default_config = {} default_location = path.join(path.dirname(path.realpath(__file__)), '..', '..', 'strategy') assert isinstance( StrategyResolver._search_strategy(default_location, config=default_config, strategy_name='DefaultStrategy'), IStrategy) assert StrategyResolver._search_strategy( default_location, config=default_config, strategy_name='NotFoundStrategy') is None
def test_load_strategy_invalid_directory(result, caplog): resolver = StrategyResolver() extra_dir = os.path.join('some', 'path') resolver._load_strategy('TestStrategy', extra_dir) assert ( 'freqtrade.strategy.resolver', logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result)
def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None: mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError()) conf = deepcopy(default_conf) conf.update({'config': 'config.json.example'}) conf.update({'epochs': 1}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = create_trials(mocker) hyperopt.tickerdata_to_dataframe = MagicMock() hyperopt.start() exists = [ 'Best Result:', 'Sorry, Hyperopt was not able to find good parameters. Please try with more epochs ' '(param: -e).', ] for line in exists: assert line in caplog.text
def test_load_strategy_byte64(result): with open("freqtrade/tests/strategy/test_strategy.py", "r") as file: encoded_string = urlsafe_b64encode( file.read().encode("utf-8")).decode("utf-8") resolver = StrategyResolver( {'strategy': 'TestStrategy:{}'.format(encoded_string)}) assert 'adx' in resolver.strategy.advise_indicators(result, 'ETH/BTC')
def test_call_deprecated_function(result, monkeypatch): default_location = path.join(path.dirname(path.realpath(__file__))) resolver = StrategyResolver({ 'strategy': 'TestStrategyLegacy', 'strategy_path': default_location }) metadata = {'pair': 'ETH/BTC'} # Make sure we are using a legacy function assert resolver.strategy._populate_fun_len == 2 assert resolver.strategy._buy_fun_len == 2 assert resolver.strategy._sell_fun_len == 2 indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata) assert type(indicator_df) is DataFrame assert 'adx' in indicator_df.columns buydf = resolver.strategy.advise_buy(result, metadata=metadata) assert type(buydf) is DataFrame assert 'buy' in buydf.columns selldf = resolver.strategy.advise_sell(result, metadata=metadata) assert type(selldf) is DataFrame assert 'sell' in selldf
def test_deprecate_populate_indicators(result): default_location = path.join(path.dirname(path.realpath(__file__))) resolver = StrategyResolver({ 'strategy': 'TestStrategyLegacy', 'strategy_path': default_location }) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") indicators = resolver.strategy.advise_indicators(result, 'ETH/BTC') assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated - check out the Sample strategy to see the current function headers!" \ in str(w[-1].message) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") resolver.strategy.advise_buy(indicators, 'ETH/BTC') assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated - check out the Sample strategy to see the current function headers!" \ in str(w[-1].message) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") resolver.strategy.advise_sell(indicators, 'ETH_BTC') assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated - check out the Sample strategy to see the current function headers!" \ in str(w[-1].message)
def test_loss_calculation_prefer_correct_trade_count(hyperopt) -> None: StrategyResolver({'strategy': 'DefaultStrategy'}) correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20) over = hyperopt.calculate_loss(1, hyperopt.target_trades + 100, 20) under = hyperopt.calculate_loss(1, hyperopt.target_trades - 100, 20) assert over > correct assert under > correct
def test_strategy_override_minimal_roi(caplog): caplog.set_level(logging.INFO) config = {'strategy': 'DefaultStrategy', 'minimal_roi': {"0": 0.5}} resolver = StrategyResolver(config) assert resolver.strategy.minimal_roi[0] == 0.5 assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'minimal_roi\' with value in config file.' ) in caplog.record_tuples
def test_strategy_override_stoploss(caplog): caplog.set_level(logging.INFO) config = {'strategy': 'DefaultStrategy', 'stoploss': -0.5} resolver = StrategyResolver(config) assert resolver.strategy.stoploss == -0.5 assert ('freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'stoploss\' with value in config file: -0.5.' ) in caplog.record_tuples
def test_strategy_override_ticker_interval(caplog): caplog.set_level(logging.INFO) config = {'strategy': 'DefaultStrategy', 'ticker_interval': 60} resolver = StrategyResolver(config) assert resolver.strategy.ticker_interval == 60 assert ( 'freqtrade.strategy.resolver', logging.INFO, 'Override strategy \'ticker_interval\' with value in config file: 60.' ) in caplog.record_tuples
def test_start_failure(mocker, default_conf, caplog) -> None: start_mock = MagicMock() mocker.patch('freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf) mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) patch_exchange(mocker) args = [ '--config', 'config.json', '--strategy', 'TestStrategy', 'hyperopt', '--epochs', '5' ] args = get_args(args) StrategyResolver({'strategy': 'DefaultStrategy'}) with pytest.raises(ValueError): start(args) assert log_has("Please don't use --strategy for hyperopt.", caplog.record_tuples)
def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> None: trials = create_trials(mocker) conf = deepcopy(default_conf) conf.update({'config': 'config.json.example'}) conf.update({'epochs': 1}) conf.update({'mongodb': False}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) mocker.patch('freqtrade.optimize.hyperopt.os.path.exists', return_value=True) mocker.patch('freqtrade.optimize.hyperopt.len', return_value=len(trials.results)) mock_read = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.read_trials', return_value=trials) mock_save = mocker.patch( 'freqtrade.optimize.hyperopt.Hyperopt.save_trials', return_value=None) mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = trials hyperopt.tickerdata_to_dataframe = MagicMock() hyperopt.start() mock_read.assert_called_once() mock_save.assert_called_once() current_tries = hyperopt.current_tries total_tries = hyperopt.total_tries assert current_tries == len(trials.results) assert total_tries == (current_tries + len(trials.results))
def test_start(mocker, default_conf, caplog) -> None: start_mock = MagicMock() mocker.patch('freqtrade.configuration.Configuration._load_config_file', lambda *args, **kwargs: default_conf) mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) patch_exchange(mocker) args = [ '--config', 'config.json', '--strategy', 'DefaultStrategy', 'hyperopt', '--epochs', '5' ] args = get_args(args) StrategyResolver({'strategy': 'DefaultStrategy'}) start(args) import pprint pprint.pprint(caplog.record_tuples) assert log_has('Starting freqtrade in Hyperopt mode', caplog.record_tuples) assert start_mock.call_count == 1
def test_strategy(result): resolver = StrategyResolver({'strategy': 'DefaultStrategy'}) assert hasattr(resolver.strategy, 'minimal_roi') assert resolver.strategy.minimal_roi[0] == 0.04 assert hasattr(resolver.strategy, 'stoploss') assert resolver.strategy.stoploss == -0.10 assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result) assert hasattr(resolver.strategy, 'populate_buy_trend') dataframe = resolver.strategy.populate_buy_trend( resolver.strategy.populate_indicators(result)) assert 'buy' in dataframe.columns assert hasattr(resolver.strategy, 'populate_sell_trend') dataframe = resolver.strategy.populate_sell_trend( resolver.strategy.populate_indicators(result)) assert 'sell' in dataframe.columns
def test_start(mocker, default_conf, caplog) -> None: """ Test start() function """ start_mock = MagicMock() mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock) mocker.patch('freqtrade.configuration.open', mocker.mock_open(read_data=json.dumps(default_conf))) args = [ '--config', 'config.json', '--strategy', 'DefaultStrategy', 'hyperopt', '--epochs', '5' ] args = get_args(args) StrategyResolver({'strategy': 'DefaultStrategy'}) start(args) import pprint pprint.pprint(caplog.record_tuples) assert log_has('Starting freqtrade in Hyperopt mode', caplog.record_tuples) assert start_mock.call_count == 1
def __init__(self, config: Dict[str, Any]) -> None: """ Init all variables and object the bot need to work :param config: configuration dict, you can use the Configuration.get_config() method to get the config dict. """ logger.info( 'Starting freqtrade %s', __version__, ) # Init bot states self.state = State.STOPPED # Init objects self.config = config self.strategy: IStrategy = StrategyResolver(self.config).strategy self.rpc: RPCManager = RPCManager(self) self.persistence = None self.exchange = Exchange(self.config) self._init_modules()
def test_strategy(result): config = {'strategy': 'DefaultStrategy'} resolver = StrategyResolver(config) metadata = {'pair': 'ETH/BTC'} assert resolver.strategy.minimal_roi[0] == 0.04 assert config["minimal_roi"]['0'] == 0.04 assert resolver.strategy.stoploss == -0.10 assert config['stoploss'] == -0.10 assert resolver.strategy.ticker_interval == '5m' assert config['ticker_interval'] == '5m' df_indicators = resolver.strategy.advise_indicators(result, metadata=metadata) assert 'adx' in df_indicators dataframe = resolver.strategy.advise_buy(df_indicators, metadata=metadata) assert 'buy' in dataframe.columns dataframe = resolver.strategy.advise_sell(df_indicators, metadata=metadata) assert 'sell' in dataframe.columns
def test_load_strategy(result): resolver = StrategyResolver({'strategy': 'TestStrategy'}) metadata = {'pair': 'ETH/BTC'} assert 'adx' in resolver.strategy.advise_indicators(result, metadata=metadata)
def test_load_strategy(result): resolver = StrategyResolver() resolver._load_strategy('TestStrategy') assert hasattr(resolver.strategy, 'populate_indicators') assert 'adx' in resolver.strategy.populate_indicators(result)
def test_fmin_best_results(mocker, default_conf, caplog) -> None: fmin_result = { "macd_below_zero": 0, "adx": 1, "adx-value": 15.0, "fastd": 1, "fastd-value": 40.0, "green_candle": 1, "mfi": 0, "over_sar": 0, "rsi": 1, "rsi-value": 37.0, "trigger": 2, "uptrend_long_ema": 1, "uptrend_short_ema": 0, "uptrend_sma": 0, "stoploss": -0.1, "roi_t1": 1, "roi_t2": 2, "roi_t3": 3, "roi_p1": 1, "roi_p2": 2, "roi_p3": 3, } conf = deepcopy(default_conf) conf.update({'config': 'config.json.example'}) conf.update({'epochs': 1}) conf.update({'timerange': None}) conf.update({'spaces': 'all'}) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf) StrategyResolver({'strategy': 'DefaultStrategy'}) hyperopt = Hyperopt(conf) hyperopt.trials = create_trials(mocker) hyperopt.tickerdata_to_dataframe = MagicMock() hyperopt.start() exists = [ 'Best parameters:', '"adx": {\n "enabled": true,\n "value": 15.0\n },', '"fastd": {\n "enabled": true,\n "value": 40.0\n },', '"green_candle": {\n "enabled": true\n },', '"macd_below_zero": {\n "enabled": false\n },', '"mfi": {\n "enabled": false\n },', '"over_sar": {\n "enabled": false\n },', '"roi_p1": 1.0,', '"roi_p2": 2.0,', '"roi_p3": 3.0,', '"roi_t1": 1.0,', '"roi_t2": 2.0,', '"roi_t3": 3.0,', '"rsi": {\n "enabled": true,\n "value": 37.0\n },', '"stoploss": -0.1,', '"trigger": {\n "type": "faststoch10"\n },', '"uptrend_long_ema": {\n "enabled": true\n },', '"uptrend_short_ema": {\n "enabled": false\n },', '"uptrend_sma": {\n "enabled": false\n }', 'ROI table:\n{0: 6.0, 3.0: 3.0, 5.0: 1.0, 6.0: 0}', 'Best Result:\nfoo' ] for line in exists: assert line in caplog.text
class Analyze(object): """ Analyze class contains everything the bot need to determine if the situation is good for buying or selling. """ def __init__(self, config: dict) -> None: """ Init Analyze :param config: Bot configuration (use the one from Configuration()) """ self.config = config self.strategy = StrategyResolver(self.config).strategy @staticmethod def parse_ticker_dataframe(ticker: list) -> DataFrame: """ Analyses the trend for the given ticker history :param ticker: See exchange.get_ticker_history :return: DataFrame """ columns = {'C': 'close', 'V': 'volume', 'O': 'open', 'H': 'high', 'L': 'low', 'T': 'date'} frame = DataFrame(ticker).rename(columns=columns) if 'BV' in frame: frame.drop('BV', axis=1, inplace=True) frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True) # group by index and aggregate results to eliminate duplicate ticks frame = frame.groupby(by='date', as_index=False, sort=True).agg({ 'close': 'last', 'high': 'max', 'low': 'min', 'open': 'first', 'volume': 'max', }) return frame def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Adds several different TA indicators to the given DataFrame Performance Note: For the best performance be frugal on the number of indicators you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. """ return self.strategy.populate_indicators(dataframe=dataframe) def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame :return: DataFrame with buy column """ return self.strategy.populate_buy_trend(dataframe=dataframe) def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame :return: DataFrame with buy column """ return self.strategy.populate_sell_trend(dataframe=dataframe) def get_ticker_interval(self) -> int: """ Return ticker interval to use :return: Ticker interval value to use """ return self.strategy.ticker_interval def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: """ Parses the given ticker history and returns a populated DataFrame add several TA indicators and buy signal to it :return DataFrame with ticker data and indicator data """ dataframe = self.parse_ticker_dataframe(ticker_history) dataframe = self.populate_indicators(dataframe) dataframe = self.populate_buy_trend(dataframe) dataframe = self.populate_sell_trend(dataframe) return dataframe def get_signal(self, pair: str, interval: int) -> Tuple[bool, bool]: """ Calculates current signal based several technical analysis indicators :param pair: pair in format BTC_ANT or BTC-ANT :param interval: Interval to use (in min) :return: (Buy, Sell) A bool-tuple indicating buy/sell signal """ ticker_hist = get_ticker_history(pair, interval) if not ticker_hist: logger.warning('Empty ticker history for pair %s', pair) return False, False try: dataframe = self.analyze_ticker(ticker_hist) except ValueError as error: logger.warning( 'Unable to analyze ticker for pair %s: %s', pair, str(error) ) return False, False except Exception as error: logger.exception( 'Unexpected error when analyzing ticker for pair %s: %s', pair, str(error) ) return False, False if dataframe.empty: logger.warning('Empty dataframe for pair %s', pair) return False, False latest = dataframe.iloc[-1] # Check if dataframe is out of date signal_date = arrow.get(latest['date']) if signal_date < arrow.utcnow() - timedelta(minutes=(interval + 5)): logger.warning( 'Outdated history for pair %s. Last tick is %s minutes old', pair, (arrow.utcnow() - signal_date).seconds // 60 ) return False, False (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1 logger.debug( 'trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell) ) return buy, sell def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, sell: bool) -> bool: """ This function evaluate if on the condition required to trigger a sell has been reached if the threshold is reached and updates the trade record. :return: True if trade should be sold, False otherwise """ # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee) if self.min_roi_reached(trade=trade, current_rate=rate, current_time=date): logger.debug('Required profit reached. Selling..') return True # Experimental: Check if the trade is profitable before selling it (avoid selling at loss) if self.config.get('experimental', {}).get('sell_profit_only', False): logger.debug('Checking if trade is profitable..') if trade.calc_profit(rate=rate) <= 0: return False if sell and not buy and self.config.get('experimental', {}).get('use_sell_signal', False): logger.debug('Sell signal received. Selling..') return True return False def min_roi_reached(self, trade: Trade, current_rate: float, current_time: datetime) -> bool: """ Based an earlier trade and current price and ROI configuration, decides whether bot should sell :return True if bot should sell at current rate """ current_profit = trade.calc_profit_percent(current_rate) if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: logger.debug('Stop loss hit.') return True # Check if time matches and current rate is above threshold time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60 for duration, threshold in self.strategy.minimal_roi.items(): if time_diff <= duration: return False if current_profit > threshold: return True return False def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: """ Creates a dataframe and populates indicators for given ticker data """ return {pair: self.populate_indicators(self.parse_ticker_dataframe(pair_data)) for pair, pair_data in tickerdata.items()}
def test_dataframe_load(): StrategyResolver({'strategy': 'DefaultStrategy'}) dataframe = load_dataframe_pair(_pairs) assert isinstance(dataframe, pandas.core.frame.DataFrame)
def plot_analyzed_dataframe(args: Namespace) -> None: """ Calls analyze() and plots the returned dataframe :return: None """ global _CONF # Load the configuration _CONF.update(setup_configuration(args)) print(_CONF) # Set the pair to audit pair = args.pair if pair is None: logger.critical('Parameter --pair mandatory;. E.g --pair ETH/BTC') exit() if '/' not in pair: logger.critical('--pair format must be XXX/YYY') exit() # Set timerange to use timerange = Arguments.parse_timerange(args.timerange) # Load the strategy try: strategy = StrategyResolver(_CONF).strategy exchange = Exchange(_CONF) except AttributeError: logger.critical( 'Impossible to load the strategy. Please check the file "user_data/strategies/%s.py"', args.strategy ) exit() # Set the ticker to use tick_interval = strategy.ticker_interval # Load pair tickers tickers = {} if args.live: logger.info('Downloading pair.') tickers[pair] = exchange.get_candle_history(pair, tick_interval) else: tickers = optimize.load_data( datadir=_CONF.get("datadir"), pairs=[pair], ticker_interval=tick_interval, refresh_pairs=_CONF.get('refresh_pairs', False), timerange=timerange, exchange=Exchange(_CONF) ) # No ticker found, or impossible to download if tickers == {}: exit() # Get trades already made from the DB trades = load_trades(args, pair, timerange) dataframes = strategy.tickerdata_to_dataframe(tickers) dataframe = dataframes[pair] dataframe = strategy.advise_buy(dataframe, {'pair': pair}) dataframe = strategy.advise_sell(dataframe, {'pair': pair}) if len(dataframe.index) > args.plot_limit: logger.warning('Ticker contained more than %s candles as defined ' 'with --plot-limit, clipping.', args.plot_limit) dataframe = dataframe.tail(args.plot_limit) trades = trades.loc[trades['opents'] >= dataframe.iloc[0]['date']] fig = generate_graph( pair=pair, trades=trades, data=dataframe, args=args ) plot(fig, filename=str(Path('user_data').joinpath('freqtrade-plot.html')))
def test_dataframe_columns_exists(): StrategyResolver({'strategy': 'DefaultStrategy'}) dataframe = load_dataframe_pair(_pairs) assert 'high' in dataframe.columns assert 'low' in dataframe.columns assert 'close' in dataframe.columns