Esempio n. 1
0
def test_get_ticker(default_conf, mocker):
    maybe_init_api(default_conf, mocker)
    api_mock = MagicMock()
    tick = {
        "success": True,
        'result': {
            'Bid': 0.00001098,
            'Ask': 0.00001099,
            'Last': 0.0001
        }
    }
    api_mock.get_ticker = MagicMock(return_value=tick)
    mocker.patch('freqtrade.exchange.bittrex._API', api_mock)

    # retrieve original ticker
    ticker = get_ticker(pair='BTC_ETH')
    assert ticker['bid'] == 0.00001098
    assert ticker['ask'] == 0.00001099

    # change the ticker
    tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
    api_mock.get_ticker = MagicMock(return_value=tick)
    mocker.patch('freqtrade.exchange.bittrex._API', api_mock)

    # if not caching the result we should get the same ticker
    # if not fetching a new result we should get the cached ticker
    ticker = get_ticker(pair='BTC_ETH', refresh=False)
    assert ticker['bid'] == 0.00001098
    assert ticker['ask'] == 0.00001099

    # force ticker refresh
    ticker = get_ticker(pair='BTC_ETH', refresh=True)
    assert ticker['bid'] == 0.5
    assert ticker['ask'] == 1
Esempio n. 2
0
def test_get_ticker(mocker, ticker):

    api_mock = MagicMock()
    tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
    api_mock.get_ticker = MagicMock(return_value=tick)
    mocker.patch('freqtrade.exchange.bittrex._API', api_mock)

    # retrieve original ticker
    ticker = get_ticker(pair='BTC_ETH')
    assert ticker['bid'] == 0.00001098
    assert ticker['ask'] == 0.00001099

    # change the ticker
    tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
    api_mock.get_ticker = MagicMock(return_value=tick)
    mocker.patch('freqtrade.exchange.bittrex._API', api_mock)

    # if not caching the result we should get the same ticker
    ticker = get_ticker(pair='BTC_ETH', refresh=False)
    assert ticker['bid'] == 0.00001098
    assert ticker['ask'] == 0.00001099

    # force ticker refresh
    ticker = get_ticker(pair='BTC_ETH', refresh=True)
    assert ticker['bid'] == 0.5
    assert ticker['ask'] == 1
Esempio n. 3
0
def _forcesell(bot: Bot, update: Update) -> None:
    """
    Handler for /forcesell <id>.
    Sells the given trade at current price
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    if get_state() != State.RUNNING:
        send_msg('`trader is not running`', bot=bot)
        return

    trade_id = update.message.text.replace('/forcesell', '').strip()
    if trade_id == 'all':
        # Execute sell for all open orders
        for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
            # Get current rate
            current_rate = exchange.get_ticker(trade.pair)['bid']
            from freqtrade.main import execute_sell
            execute_sell(trade, current_rate)
        return

    # Query for trade
    trade = Trade.query.filter(
        and_(Trade.id == trade_id, Trade.is_open.is_(True))).first()
    if not trade:
        send_msg('Invalid argument. See `/help` to view usage')
        logger.warning('/forcesell: Invalid argument received')
        return
    # Get current rate
    current_rate = exchange.get_ticker(trade.pair)['bid']
    from freqtrade.main import execute_sell
    execute_sell(trade, current_rate)
Esempio n. 4
0
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)
Esempio n. 5
0
def _status_table(bot: Bot, update: Update) -> None:
    """
    Handler for /status table.
    Returns the current TradeThread status in table format
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    # 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 order`', bot=bot)
    else:
        trades_list = []
        for trade in trades:
            # calculate profit and send message to user
            current_rate = exchange.get_ticker(trade.pair)['bid']
            trades_list.append([
                trade.id, trade.pair,
                shorten_date(
                    arrow.get(trade.open_date).humanize(only_distance=True)),
                '{:.2f}'.format(100 * trade.calc_profit(current_rate))
            ])

        columns = ['ID', 'Pair', 'Since', 'Profit']
        df_statuses = DataFrame.from_records(trades_list, columns=columns)
        df_statuses = df_statuses.set_index(columns[0])

        message = tabulate(df_statuses, headers='keys', tablefmt='simple')
        message = "<pre>{}</pre>".format(message)

        send_msg(message, parse_mode=ParseMode.HTML)
