def test_get_order(default_conf, mocker): default_conf['dry_run'] = True mocker.patch.dict('freqtrade.exchange._CONF', default_conf) order = MagicMock() order.myid = 123 exchange._DRY_RUN_OPEN_ORDERS['X'] = order print(exchange.get_order('X')) assert exchange.get_order('X').myid == 123 default_conf['dry_run'] = False mocker.patch.dict('freqtrade.exchange._CONF', default_conf) api_mock = MagicMock() api_mock.get_order = MagicMock(return_value=456) mocker.patch('freqtrade.exchange._API', api_mock) assert exchange.get_order('X') == 456
def check_handle_timedout(self, timeoutvalue: int) -> None: """ Check if any orders are timed out and cancel if neccessary :param timeoutvalue: Number of minutes until order is considered timed out :return: None """ timeoutthreashold = arrow.utcnow().shift( minutes=-timeoutvalue).datetime for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all(): try: order = exchange.get_order(trade.open_order_id) except requests.exceptions.RequestException: logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc()) continue ordertime = arrow.get(order['opened']) # Check if trade is still actually open if int(order['remaining']) == 0: continue if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold: self.handle_timedout_limit_buy(trade, order) elif order[ 'type'] == "LIMIT_SELL" and ordertime < timeoutthreashold: self.handle_timedout_limit_sell(trade, order)
def _status(bot: Bot, update: Update) -> None: """ Handler for /status. Returns the current TradeThread status :param bot: telegram bot :param update: message update :return: None """ # Check if additional parameters are passed params = update.message.text.replace('/status', '').split(' ') \ if update.message.text else [] if 'table' in params: _status_table(bot, update) return # Fetch open trade trades = Trade.query.filter(Trade.is_open.is_(True)).all() if get_state() != State.RUNNING: send_msg('*Status:* `trader is not running`', bot=bot) elif not trades: send_msg('*Status:* `no active trade`', bot=bot) else: for trade in trades: order = None if trade.open_order_id: order = exchange.get_order(trade.open_order_id) # calculate profit and send message to user current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = '{:.2f}%'.format( round(trade.close_profit * 100, 2) ) if trade.close_profit else None message = """ *Trade ID:* `{trade_id}` *Current Pair:* [{pair}]({market_url}) *Open Since:* `{date}` *Amount:* `{amount}` *Open Rate:* `{open_rate:.8f}` *Close Rate:* `{close_rate}` *Current Rate:* `{current_rate:.8f}` *Close Profit:* `{close_profit}` *Current Profit:* `{current_profit:.2f}%` *Open Order:* `{open_order}` """.format( trade_id=trade.id, pair=trade.pair, market_url=exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date).humanize(), open_rate=trade.open_rate, close_rate=trade.close_rate, current_rate=current_rate, amount=round(trade.amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), open_order='({} rem={:.8f})'.format( order['type'], order['remaining'] ) if order else None, ) send_msg(message, bot=bot)
def _status(bot: Bot, update: Update) -> None: """ Handler for /status. Returns the current TradeThread status :param bot: telegram bot :param update: message update :return: None """ # Check if additional parameters are passed params = update.message.text.replace('/status', '').split(' ') \ if update.message.text else [] if 'table' in params: _status_table(bot, update) return # Fetch open trade trades = Trade.query.filter(Trade.is_open.is_(True)).all() if get_state() != State.RUNNING: send_msg('*Status:* `trader is not running`', bot=bot) elif not trades: send_msg('*Status:* `no active trade`', bot=bot) else: for trade in trades: order = None if trade.open_order_id: order = exchange.get_order(trade.open_order_id) # calculate profit and send message to user current_rate = exchange.get_ticker(trade.pair)['bid'] current_profit = trade.calc_profit(current_rate) fmt_close_profit = '{:.2f}%'.format( round(trade.close_profit * 100, 2) ) if trade.close_profit else None message = """ *Trade ID:* `{trade_id}` *Current Pair:* [{pair}]({market_url}) *Open Since:* `{date}` *Amount:* `{amount}` *Open Rate:* `{open_rate:.8f}` *Close Rate:* `{close_rate}` *Current Rate:* `{current_rate:.8f}` *Close Profit:* `{close_profit}` *Current Profit:* `{current_profit:.2f}%` *Open Order:* `{open_order}` """.format( trade_id=trade.id, pair=trade.pair, market_url=exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date).humanize(), open_rate=trade.open_rate, close_rate=trade.close_rate, current_rate=current_rate, amount=round(trade.amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), open_order='{} ({})'.format( order['remaining'], order['type'] ) if order else None, ) send_msg(message, bot=bot)
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 rpc_trade_status(self) -> Tuple[bool, Any]: """ Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is a remotely exposed function :return: """ # Fetch open trade trades = Trade.query.filter(Trade.is_open.is_(True)).all() if self.freqtrade.state != State.RUNNING: return True, '*Status:* `trader is not running`' elif not trades: return True, '*Status:* `no active trade`' else: result = [] for trade in trades: order = None if trade.open_order_id: order = exchange.get_order(trade.open_order_id) # calculate profit and send message to user current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = '{:.2f}%'.format( round(trade.close_profit * 100, 2)) if trade.close_profit else None message = "*Trade ID:* `{trade_id}`\n" \ "*Current Pair:* [{pair}]({market_url})\n" \ "*Open Since:* `{date}`\n" \ "*Amount:* `{amount}`\n" \ "*Open Rate:* `{open_rate:.8f}`\n" \ "*Close Rate:* `{close_rate}`\n" \ "*Current Rate:* `{current_rate:.8f}`\n" \ "*Close Profit:* `{close_profit}`\n" \ "*Current Profit:* `{current_profit:.2f}%`\n" \ "*Open Order:* `{open_order}`"\ .format( trade_id=trade.id, pair=trade.pair, market_url=exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date).humanize(), open_rate=trade.open_rate, close_rate=trade.close_rate, current_rate=current_rate, amount=round(trade.amount, 8), close_profit=fmt_close_profit, current_profit=round(current_profit * 100, 2), open_order='({} rem={:.8f})'.format( order['type'], order['remaining'] ) if order else None, ) result.append(message) return False, result
def process_maybe_execute_sell(self, trade: Trade) -> bool: """ Tries to execute a sell trade :return: True if executed """ # Get order details for actual price per unit if trade.open_order_id: # Update trade with order values logger.info('Found 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 return self.handle_trade(trade) return False
def handle_trade(trade: Trade) -> bool: """ Sells the current pair if the threshold is reached and updates the trade record. :return: True if trade has been sold, False otherwise """ if not trade.is_open: raise ValueError('attempt to handle closed trade: {}'.format(trade)) logger.debug('Handling %s ...', trade) # first determine if buy order that is still open if trade.open_order_id: order_info = exchange.get_order(trade.open_order_id) order_type = order_info['type'] if order_type == 'LIMIT_BUY': max_open_time = 10 current_time = datetime.utcnow() amount_minutes_open = (current_time - trade.open_date).total_seconds() / 60. if amount_minutes_open > max_open_time: 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: logger.debug('Cancelling %s ...', trade) exchange.cancel_order(trade.open_order_id) # trade.is_open = 0 # trade.open_order_id = None Trade.session.delete(trade) else: logger.debug( 'Cancelling could not execute due to wallet heath for %s ...', trade) return False current_rate = exchange.get_ticker(trade.pair)['bid'] # ask? if current_rate is None: return False if should_sell(trade, current_rate, datetime.utcnow()): execute_sell(trade, current_rate) return True return False
def check_handle_timedout(timeoutvalue: int) -> None: """ Check if any orders are timed out and cancel if neccessary :param timeoutvalue: Number of minutes until order is considered timed out :return: None """ timeoutthreashold = arrow.utcnow().shift(minutes=-timeoutvalue).datetime for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all(): order = exchange.get_order(trade.open_order_id) ordertime = arrow.get(order['opened']) if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold: # Buy timeout - cancel order exchange.cancel_order(trade.open_order_id) if order['remaining'] == order['amount']: # if trade is not partially completed, just delete the trade Trade.session.delete(trade) Trade.session.flush() logger.info('Buy order timeout for %s.', trade) else: # if trade is partially complete, edit the stake details for the trade # and close the order trade.amount = order['amount'] - order['remaining'] trade.stake_amount = trade.amount * trade.open_rate trade.open_order_id = None logger.info('Partial buy order timeout for %s.', trade) elif order['type'] == "LIMIT_SELL" and ordertime < timeoutthreashold: # Sell timeout - cancel order and update trade if order['remaining'] == order['amount']: # if trade is not partially completed, just cancel the trade exchange.cancel_order(trade.open_order_id) trade.close_rate = None trade.close_profit = None trade.close_date = None trade.is_open = True trade.open_order_id = None logger.info('Sell order timeout for %s.', trade) return True else: # TODO: figure out how to handle partially complete sell orders pass
def _exec_forcesell(trade: Trade) -> None: # Check if there is there is an open order if trade.open_order_id: order = exchange.get_order(trade.open_order_id) # Cancel open LIMIT_BUY orders and close trade if order and not order['closed'] and order['type'] == 'LIMIT_BUY': exchange.cancel_order(trade.open_order_id) trade.close(order.get('rate') or trade.open_rate) # TODO: sell amount which has been bought already return # Ignore trades with an attached LIMIT_SELL order if order and not order['closed'] and order['type'] == 'LIMIT_SELL': return # Get current rate and execute sell current_rate = exchange.get_ticker(trade.pair, False)['bid'] from freqtrade.main import execute_sell execute_sell(trade, current_rate)
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