def test_count_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value='mocked_order_id')) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) _count(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() update_state(State.RUNNING) # Create some test data create_trade(0.001) msg_mock.reset_mock() _count(bot=MagicMock(), update=update) msg = '<pre> current max\n--------- -----\n 1 {}</pre>'.format( default_conf['max_open_trades'] ) assert msg in msg_mock.call_args_list[0][0][0]
def test_status_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) _status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'trader is not running' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() update_state(State.RUNNING) _status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active trade' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data create_trade(0.001) # Trigger status while we have a fulfilled order for the open trade _status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0]
def test_count_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch.multiple( 'freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value='mocked_order_id')) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) _count(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() update_state(State.RUNNING) # Create some test data Trade.session.add(create_trade(15.0)) Trade.session.flush() msg_mock.reset_mock() _count(bot=MagicMock(), update=update) msg = '<pre> current max\n--------- -----\n 1 {}</pre>'.format( default_conf['max_open_trades'] ) assert msg in msg_mock.call_args_list[0][0][0]
def _process(dynamic_whitelist: Optional[bool] = False) -> bool: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. :param: dynamic_whitelist: True is a dynamic whitelist should be generated (optional) :return: True if a trade has been created or closed, False otherwise """ state_changed = False try: # Refresh whitelist based on wallet maintenance refresh_whitelist( gen_pair_whitelist(_CONF['stake_currency'] ) if dynamic_whitelist else None) # Query trades from persistence layer trades = Trade.query.filter(Trade.is_open.is_(True)).all() if len(trades) < _CONF['max_open_trades']: try: # Create entity and execute trade trade = create_trade(float(_CONF['stake_amount'])) if trade: Trade.session.add(trade) state_changed = True else: logger.info( 'Checked all whitelisted currencies. ' 'Found no suitable entry positions for buying. Will keep looking ...' ) except ValueError: logger.exception('Unable to create trade') for trade in trades: # Get order details for actual price per unit if trade.open_order_id: # Update trade with order values logger.info('Got open order for %s', trade) trade.update(exchange.get_order(trade.open_order_id)) if not close_trade_if_fulfilled(trade): # Check if we can sell our current pair state_changed = handle_trade(trade) or state_changed Trade.session.flush() except (requests.exceptions.RequestException, json.JSONDecodeError) as error: msg = 'Got {} in _process(), retrying in 30 seconds...'.format( error.__class__.__name__) logger.exception(msg) time.sleep(30) except RuntimeError: telegram.send_msg( '*Status:* Got RuntimeError:\n```\n{traceback}```{hint}'.format( traceback=traceback.format_exc(), hint='Issue `/start` if you think it is safe to restart.')) logger.exception('Got RuntimeError. Stopping trader ...') update_state(State.STOPPED) return state_changed
def cleanup(*args, **kwargs) -> None: """ Cleanup the application state und finish all pending tasks :return: None """ telegram.send_msg('*Status:* `Stopping trader...`') logger.info('Stopping trader and cleaning up modules...') update_state(State.STOPPED) persistence.cleanup() telegram.cleanup() exit(0)
def cleanup() -> None: """ Cleanup the application state und finish all pending tasks :return: None """ rpc.send_msg('*Status:* `Stopping trader...`') logger.info('Stopping trader and cleaning up modules...') update_state(State.STOPPED) persistence.cleanup() rpc.cleanup() exit(0)
def _start(bot: Bot, update: Update) -> None: """ Handler for /start. Starts TradeThread :param bot: telegram bot :param update: message update :return: None """ if get_state() == State.RUNNING: send_msg('*Status:* `already running`', bot=bot) else: update_state(State.RUNNING)
def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) mocker.patch.multiple( 'freqtrade.fiat_convert.Pymarketcap', ticker=MagicMock(return_value={'price_usd': 15000.0}), _cache_symbols=MagicMock(return_value={'BTC': 1})) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data create_trade(0.001) trade = Trade.query.first() assert trade # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False # Try valid data update.message.text = '/daily 2' _daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Daily' in msg_mock.call_args_list[0][0][0] assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() update_state(State.RUNNING) update.message.text = '/daily -2' _daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][ 0][0]
def _stop(bot: Bot, update: Update) -> None: """ Handler for /stop. Stops TradeThread :param bot: telegram bot :param update: message update :return: None """ if get_state() == State.RUNNING: send_msg('`Stopping trader ...`', bot=bot) update_state(State.STOPPED) else: send_msg('*Status:* `already stopped`', bot=bot)
def test_daily_handle( default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', ticker=MagicMock(return_value={'price_usd': 15000.0}), _cache_symbols=MagicMock(return_value={'BTC': 1})) mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data create_trade(0.001) trade = Trade.query.first() assert trade # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False # Try valid data update.message.text = '/daily 2' _daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Daily' in msg_mock.call_args_list[0][0][0] assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0] assert str(' 0.00006217 BTC') in msg_mock.call_args_list[0][0][0] assert str(' 0.933 USD') in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() update_state(State.RUNNING) update.message.text = '/daily -2' _daily(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
def test_start_handle(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', _CONF=default_conf, init=MagicMock()) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) assert get_state() == State.STOPPED _start(bot=MagicMock(), update=update) assert get_state() == State.RUNNING assert msg_mock.call_count == 0
def test_performance_handle_invalid(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock()) init(default_conf, create_engine('sqlite://')) # Trader is not running update_state(State.STOPPED) _performance(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0]
def test_stop_handle_already_stopped(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', _CONF=default_conf, init=MagicMock()) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) assert get_state() == State.STOPPED _stop(bot=MagicMock(), update=update) assert get_state() == State.STOPPED assert msg_mock.call_count == 1 assert 'already stopped' in msg_mock.call_args_list[0][0][0]
def test_start_handle_already_running(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.main.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', _CONF=default_conf, init=MagicMock()) init(default_conf, create_engine('sqlite://')) update_state(State.RUNNING) assert get_state() == State.RUNNING _start(bot=MagicMock(), update=update) assert get_state() == State.RUNNING assert msg_mock.call_count == 1 assert 'already running' in msg_mock.call_args_list[0][0][0]
def init(config: dict, db_url: Optional[str] = None) -> None: """ Initializes all modules and updates the config :param config: config as dict :param db_url: database connector string for sqlalchemy (Optional) :return: None """ # Initialize all modules rpc.init(config) persistence.init(config, db_url) exchange.init(config) # Set initial application state initial_state = config.get('initial_state') if initial_state: update_state(State[initial_state.upper()]) else: update_state(State.STOPPED)
def init(config: dict, db_url: Optional[str] = None) -> None: """ Initializes all modules and updates the config :param config: config as dict :param db_url: database connector string for sqlalchemy (Optional) :return: None """ # Initialize all modules telegram.init(config) persistence.init(config, db_url) exchange.init(config) # Set initial application state initial_state = config.get('initial_state') if initial_state: update_state(State[initial_state.upper()]) else: update_state(State.STOPPED)
def test_stop_handle(conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', conf) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.main.telegram', _CONF=conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', _CONF=conf, init=MagicMock()) init(conf, 'sqlite://') update_state(State.RUNNING) assert get_state() == State.RUNNING _stop(bot=MagicBot(), update=update) assert get_state() == State.STOPPED assert msg_mock.call_count == 1 assert 'Stopping trader' in msg_mock.call_args_list[0][0][0]
def test_status_table_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple( 'freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value='mocked_order_id')) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) _status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'trader is not running' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() update_state(State.RUNNING) _status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active order' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data trade = create_trade(15.0) assert trade Trade.session.add(trade) Trade.session.flush() _status_table(bot=MagicMock(), update=update) text = re.sub('</?pre>', '', msg_mock.call_args_list[-1][0][0]) line = text.split("\n") fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 assert fields[1] == 'BTC_ETH' assert msg_mock.call_count == 1
def test_forcesell_handle_invalid(default_conf, update, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock()) init(default_conf, create_engine('sqlite://')) # Trader is not running update_state(State.STOPPED) update.message.text = '/forcesell 1' _forcesell(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] # No argument msg_mock.reset_mock() update_state(State.RUNNING) update.message.text = '/forcesell' _forcesell(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Invalid argument' in msg_mock.call_args_list[0][0][0] # Invalid argument msg_mock.reset_mock() update_state(State.RUNNING) update.message.text = '/forcesell 123456' _forcesell(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Invalid argument.' in msg_mock.call_args_list[0][0][0]
def init(config: dict, db_url: Optional[str] = None) -> None: """ Initializes all modules and updates the config :param config: config as dict :param db_url: database connector string for sqlalchemy (Optional) :return: None """ # Initialize all modules rpc.init(config) persistence.init(config, db_url) exchange.init(config) # Set initial application state initial_state = config.get('initial_state') if initial_state: update_state(State[initial_state.upper()]) else: update_state(State.STOPPED) # Register signal handlers for sig in (SIGINT, SIGTERM, SIGABRT): signal(sig, cleanup)
def test_status_table_handle(default_conf, update, ticker, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) msg_mock = MagicMock() mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch.multiple( 'freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value='mocked_order_id')) init(default_conf, create_engine('sqlite://')) update_state(State.STOPPED) _status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'trader is not running' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() update_state(State.RUNNING) _status_table(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'no active order' in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data create_trade(15.0) _status_table(bot=MagicMock(), update=update) text = re.sub('</?pre>', '', msg_mock.call_args_list[-1][0][0]) line = text.split("\n") fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 assert fields[1] == 'BTC_ETH' assert msg_mock.call_count == 1
def _process(nb_assets: Optional[int] = 0) -> bool: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. :param: nb_assets: the maximum number of pairs to be traded at the same time :return: True if a trade has been created or closed, False otherwise """ state_changed = False try: # Refresh whitelist based on wallet maintenance sanitized_list = refresh_whitelist( gen_pair_whitelist(_CONF['stake_currency']) if nb_assets else _CONF['exchange']['pair_whitelist']) # Keep only the subsets of pairs wanted (up to nb_assets) final_list = sanitized_list[:nb_assets] if nb_assets else sanitized_list _CONF['exchange']['pair_whitelist'] = final_list # Query trades from persistence layer trades = Trade.query.filter(Trade.is_open.is_(True)).all() if len(trades) < _CONF['max_open_trades']: try: # Create entity and execute trade state_changed = create_trade(float(_CONF['stake_amount'])) if not state_changed: logger.info( 'Checked all whitelisted currencies. ' 'Found no suitable entry positions for buying. Will keep looking ...' ) except DependencyException as exception: logger.warning('Unable to create trade: %s', exception) for trade in trades: # Get order details for actual price per unit if trade.open_order_id: # Update trade with order values logger.info('Got open order for %s', trade) trade.update(exchange.get_order(trade.open_order_id)) if trade.is_open and trade.open_order_id is None: # Check if we can sell our current pair state_changed = handle_trade(trade) or state_changed if 'unfilledtimeout' in _CONF: # Check and handle any timed out open orders check_handle_timedout(_CONF['unfilledtimeout']) Trade.session.flush() except (requests.exceptions.RequestException, json.JSONDecodeError) as error: logger.warning('Got %s in _process(), retrying in 30 seconds...', error) time.sleep(30) except OperationalException: rpc.send_msg( '*Status:* Got OperationalException:\n```\n{traceback}```{hint}'. format(traceback=traceback.format_exc(), hint='Issue `/start` if you think it is safe to restart.')) logger.exception('Got OperationalException. Stopping trader ...') update_state(State.STOPPED) return state_changed
def _process(nb_assets: Optional[int] = 0) -> bool: """ Queries the persistence layer for open trades and handles them, otherwise a new trade is created. :param: nb_assets: the maximum number of pairs to be traded at the same time :return: True if a trade has been created or closed, False otherwise """ state_changed = False try: # Refresh whitelist based on wallet maintenance sanitized_list = refresh_whitelist( gen_pair_whitelist( _CONF['stake_currency'] ) if nb_assets else _CONF['exchange']['pair_whitelist'] ) # Keep only the subsets of pairs wanted (up to nb_assets) final_list = sanitized_list[:nb_assets] if nb_assets else sanitized_list _CONF['exchange']['pair_whitelist'] = final_list # Query trades from persistence layer trades = Trade.query.filter(Trade.is_open.is_(True)).all() if len(trades) < _CONF['max_open_trades']: try: # Create entity and execute trade state_changed = create_trade(float(_CONF['stake_amount'])) if not state_changed: logger.info( 'Checked all whitelisted currencies. ' 'Found no suitable entry positions for buying. Will keep looking ...' ) except DependencyException as exception: logger.warning('Unable to create trade: %s', exception) for trade in trades: # Get order details for actual price per unit if trade.open_order_id: # Update trade with order values logger.info('Got open order for %s', trade) trade.update(exchange.get_order(trade.open_order_id)) if trade.is_open and trade.open_order_id is None: # Check if we can sell our current pair state_changed = handle_trade(trade) or state_changed if 'unfilledtimeout' in _CONF: # Check and handle any timed out open orders check_handle_timedout(_CONF['unfilledtimeout']) Trade.session.flush() except (requests.exceptions.RequestException, json.JSONDecodeError) as error: logger.warning( 'Got %s in _process(), retrying in 30 seconds...', error ) time.sleep(30) except OperationalException: rpc.send_msg('*Status:* Got OperationalException:\n```\n{traceback}```{hint}'.format( traceback=traceback.format_exc(), hint='Issue `/start` if you think it is safe to restart.' )) logger.exception('Got OperationalException. Stopping trader ...') update_state(State.STOPPED) return state_changed