Esempio n. 6
0
    def handle_trade(self, 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)
        current_rate = exchange.get_ticker(trade.pair)['bid']

        (buy, sell) = (False, False)

        if self.config.get('experimental', {}).get('use_sell_signal'):
            (buy, sell) = self.analyze.get_signal(
                trade.pair, self.analyze.get_ticker_interval())

        if self.analyze.should_sell(trade, current_rate, datetime.utcnow(),
                                    buy, sell):
            self.execute_sell(trade, current_rate)
            return True
        logger.info(
            'Found no sell signals for whitelisted currencies. Trying again..')
        return False
Esempio n. 7
0
def _profit(bot: Bot, update: Update) -> None:
    """
    Handler for /profit.
    Returns a cumulative profit statistics.
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    trades = Trade.query.order_by(Trade.id).all()

    profit_amounts = []
    profits = []
    durations = []
    for trade in trades:
        if not trade.open_rate:
            continue
        if trade.close_date:
            durations.append(
                (trade.close_date - trade.open_date).total_seconds())
        if trade.close_profit:
            profit = trade.close_profit
        else:
            # Get current rate
            current_rate = exchange.get_ticker(trade.pair)['bid']
            profit = trade.calc_profit(current_rate)

        profit_amounts.append(profit * trade.stake_amount)
        profits.append(profit)

    best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \
        .filter(Trade.is_open.is_(False)) \
        .group_by(Trade.pair) \
        .order_by(text('profit_sum DESC')) \
        .first()

    if not best_pair:
        send_msg('*Status:* `no closed trade`', bot=bot)
        return

    bp_pair, bp_rate = best_pair
    markdown_msg = """
*ROI:* `{profit_btc:.8f} ({profit:.2f}%)`
*Trade Count:* `{trade_count}`
*First Trade opened:* `{first_trade_date}`
*Latest Trade opened:* `{latest_trade_date}`
*Avg. Duration:* `{avg_duration}`
*Best Performing:* `{best_pair}: {best_rate:.2f}%`
    """.format(
        profit_btc=round(sum(profit_amounts), 8),
        profit=round(sum(profits) * 100, 2),
        trade_count=len(trades),
        first_trade_date=arrow.get(trades[0].open_date).humanize(),
        latest_trade_date=arrow.get(trades[-1].open_date).humanize(),
        avg_duration=str(
            timedelta(seconds=sum(durations) /
                      float(len(durations)))).split('.')[0],
        best_pair=bp_pair,
        best_rate=round(bp_rate * 100, 2),
    )
    send_msg(markdown_msg, bot=bot)
Esempio n. 8
0
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)
    current_rate = exchange.get_ticker(trade.pair)['bid']

    # Check if minimal roi has been reached
    if min_roi_reached(trade, current_rate, datetime.utcnow()):
        logger.debug('Executing sell due to ROI ...')
        execute_sell(trade, current_rate)
        return True

    # Experimental: Check if sell signal has been enabled and triggered
    if _CONF.get('experimental', {}).get('use_sell_signal'):
        # Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
        if _CONF.get('experimental', {}).get('sell_profit_only'):
            logger.debug('Checking if trade is profitable ...')
            if trade.calc_profit(rate=current_rate) <= 0:
                return False
        logger.debug('Checking sell_signal ...')
        if get_signal(trade.pair, SignalType.SELL):
            logger.debug('Executing sell due to sell signal ...')
            execute_sell(trade, current_rate)
            return True

    return False
Esempio n. 9
0
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)
Esempio n. 10
0
def _status_table(bot: Bot, update: Update) -> None:
    """
    Handler for /status table.
    Returns the current TradeThread status in table format
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    # 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 order`', bot=bot)
    else:
        trades_list = []
        for trade in trades:
            # calculate profit and send message to user
            current_rate = exchange.get_ticker(trade.pair, False)['bid']
            trades_list.append([
                trade.id,
                trade.pair,
                shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
                '{:.2f}%'.format(100 * trade.calc_profit_percent(current_rate))
            ])

        columns = ['ID', 'Pair', 'Since', 'Profit']
        df_statuses = DataFrame.from_records(trades_list, columns=columns)
        df_statuses = df_statuses.set_index(columns[0])

        message = tabulate(df_statuses, headers='keys', tablefmt='simple')
        message = "<pre>{}</pre>".format(message)

        send_msg(message, parse_mode=ParseMode.HTML)
