def main(): """ Loads and validates the config and handles the main loop :return: None """ logger.info('Starting freqtrade %s', __version__) global _CONF with open('config.json') as file: _CONF = json.load(file) logger.info('Validating configuration ...') validate(_CONF, CONF_SCHEMA) init(_CONF) old_state = get_state() logger.info('Initial State: %s', old_state) telegram.send_msg('*Status:* `{}`'.format(old_state.name.lower())) while True: new_state = get_state() # Log state transition if new_state != old_state: telegram.send_msg('*Status:* `{}`'.format(new_state.name.lower())) logging.info('Changing state to: %s', new_state.name) if new_state == State.STOPPED: time.sleep(1) elif new_state == State.RUNNING: _process() # We need to sleep here because otherwise we would run into bittrex rate limit time.sleep(exchange.get_sleep_time()) old_state = new_state
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 test_send_msg_network_error(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock()) default_conf['telegram']['enabled'] = True bot = MagicMock() bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) send_msg('test', bot) # Bot should've tried to send it twice assert len(bot.method_calls) == 2
def test_send_msg_network_error(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock()) default_conf['telegram']['enabled'] = True bot = MagicMock() bot.send_message = MagicMock(side_effect=NetworkError('Oh snap')) send_msg('test', bot) # Bot should've tried to send it twice assert len(bot.method_calls) == 2
def create_trade(stake_amount: float) -> Optional[Trade]: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :param stake_amount: amount of btc to spend """ logger.info( 'Checking buy signals to create a new trade with stake_amount: %f ...', stake_amount) whitelist = copy.deepcopy(_CONF['exchange']['pair_whitelist']) # Check if stake_amount is fulfilled if exchange.get_balance(_CONF['stake_currency']) < stake_amount: raise ValueError('stake amount is not fulfilled (currency={})'.format( _CONF['stake_currency'])) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise ValueError('No pair in whitelist') # Pick pair based on StochRSI buy signals for _pair in whitelist: if get_buy_signal(_pair): pair = _pair break else: return None # Calculate amount and subtract fee fee = exchange.get_fee() buy_limit = get_target_bid(exchange.get_ticker(pair)) amount = (1 - fee) * stake_amount / buy_limit order_id = exchange.buy(pair, buy_limit, amount) # Create trade entity and return message = '*{}:* Buying [{}]({}) with limit `{:.8f}`'.format( exchange.get_name().upper(), pair.replace('_', '/'), exchange.get_pair_detail_url(pair), buy_limit) logger.info(message) telegram.send_msg(message) # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL return Trade(pair=pair, stake_amount=stake_amount, amount=amount, fee=fee * 2, open_rate=buy_limit, open_date=datetime.utcnow(), exchange=exchange.get_name().upper(), open_order_id=order_id)
def test_send_msg(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock()) bot = MagicMock() send_msg('test', bot) assert not bot.method_calls bot.reset_mock() default_conf['telegram']['enabled'] = True send_msg('test', bot) assert len(bot.method_calls) == 1
def test_send_msg(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.multiple('freqtrade.rpc.telegram', _CONF=default_conf, init=MagicMock()) bot = MagicMock() send_msg('test', bot) assert not bot.method_calls bot.reset_mock() default_conf['telegram']['enabled'] = True send_msg('test', bot) assert len(bot.method_calls) == 1
def create_trade(stake_amount: float) -> Optional[Trade]: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :param stake_amount: amount of btc to spend """ logger.info('Creating new trade with stake_amount: %f ...', stake_amount) whitelist = copy.deepcopy(_CONF['exchange']['pair_whitelist']) # Check if stake_amount is fulfilled if exchange.get_balance(_CONF['stake_currency']) < stake_amount: raise ValueError( 'stake amount is not fulfilled (currency={}'.format(_CONF['stake_currency']) ) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) if not whitelist: raise ValueError('No pair in whitelist') # Pick pair based on StochRSI buy signals for _pair in whitelist: if get_buy_signal(_pair): pair = _pair break else: return None open_rate = get_target_bid(exchange.get_ticker(pair)) amount = stake_amount / open_rate order_id = exchange.buy(pair, open_rate, amount) # Create trade entity and return message = '*{}:* Buying [{}]({}) at rate `{:f}`'.format( exchange.EXCHANGE.name.upper(), pair.replace('_', '/'), exchange.get_pair_detail_url(pair), open_rate ) logger.info(message) telegram.send_msg(message) return Trade(pair=pair, stake_amount=stake_amount, open_rate=open_rate, open_date=datetime.utcnow(), amount=amount, exchange=exchange.EXCHANGE.name.upper(), open_order_id=order_id, is_open=True)
def app(config: dict) -> None: """ Main loop which handles the application state :param config: config as dict :return: None """ logger.info('Starting freqtrade %s', __version__) init(config) try: old_state = get_state() logger.info('Initial State: %s', old_state) telegram.send_msg('*Status:* `{}`'.format(old_state.name.lower())) while True: new_state = get_state() # Log state transition if new_state != old_state: telegram.send_msg('*Status:* `{}`'.format( new_state.name.lower())) logging.info('Changing state to: %s', new_state.name) if new_state == State.STOPPED: time.sleep(1) elif new_state == State.RUNNING: _process() # We need to sleep here because otherwise we would run into bittrex rate limit time.sleep(exchange.EXCHANGE.sleep_time) old_state = new_state except RuntimeError: telegram.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format( traceback.format_exc())) logger.exception('RuntimeError. Trader stopped!') finally: telegram.send_msg('*Status:* `Trader has stopped`')
def main(): """ Loads and validates the config and handles the main loop :return: None """ global _CONF args = build_arg_parser().parse_args() # Initialize logger logging.basicConfig( level=args.loglevel, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', ) logger.info('Starting freqtrade %s (loglevel=%s)', __version__, logging.getLevelName(args.loglevel)) # Load and validate configuration with open(args.config) as file: _CONF = json.load(file) if 'internals' not in _CONF: _CONF['internals'] = {} logger.info('Validating configuration ...') validate(_CONF, CONF_SCHEMA) # Initialize all modules and start main loop if args.dynamic_whitelist: logger.info( 'Using dynamically generated whitelist. (--dynamic-whitelist detected)' ) init(_CONF) old_state = None while True: new_state = get_state() # Log state transition if new_state != old_state: telegram.send_msg('*Status:* `{}`'.format(new_state.name.lower())) logger.info('Changing state to: %s', new_state.name) if new_state == State.STOPPED: time.sleep(1) elif new_state == State.RUNNING: throttle( _process, min_secs=_CONF['internals'].get('process_throttle_secs', 10), dynamic_whitelist=args.dynamic_whitelist, ) old_state = new_state
def execute_sell(trade: Trade, current_rate: float) -> None: """ Executes a sell for the given trade and current rate :param trade: Trade instance :param current_rate: current rate :return: None """ # Get available balance currency = trade.pair.split('_')[1] balance = exchange.get_balance(currency) profit = trade.exec_sell_order(current_rate, balance) message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format( trade.exchange, trade.pair.replace('_', '/'), exchange.get_pair_detail_url(trade.pair), trade.close_rate, round(profit, 2)) logger.info(message) telegram.send_msg(message)
def execute_sell(trade: Trade, limit: float) -> None: """ Executes a limit sell for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order :return: None """ # Execute sell and update trade record order_id = exchange.sell(str(trade.pair), limit, trade.amount) trade.open_order_id = order_id fmt_exp_profit = round(trade.calc_profit(limit) * 100, 2) message = '*{}:* Selling [{}]({}) with limit `{:.8f} (profit: ~{:.2f}%)`'.format( trade.exchange, trade.pair.replace('_', '/'), exchange.get_pair_detail_url(trade.pair), limit, fmt_exp_profit) logger.info(message) telegram.send_msg(message)
def execute_sell(trade: Trade, limit: float) -> None: """ Executes a limit sell for the given trade and limit :param trade: Trade instance :param limit: limit rate for the sell order :return: None """ # check how much is available available = exchange.get_available_balance(get_quote_token(str( trade.pair))) # Execute sell and update trade record order_id = exchange.sell(str(trade.pair), limit, available) # trade.amount trade.open_order_id = order_id if available < trade.amount: # cancel the rest # todo: check if wallet is healthy before cancelling health = exchange.get_wallet_health() if exchange.get_name() == 'HitBTC': token = trade.pair else: token = get_quote_token(trade.pair) token_healthy = False for status in health: if status['Currency'] == token: token_healthy = status['IsActive'] if token_healthy: exchange.cancel_order(trade.id) fmt_exp_profit = round(trade.calc_profit(limit) * 100, 2) message = '*{}:* Selling [{}]({}) with limit `{:.8f} (profit: ~{:.2f}%)`'.format( trade.exchange, trade.pair.replace('_', '/'), exchange.get_pair_detail_url(trade.pair), limit, fmt_exp_profit) logger.info(message) telegram.send_msg(message)
def main(config_suffix=None): """ Loads and validates the config and handles the main loop :return: None """ logger.info('Starting freqtrade %s', __version__) global _CONF with open('config' + config_suffix + '.json') as file: _CONF = json.load(file) logger.info('Validating configuration ...') validate(_CONF, CONF_SCHEMA) init(_CONF) # get trade interval from config for main loop trade_interval = _CONF['trade_interval'] # get analyze_method from config for buy signals analyze_method = _CONF['analyze_method'] global analyzer if analyze_method == 'danml': analyzer = DanML() elif analyze_method == 'cryptoml': analyzer = CrytpoML(whitelist=_CONF['exchange']['pair_whitelist']) else: assert analyzer is not None old_state = get_state() logger.info('Initial State: %s', old_state) telegram.send_msg('*Status:* `{}`'.format(old_state.name.lower())) while True: s = datetime.utcnow().second m = datetime.utcnow().minute h = datetime.utcnow().hour if s == 0 and m % trade_interval == 0: new_state = get_state() # Log state transition if new_state != old_state: telegram.send_msg('*Status:* `{}`'.format( new_state.name.lower())) logging.info('Changing state to: %s', new_state.name) if new_state == State.STOPPED: time.sleep(1) elif new_state == State.RUNNING: _process(buying=True) # We need to sleep here because otherwise we would run into bittrex rate limit # time.sleep(exchange.get_sleep_time()) old_state = new_state if s == 30: new_state = get_state() # Log state transition if new_state != old_state: telegram.send_msg('*Status:* `{}`'.format( new_state.name.lower())) logging.info('Changing state to: %s', new_state.name) if new_state == State.STOPPED: time.sleep(1) elif new_state == State.RUNNING: _process(buying=False) # We need to sleep here because otherwise we would run into bittrex rate limit # time.sleep(exchange.get_sleep_time()) old_state = new_state if h == 0: if analyzer.name == 'cryptoml': analyzer.reset_buffer() else: time.sleep(0.1)
def create_trade(stake_amount: float) -> Optional[Trade]: """ Checks the implemented trading indicator(s) for a randomly picked pair, if one pair triggers the buy_signal a new trade record gets created :param stake_amount: amount of btc to spend """ logger.info('Creating new trade with stake_amount: %f ...', stake_amount) whitelist = copy.deepcopy(_CONF['exchange']['pair_whitelist']) # Check if stake_amount is fulfilled if exchange.get_balance(_CONF['stake_currency']) < stake_amount: raise ValueError('stake amount is not fulfilled (currency={})'.format( _CONF['stake_currency'])) # Remove currently opened and latest pairs from whitelist for trade in Trade.query.filter(Trade.is_open.is_(True)).all(): if trade.pair in whitelist: whitelist.remove(trade.pair) logger.debug('Ignoring %s in pair whitelist', trade.pair) # if not whitelist: # raise ValueError('No pair in whitelist') # Pick pair based on StochRSI buy signals if analyzer.name == 'danml': for _pair in whitelist: if get_buy_signal(_pair, exchange.get_name(), analyzer): pair = _pair break else: return None elif analyzer.name == 'cryptoml': update = False if datetime.utcnow().minute % 5 == 0: update = True pair = analyzer.get_buy_signal(whitelist, update=update, threshold=0.01, repeats=3) if pair is None: return None # Calculate amount and subtract fee fee = exchange.get_fee() buy_limit = get_target_bid(exchange.get_ticker(pair)) amount = (1 - fee) * stake_amount / buy_limit health = exchange.get_wallet_health() if exchange.get_name() == 'HitBTC': token = pair else: token = get_quote_token(pair) token_healthy = False for status in health: if status['Currency'] == token: token_healthy = status['IsActive'] if token_healthy: order_id = exchange.buy(pair, buy_limit, amount) # Create trade entity and return message = '*{}:* Buying [{}]({}) with limit `{:.8f}`'.format( exchange.get_name().upper(), pair.replace('_', '/'), exchange.get_pair_detail_url(pair), buy_limit) logger.info(message) telegram.send_msg(message) # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL return Trade( pair=pair, stake_amount=stake_amount, amount=amount, fee=fee * 2., open_rate=buy_limit, open_date=datetime.utcnow(), exchange=exchange.get_name().upper(), open_order_id=order_id, # open_order_type='buy' )