Esempio n. 11
0
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)
    current_rate = exchange.get_ticker(trade.pair)['bid']

    # Check if minimal roi has been reached
    if min_roi_reached(trade, current_rate, datetime.utcnow()):
        logger.debug('Executing sell due to ROI ...')
        execute_sell(trade, current_rate)
        return True

    # Experimental: Check if sell signal has been enabled and triggered
    if _CONF.get('experimental', {}).get('use_sell_signal'):
        # Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
        if _CONF.get('experimental', {}).get('sell_profit_only'):
            logger.debug('Checking if trade is profitable ...')
            if trade.calc_profit(rate=current_rate) <= 0:
                return False
        logger.debug('Checking sell_signal ...')
        if get_signal(trade.pair, SignalType.SELL):
            logger.debug('Executing sell due to sell signal ...')
            execute_sell(trade, current_rate)
            return True

    return False
Esempio n. 12
0
    def rpc_status_table(self) -> Tuple[bool, Any]:
        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 order`'
        else:
            trades_list = []
            for trade in trades:
                # calculate profit and send message to user
                current_rate = exchange.get_ticker(trade.pair, False)['bid']
                trades_list.append([
                    trade.id, trade.pair,
                    shorten_date(
                        arrow.get(
                            trade.open_date).humanize(only_distance=True)),
                    '{:.2f}%'.format(100 *
                                     trade.calc_profit_percent(current_rate))
                ])

            columns = ['ID', 'Pair', 'Since', 'Profit']
            df_statuses = DataFrame.from_records(trades_list, columns=columns)
            df_statuses = df_statuses.set_index(columns[0])
            # The style used throughout is to return a tuple
            # consisting of (error_occured?, result)
            # Another approach would be to just return the
            # result, or raise error
            return False, df_statuses
Esempio n. 13
0
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
    """
    # 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 order`', bot=bot)
    else:
        for trade in trades:
            # calculate profit and send message to user
            current_rate = exchange.get_ticker(trade.pair)['bid']
            current_profit = 100 * (
                (current_rate - trade.open_rate) / trade.open_rate)
            orders = exchange.get_open_orders(trade.pair)
            orders = [o for o in orders if o['id'] == trade.open_order_id]
            order = orders[0] if orders else None

            fmt_close_profit = '{:.2f}%'.format(round(
                trade.close_profit, 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}`
*Close Rate:* `{close_rate}`
*Current Rate:* `{current_rate}`
*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, 2),
                open_order='{} ({})'.format(order['remaining'], order['type'])
                if order else None,
            )
            send_msg(message, bot=bot)
Esempio n. 14
0
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)
Esempio n. 15
0
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)
Esempio n. 16
0
 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
Esempio n. 17
0
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)
    current_rate = exchange.get_ticker(trade.pair)['bid']
    if should_sell(trade, current_rate, datetime.utcnow()):
        execute_sell(trade, current_rate)
        return True
    return False
Esempio n. 18
0
    def rpc_balance(self, fiat_display_currency: str) -> Tuple[bool, Any]:
        """
        :return: current account balance per crypto
        """
        balances = [
            c for c in exchange.get_balances()
            if c['Balance'] or c['Available'] or c['Pending']
        ]
        if not balances:
            return True, '`All balances are zero.`'

        output = []
        total = 0.0
        for currency in balances:
            coin = currency['Currency']
            if coin == 'BTC':
                currency["Rate"] = 1.0
            else:
                if coin == 'USDT':
                    currency["Rate"] = 1.0 / exchange.get_ticker(
                        'USDT_BTC', False)['bid']
                else:
                    currency["Rate"] = exchange.get_ticker(
                        'BTC_' + coin, False)['bid']
            currency['BTC'] = currency["Rate"] * currency["Balance"]
            total = total + currency['BTC']
            output.append({
                'currency': currency['Currency'],
                'available': currency['Available'],
                'balance': currency['Balance'],
                'pending': currency['Pending'],
                'est_btc': currency['BTC']
            })
        fiat = self.freqtrade.fiat_converter
        symbol = fiat_display_currency
        value = fiat.convert_amount(total, 'BTC', symbol)
        return False, (output, total, symbol, value)
Esempio n. 19
0
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
Esempio n. 20
0
def handle_trade(trade: Trade) -> None:
    """
    Sells the current pair if the threshold is reached and updates the trade record.
    :return: None
    """
    try:
        if not trade.is_open:
            raise ValueError('attempt to handle closed trade: {}'.format(trade))

        logger.debug('Handling open trade %s ...', trade)

        current_rate = exchange.get_ticker(trade.pair)['bid']
        if should_sell(trade, current_rate, datetime.utcnow()):
            execute_sell(trade, current_rate)
            return

    except ValueError:
        logger.exception('Unable to handle open order')
Esempio n. 21
0
def _forcesell(bot: Bot, update: Update) -> None:
    """
    Handler for /forcesell <id>.
    Sells the given trade at current price
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    if get_state() != State.RUNNING:
        send_msg('`trader is not running`', bot=bot)
        return

    try:
        trade_id = int(update.message.text
                       .replace('/forcesell', '')
                       .strip())
        # Query for trade
        trade = Trade.query.filter(and_(
            Trade.id == trade_id,
            Trade.is_open.is_(True)
        )).first()
        if not trade:
            send_msg('There is no open trade with ID: `{}`'.format(trade_id))
            return
        # Get current rate
        current_rate = exchange.get_ticker(trade.pair)['bid']
        # Get available balance
        currency = trade.pair.split('_')[1]
        balance = exchange.get_balance(currency)
        # Execute sell
        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)
        send_msg(message)

    except ValueError:
        send_msg('Invalid argument. Usage: `/forcesell <trade_id>`')
        logger.warning('/forcesell: Invalid argument received')
Esempio n. 22
0
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)
Esempio n. 23
0
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)
Esempio n. 24
0
from freqtrade import exchange
from freqtrade.analyze import analyze_ticker


# ensure directory exists
base_path = os.path.join(os.path.expanduser('~'), 'freqtrade')

# get configuration
with open(os.path.join(base_path, 'config.json')) as file:
	_CONF = json.load(file)

# initialize the exchange
exchange.init(_CONF)

# get ticker
data = exchange.get_ticker(pair='ETH_BTC')
print(data)


# get ticker history
df = exchange.get_ticker_history(pair='ETH_BTC', tick_interval=1)
print(pd.DataFrame(df))

# get markets
data = exchange.get_markets()
print(data)

# get name
print(exchange.get_name())

print(exchange.get_sleep_time())
Esempio n. 25
0
    def rpc_trade_statistics(self, stake_currency: str,
                             fiat_display_currency: str) -> Tuple[bool, Any]:
        """
        :return: cumulative profit statistics.
        """
        trades = Trade.query.order_by(Trade.id).all()

        profit_all_coin = []
        profit_all_percent = []
        profit_closed_coin = []
        profit_closed_percent = []
        durations = []

        for trade in trades:
            current_rate = None

            if not trade.open_rate:
                continue
            if trade.close_date:
                durations.append(
                    (trade.close_date - trade.open_date).total_seconds())

            if not trade.is_open:
                profit_percent = trade.calc_profit_percent()
                profit_closed_coin.append(trade.calc_profit())
                profit_closed_percent.append(profit_percent)
            else:
                # Get current rate
                current_rate = exchange.get_ticker(trade.pair, False)['bid']
                profit_percent = trade.calc_profit_percent(rate=current_rate)

            profit_all_coin.append(
                trade.calc_profit(
                    rate=Decimal(trade.close_rate or current_rate)))
            profit_all_percent.append(profit_percent)

        best_pair = Trade.session.query(
            Trade.pair, sql.func.sum(Trade.close_profit).label('profit_sum')
        ).filter(Trade.is_open.is_(False)) \
            .group_by(Trade.pair) \
            .order_by(sql.text('profit_sum DESC')).first()

        if not best_pair:
            return True, '*Status:* `no closed trade`'

        bp_pair, bp_rate = best_pair

        # FIX: we want to keep fiatconverter in a state/environment,
        #      doing this will utilize its caching functionallity, instead we reinitialize it here
        fiat = self.freqtrade.fiat_converter
        # Prepare data to display
        profit_closed_coin = round(sum(profit_closed_coin), 8)
        profit_closed_percent = round(sum(profit_closed_percent) * 100, 2)
        profit_closed_fiat = fiat.convert_amount(profit_closed_coin,
                                                 stake_currency,
                                                 fiat_display_currency)
        profit_all_coin = round(sum(profit_all_coin), 8)
        profit_all_percent = round(sum(profit_all_percent) * 100, 2)
        profit_all_fiat = fiat.convert_amount(profit_all_coin, stake_currency,
                                              fiat_display_currency)
        num = float(len(durations) or 1)
        return (False, {
            'profit_closed_coin':
            profit_closed_coin,
            'profit_closed_percent':
            profit_closed_percent,
            'profit_closed_fiat':
            profit_closed_fiat,
            'profit_all_coin':
            profit_all_coin,
            'profit_all_percent':
            profit_all_percent,
            'profit_all_fiat':
            profit_all_fiat,
            'trade_count':
            len(trades),
            'first_trade_date':
            arrow.get(trades[0].open_date).humanize(),
            'latest_trade_date':
            arrow.get(trades[-1].open_date).humanize(),
            'avg_duration':
            str(timedelta(seconds=sum(durations) / num)).split('.')[0],
            'best_pair':
            bp_pair,
            'best_rate':
            round(bp_rate * 100, 2)
        })
Esempio n. 26
0
    def execute_sell(self, 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_percent(rate=limit) * 100, 2)
        profit_trade = trade.calc_profit(rate=limit)
        current_rate = exchange.get_ticker(trade.pair, False)['bid']
        profit = trade.calc_profit_percent(current_rate)

        message = "*{exchange}:* Selling\n" \
                  "*Current Pair:* [{pair}]({pair_url})\n" \
                  "*Limit:* `{limit}`\n" \
                  "*Amount:* `{amount}`\n" \
                  "*Open Rate:* `{open_rate:.8f}`\n" \
                  "*Current Rate:* `{current_rate:.8f}`\n" \
                  "*Profit:* `{profit:.2f}%`" \
                  "".format(
                      exchange=trade.exchange,
                      pair=trade.pair,
                      pair_url=exchange.get_pair_detail_url(trade.pair),
                      limit=limit,
                      open_rate=trade.open_rate,
                      current_rate=current_rate,
                      amount=round(trade.amount, 8),
                      profit=round(profit * 100, 2),
                  )

        # For regular case, when the configuration exists
        if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
            fiat_converter = CryptoToFiatConverter()
            profit_fiat = fiat_converter.convert_amount(
                profit_trade, self.config['stake_currency'],
                self.config['fiat_display_currency'])
            message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f} {coin}`' \
                       '` / {profit_fiat:.3f} {fiat})`' \
                       ''.format(
                           gain="profit" if fmt_exp_profit > 0 else "loss",
                           profit_percent=fmt_exp_profit,
                           profit_coin=profit_trade,
                           coin=self.config['stake_currency'],
                           profit_fiat=profit_fiat,
                           fiat=self.config['fiat_display_currency'],
                       )
        # Because telegram._forcesell does not have the configuration
        # Ignore the FIAT value and does not show the stake_currency as well
        else:
            message += '` ({gain}: {profit_percent:.2f}%, {profit_coin:.8f})`'.format(
                gain="profit" if fmt_exp_profit > 0 else "loss",
                profit_percent=fmt_exp_profit,
                profit_coin=profit_trade)

        # Send the message
        self.rpc.send_msg(message)
        Trade.session.flush()
Esempio n. 27
0
    def create_trade(self) -> bool:
        """
        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
        :param interval: Ticker interval used for Analyze
        :return: True if a trade object has been created and persisted, False otherwise
        """
        stake_amount = self.config['stake_amount']
        interval = self.analyze.get_ticker_interval()

        logger.info(
            'Checking buy signals to create a new trade with stake_amount: %f ...',
            stake_amount)
        whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
        # Check if stake_amount is fulfilled
        if exchange.get_balance(self.config['stake_currency']) < stake_amount:
            raise DependencyException(
                'stake amount is not fulfilled (currency={})'.format(
                    self.config['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 DependencyException('No currency pairs in whitelist')

        # Pick pair based on StochRSI buy signals
        for _pair in whitelist:
            (buy, sell) = self.analyze.get_signal(_pair, interval)
            if buy and not sell:
                pair = _pair
                break
        else:
            return False

        # Calculate amount
        buy_limit = self.get_target_bid(exchange.get_ticker(pair))
        amount = stake_amount / buy_limit

        order_id = exchange.buy(pair, buy_limit, amount)

        stake_amount_fiat = self.fiat_converter.convert_amount(
            stake_amount, self.config['stake_currency'],
            self.config['fiat_display_currency'])

        # Create trade entity and return
        self.rpc.send_msg(
            '*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '
            .format(exchange.get_name().upper(), pair.replace('_', '/'),
                    exchange.get_pair_detail_url(pair), buy_limit,
                    stake_amount, self.config['stake_currency'],
                    stake_amount_fiat, self.config['fiat_display_currency']))
        # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
        trade = Trade(pair=pair,
                      stake_amount=stake_amount,
                      amount=amount,
                      fee=exchange.get_fee(),
                      open_rate=buy_limit,
                      open_date=datetime.utcnow(),
                      exchange=exchange.get_name().upper(),
                      open_order_id=order_id)
        Trade.session.add(trade)
        Trade.session.flush()
        return True
Esempio n. 28
0
def _profit(bot: Bot, update: Update) -> None:
    """
    Handler for /profit.
    Returns a cumulative profit statistics.
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    trades = Trade.query.order_by(Trade.id).all()

    profit_all_coin = []
    profit_all_percent = []
    profit_closed_coin = []
    profit_closed_percent = []
    durations = []

    for trade in trades:
        current_rate = None

        if not trade.open_rate:
            continue
        if trade.close_date:
            durations.append((trade.close_date - trade.open_date).total_seconds())

        if not trade.is_open:
            profit_percent = trade.calc_profit_percent()
            profit_closed_coin.append(trade.calc_profit())
            profit_closed_percent.append(profit_percent)
        else:
            # Get current rate
            current_rate = exchange.get_ticker(trade.pair, False)['bid']
            profit_percent = trade.calc_profit_percent(rate=current_rate)

        profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)))
        profit_all_percent.append(profit_percent)

    best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \
        .filter(Trade.is_open.is_(False)) \
        .group_by(Trade.pair) \
        .order_by(text('profit_sum DESC')) \
        .first()

    if not best_pair:
        send_msg('*Status:* `no closed trade`', bot=bot)
        return

    bp_pair, bp_rate = best_pair

    # Prepare data to display
    profit_closed_coin = round(sum(profit_closed_coin), 8)
    profit_closed_percent = round(sum(profit_closed_percent) * 100, 2)
    profit_closed_fiat = _FIAT_CONVERT.convert_amount(
        profit_closed_coin,
        _CONF['stake_currency'],
        _CONF['fiat_display_currency']
    )
    profit_all_coin = round(sum(profit_all_coin), 8)
    profit_all_percent = round(sum(profit_all_percent) * 100, 2)
    profit_all_fiat = _FIAT_CONVERT.convert_amount(
        profit_all_coin,
        _CONF['stake_currency'],
        _CONF['fiat_display_currency']
    )

    # Message to display
    markdown_msg = """
*ROI:* Close trades
  ∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`
  ∙ `{profit_closed_fiat:.3f} {fiat}`
*ROI:* All trades
  ∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`
  ∙ `{profit_all_fiat:.3f} {fiat}`

*Total Trade Count:* `{trade_count}`
*First Trade opened:* `{first_trade_date}`
*Latest Trade opened:* `{latest_trade_date}`
*Avg. Duration:* `{avg_duration}`
*Best Performing:* `{best_pair}: {best_rate:.2f}%`
    """.format(
        coin=_CONF['stake_currency'],
        fiat=_CONF['fiat_display_currency'],
        profit_closed_coin=profit_closed_coin,
        profit_closed_percent=profit_closed_percent,
        profit_closed_fiat=profit_closed_fiat,
        profit_all_coin=profit_all_coin,
        profit_all_percent=profit_all_percent,
        profit_all_fiat=profit_all_fiat,
        trade_count=len(trades),
        first_trade_date=arrow.get(trades[0].open_date).humanize(),
        latest_trade_date=arrow.get(trades[-1].open_date).humanize(),
        avg_duration=str(timedelta(seconds=sum(durations) / float(len(durations)))).split('.')[0],
        best_pair=bp_pair,
        best_rate=round(bp_rate * 100, 2),
    )
    send_msg(markdown_msg, bot=bot)
Esempio n. 29
0
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'
        )
Esempio n. 30
0
def create_trade(stake_amount: float) -> bool:
    """
    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
    :return: True if a trade object has been created and persisted, False otherwise
    """
    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 DependencyException(
            '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 DependencyException('No pair in whitelist')

    # Pick pair based on StochRSI buy signals
    for _pair in whitelist:
        if get_signal(_pair, SignalType.BUY):
            pair = _pair
            break
    else:
        return False

    # Calculate amount
    buy_limit = get_target_bid(exchange.get_ticker(pair))
    amount = stake_amount / buy_limit

    order_id = exchange.buy(pair, buy_limit, amount)

    fiat_converter = CryptoToFiatConverter()
    stake_amount_fiat = fiat_converter.convert_amount(
        stake_amount,
        _CONF['stake_currency'],
        _CONF['fiat_display_currency']
    )

    # Create trade entity and return
    rpc.send_msg('*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '.format(
        exchange.get_name().upper(),
        pair.replace('_', '/'),
        exchange.get_pair_detail_url(pair),
        buy_limit, stake_amount, _CONF['stake_currency'],
        stake_amount_fiat, _CONF['fiat_display_currency']
    ))
    # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
    trade = Trade(
        pair=pair,
        stake_amount=stake_amount,
        amount=amount,
        fee=exchange.get_fee(),
        open_rate=buy_limit,
        open_date=datetime.utcnow(),
        exchange=exchange.get_name().upper(),
        open_order_id=order_id
    )
    Trade.session.add(trade)
    Trade.session.flush()
    return True
Esempio n. 31
0
def _profit(bot: Bot, update: Update) -> None:
    """
    Handler for /profit.
    Returns a cumulative profit statistics.
    :param bot: telegram bot
    :param update: message update
    :return: None
    """
    trades = Trade.query.order_by(Trade.id).all()

    profit_all_coin = []
    profit_all_percent = []
    profit_closed_coin = []
    profit_closed_percent = []
    durations = []

    for trade in trades:
        current_rate = None

        if not trade.open_rate:
            continue
        if trade.close_date:
            durations.append(
                (trade.close_date - trade.open_date).total_seconds())

        if not trade.is_open:
            profit_percent = trade.calc_profit_percent()
            profit_closed_coin.append(trade.calc_profit())
            profit_closed_percent.append(profit_percent)
        else:
            # Get current rate
            current_rate = exchange.get_ticker(trade.pair, False)['bid']
            profit_percent = trade.calc_profit_percent(rate=current_rate)

        profit_all_coin.append(
            trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)))
        profit_all_percent.append(profit_percent)

    best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \
        .filter(Trade.is_open.is_(False)) \
        .group_by(Trade.pair) \
        .order_by(text('profit_sum DESC')) \
        .first()

    if not best_pair:
        send_msg('*Status:* `no closed trade`', bot=bot)
        return

    bp_pair, bp_rate = best_pair

    # Prepare data to display
    profit_closed_coin = round(sum(profit_closed_coin), 8)
    profit_closed_percent = round(sum(profit_closed_percent) * 100, 2)
    profit_closed_fiat = _FIAT_CONVERT.convert_amount(
        profit_closed_coin, _CONF['stake_currency'],
        _CONF['fiat_display_currency'])
    profit_all_coin = round(sum(profit_all_coin), 8)
    profit_all_percent = round(sum(profit_all_percent) * 100, 2)
    profit_all_fiat = _FIAT_CONVERT.convert_amount(
        profit_all_coin, _CONF['stake_currency'],
        _CONF['fiat_display_currency'])

    # Message to display
    markdown_msg = """
*ROI:* Close trades
  ∙ `{profit_closed_coin:.8f} {coin} ({profit_closed_percent:.2f}%)`
  ∙ `{profit_closed_fiat:.3f} {fiat}`
*ROI:* All trades
  ∙ `{profit_all_coin:.8f} {coin} ({profit_all_percent:.2f}%)`
  ∙ `{profit_all_fiat:.3f} {fiat}`

*Total Trade Count:* `{trade_count}`
*First Trade opened:* `{first_trade_date}`
*Latest Trade opened:* `{latest_trade_date}`
*Avg. Duration:* `{avg_duration}`
*Best Performing:* `{best_pair}: {best_rate:.2f}%`
    """.format(
        coin=_CONF['stake_currency'],
        fiat=_CONF['fiat_display_currency'],
        profit_closed_coin=profit_closed_coin,
        profit_closed_percent=profit_closed_percent,
        profit_closed_fiat=profit_closed_fiat,
        profit_all_coin=profit_all_coin,
        profit_all_percent=profit_all_percent,
        profit_all_fiat=profit_all_fiat,
        trade_count=len(trades),
        first_trade_date=arrow.get(trades[0].open_date).humanize(),
        latest_trade_date=arrow.get(trades[-1].open_date).humanize(),
        avg_duration=str(
            timedelta(seconds=sum(durations) /
                      float(len(durations)))).split('.')[0],
        best_pair=bp_pair,
        best_rate=round(bp_rate * 100, 2),
    )
    send_msg(markdown_msg, bot=bot)