예제 #1
0
파일: main.py 프로젝트: enenn/freqtrade
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_percent(rate=limit) * 100, 2)
    profit_trade = trade.calc_profit(rate=limit)

    message = '*{exchange}:* Selling [{pair}]({pair_url}) with limit `{limit:.8f}`'.format(
        exchange=trade.exchange,
        pair=trade.pair.replace('_', '/'),
        pair_url=exchange.get_pair_detail_url(trade.pair),
        limit=limit
    )

    # For regular case, when the configuration exists
    if 'stake_currency' in _CONF and 'fiat_display_currency' in _CONF:
        fiat_converter = CryptoToFiatConverter()
        profit_fiat = fiat_converter.convert_amount(
            profit_trade,
            _CONF['stake_currency'],
            _CONF['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=_CONF['stake_currency'],
                       profit_fiat=profit_fiat,
                       fiat=_CONF['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
    rpc.send_msg(message)
    Trade.session.flush()
예제 #2
0
파일: main.py 프로젝트: enenn/freqtrade
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
예제 #3
0
파일: telegram.py 프로젝트: enenn/freqtrade
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)
예제 #4
0
파일: main.py 프로젝트: enenn/freqtrade
def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) -> bool:
    """
    Based an earlier trade and current price and ROI configuration, decides whether bot should sell
    :return True if bot should sell at current rate
    """
    current_profit = trade.calc_profit_percent(current_rate)
    if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']):
        logger.debug('Stop loss hit.')
        return True

    # Check if time matches and current rate is above threshold
    time_diff = (current_time - trade.open_date).total_seconds() / 60
    for duration, threshold in sorted(_CONF['minimal_roi'].items()):
        if time_diff > float(duration) and current_profit > threshold:
            return True

    logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', float(current_profit) * 100.0)
    return False
예제 #5
0
    def execute_sell(self, trade: Trade, limit: float,
                     sell_reason: SellType) -> None:
        """
        Executes a limit sell for the given trade and limit
        :param trade: Trade instance
        :param limit: limit rate for the sell order
        :param sellreason: Reason the sell was triggered
        :return: None
        """
        sell_type = 'sell'
        if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
            sell_type = 'stoploss'

        # if stoploss is on exchange and we are on dry_run mode,
        # we consider the sell price stop price
        if self.config.get('dry_run', False) and sell_type == 'stoploss' \
           and self.strategy.order_types['stoploss_on_exchange']:
            limit = trade.stop_loss

        # First cancelling stoploss on exchange ...
        if self.strategy.order_types.get(
                'stoploss_on_exchange') and trade.stoploss_order_id:
            self.exchange.cancel_order(trade.stoploss_order_id, trade.pair)

        # Execute sell and update trade record
        order_id = self.exchange.sell(
            pair=str(trade.pair),
            ordertype=self.strategy.order_types[sell_type],
            amount=trade.amount,
            rate=limit,
            time_in_force=self.strategy.order_time_in_force['sell'])['id']

        trade.open_order_id = order_id
        trade.close_rate_requested = limit
        trade.sell_reason = sell_reason.value

        profit_trade = trade.calc_profit(rate=limit)
        current_rate = self.exchange.get_ticker(trade.pair)['bid']
        profit_percent = trade.calc_profit_percent(limit)
        pair_url = self.exchange.get_pair_detail_url(trade.pair)
        gain = "profit" if profit_percent > 0 else "loss"

        msg = {
            'type': RPCMessageType.SELL_NOTIFICATION,
            'exchange': trade.exchange.capitalize(),
            'pair': trade.pair,
            'gain': gain,
            'market_url': pair_url,
            'limit': limit,
            'amount': trade.amount,
            'open_rate': trade.open_rate,
            'current_rate': current_rate,
            'profit_amount': profit_trade,
            'profit_percent': profit_percent,
            'sell_reason': sell_reason.value
        }

        # For regular case, when the configuration exists
        if 'stake_currency' in self.config and 'fiat_display_currency' in self.config:
            stake_currency = self.config['stake_currency']
            fiat_currency = self.config['fiat_display_currency']
            msg.update({
                'stake_currency': stake_currency,
                'fiat_currency': fiat_currency,
            })

        # Send the message
        self.rpc.send_msg(msg)
        Trade.session.flush()
예제 #6
0
    def min_roi_reached_dynamic(
            self, trade: Trade, current_profit: float, current_time: datetime,
            trade_dur: int) -> Tuple[Optional[int], Optional[float]]:

        dynamic_roi = self.get_pair_params(trade.pair, 'dynamic_roi')
        minimal_roi = self.get_pair_params(trade.pair, 'minimal_roi')

        if not dynamic_roi or not minimal_roi:
            return None, None

        _, table_roi = self.min_roi_reached_entry(trade_dur, trade.pair)

        # see if we have the data we need to do this, otherwise fall back to the standard table
        if self.custom_trade_info and trade and trade.pair in self.custom_trade_info:
            if self.config['runmode'].value in ('live', 'dry_run'):
                dataframe, last_updated = self.dp.get_analyzed_dataframe(
                    pair=trade.pair, timeframe=self.timeframe)
                roc = dataframe['roc'].iat[-1]
                atr = dataframe['atr'].iat[-1]
                rmi_slow = dataframe['rmi-slow'].iat[-1]
                rmi_trend = dataframe['rmi-up-trend'].iat[-1]
            # If in backtest or hyperopt, get the indicator values out of the trades dict (Thanks @JoeSchr!)
            else:
                roc = self.custom_trade_info[
                    trade.pair]['roc'].loc[current_time]['roc']
                atr = self.custom_trade_info[
                    trade.pair]['atr'].loc[current_time]['atr']
                rmi_slow = self.custom_trade_info[
                    trade.pair]['rmi-slow'].loc[current_time]['rmi-slow']
                rmi_trend = self.custom_trade_info[trade.pair][
                    'rmi-up-trend'].loc[current_time]['rmi-up-trend']

            d = dynamic_roi
            profit_factor = (1 - (rmi_slow / d['profit-factor']))
            rmi_grow = cta.linear_growth(d['rmi-start'], d['rmi-end'],
                                         d['grow-delay'], d['grow-time'],
                                         trade_dur)
            max_profit = trade.calc_profit_ratio(trade.max_rate)
            open_rate = trade.open_rate

            atr_roi = max(d['min-roc-atr'],
                          ((open_rate + atr) / open_rate) - 1)
            roc_roi = max(d['min-roc-atr'], (roc / 100))

            # atr as the fallback (if > min-roc-atr)
            if d['fallback'] == 'atr':
                min_roi = atr_roi
            # roc as the fallback (if > min-roc-atr)
            elif d['fallback'] == 'roc':
                min_roi = roc_roi
            # atr or table as the fallback (whichever is larger)
            elif d['fallback'] == 'atr-table':
                min_roi = max(table_roi, atr_roi)
            # roc or table as the fallback (whichever is larger)
            elif d['fallback'] == 'roc-table':
                min_roi = max(table_roi, roc_roi)
            # default to table
            else:
                min_roi = table_roi

            # If we observe a strong upward trend and our current profit has not retreated from the peak by much, hold
            if (rmi_trend == 1) and (rmi_slow > rmi_grow):
                if current_profit > min_roi and (current_profit <
                                                 (max_profit * profit_factor)):
                    min_roi = min_roi
                else:
                    min_roi = 100
            """
            # If we observe a strong upward trend and our current profit has not retreated from the peak by much, hold
            if (current_profit > (max_profit * profit_factor)) and (rmi_trend == 1) and (rmi_slow > rmi_grow):
                min_roi = 100
            """

        else:
            min_roi = table_roi

        # Attempting to wedge the dynamic roi value into a thing so we can trick backtesting...
        if self.config['runmode'].value not in ('live', 'dry_run'):
            # Theoretically, if backtesting uses this value, ROI was triggered so we need to trick it with a sell
            # rate other than what is on the standard ROI table...
            self.custom_trade_info['backtest']['roi'] = max(
                min_roi, current_profit)

        return trade_dur, min_roi
예제 #7
0
def backtest(stake_amount: float, processed: Dict[str, DataFrame],
             max_open_trades: int = 0, realistic: bool = True, sell_profit_only: bool = False,
             stoploss: int = -1.00, use_sell_signal: bool = False) -> DataFrame:
    """
    Implements backtesting functionality
    :param stake_amount: btc amount to use for each trade
    :param processed: a processed dictionary with format {pair, data}
    :param max_open_trades: maximum number of concurrent trades (default: 0, disabled)
    :param realistic: do we try to simulate realistic trades? (default: True)
    :return: DataFrame
    """
    trades = []
    trade_count_lock: dict = {}
    exchange._API = Bittrex({'key': '', 'secret': ''})
    for pair, pair_data in processed.items():
        pair_data['buy'], pair_data['sell'] = 0, 0
        ticker = populate_sell_trend(populate_buy_trend(pair_data))
        # for each buy point
        lock_pair_until = None
        buy_subset = ticker[ticker.buy == 1][['buy', 'open', 'close', 'date', 'sell']]
        for row in buy_subset.itertuples(index=True):
            if realistic:
                if lock_pair_until is not None and row.Index <= lock_pair_until:
                    continue
            if max_open_trades > 0:
                # Check if max_open_trades has already been reached for the given date
                if not trade_count_lock.get(row.date, 0) < max_open_trades:
                    continue

            if max_open_trades > 0:
                # Increase lock
                trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1

            trade = Trade(
                open_rate=row.close,
                open_date=row.date,
                stake_amount=stake_amount,
                amount=stake_amount / row.open,
                fee=exchange.get_fee()
            )

            # calculate win/lose forwards from buy point
            sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']]
            for row2 in sell_subset.itertuples(index=True):
                if max_open_trades > 0:
                    # Increase trade_count_lock for every iteration
                    trade_count_lock[row2.date] = trade_count_lock.get(row2.date, 0) + 1

                current_profit_percent = trade.calc_profit_percent(rate=row2.close)
                if (sell_profit_only and current_profit_percent < 0):
                    continue
                if min_roi_reached(trade, row2.close, row2.date) or \
                    (row2.sell == 1 and use_sell_signal) or \
                        current_profit_percent <= stoploss:
                    current_profit_btc = trade.calc_profit(rate=row2.close)
                    lock_pair_until = row2.Index

                    trades.append(
                        (
                            pair,
                            current_profit_percent,
                            current_profit_btc,
                            row2.Index - row.Index,
                            current_profit_btc > 0,
                            current_profit_btc < 0
                        )
                    )
                    break
    labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'profit', 'loss']
    return DataFrame.from_records(trades, columns=labels)
예제 #8
0
    def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]:
        """
        Handler for forcesell <id>.
        Sells the given trade at current price
        """
        def _exec_forcesell(trade: Trade) -> None:
            # Check if there is there is an open order
            if trade.open_order_id:
                order = self._freqtrade.exchange.get_order(
                    trade.open_order_id, trade.pair)

                # Cancel open LIMIT_BUY orders and close trade
                if order and order['status'] == 'open' \
                        and order['type'] == 'limit' \
                        and order['side'] == 'buy':
                    self._freqtrade.exchange.cancel_order(
                        trade.open_order_id, trade.pair)
                    trade.close(order.get('price') or trade.open_rate)
                    # Do the best effort, if we don't know 'filled' amount, don't try selling
                    if order['filled'] is None:
                        return
                    trade.amount = order['filled']

                # Ignore trades with an attached LIMIT_SELL order
                if order and order['status'] == 'open' \
                        and order['type'] == 'limit' \
                        and order['side'] == 'sell':
                    return

            # Get current rate and execute sell
            current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
            self._freqtrade.execute_sell(trade, current_rate,
                                         SellType.FORCE_SELL)

        # ---- EOF def _exec_forcesell ----

        if self._freqtrade.state != State.RUNNING:
            raise RPCException('trader is not running')

        with self._freqtrade._sell_lock:
            if trade_id == 'all':
                # Execute sell for all open orders
                for trade in Trade.get_open_trades():
                    _exec_forcesell(trade)
                Trade.session.flush()
                self._freqtrade.wallets.update()
                return {'result': 'Created sell orders for all open trades.'}

            # Query for trade
            trade = Trade.get_trades(trade_filter=[
                Trade.id == trade_id,
                Trade.is_open.is_(True),
            ]).first()
            if not trade:
                logger.warning('forcesell: Invalid argument received')
                raise RPCException('invalid argument')

            _exec_forcesell(trade)
            Trade.session.flush()
            self._freqtrade.wallets.update()
            return {'result': f'Created sell order for trade {trade_id}.'}
예제 #9
0
    def _rpc_trade_statistics(
        self,
        stake_currency: str,
        fiat_display_currency: str,
        start_date: datetime = datetime.fromtimestamp(0)
    ) -> Dict[str, Any]:
        """ Returns cumulative profit statistics """
        trade_filter = ((Trade.is_open.is_(False) &
                         (Trade.close_date >= start_date))
                        | Trade.is_open.is_(True))
        trades = Trade.get_trades(trade_filter).order_by(Trade.id).all()

        profit_all_coin = []
        profit_all_ratio = []
        profit_closed_coin = []
        profit_closed_ratio = []
        durations = []
        winning_trades = 0
        losing_trades = 0

        for trade in trades:
            current_rate: float = 0.0

            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_ratio = trade.close_profit
                profit_closed_coin.append(trade.close_profit_abs)
                profit_closed_ratio.append(profit_ratio)
                if trade.close_profit >= 0:
                    winning_trades += 1
                else:
                    losing_trades += 1
            else:
                # Get current rate
                try:
                    current_rate = self._freqtrade.exchange.get_sell_rate(
                        trade.pair, False)
                except (PricingError, ExchangeError):
                    current_rate = NAN
                profit_ratio = trade.calc_profit_ratio(rate=current_rate)

            profit_all_coin.append(
                trade.calc_profit(rate=trade.close_rate or current_rate))
            profit_all_ratio.append(profit_ratio)

        best_pair = Trade.get_best_pair(start_date)

        # Prepare data to display
        profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
        profit_closed_ratio_mean = float(
            mean(profit_closed_ratio) if profit_closed_ratio else 0.0)
        profit_closed_ratio_sum = sum(
            profit_closed_ratio) if profit_closed_ratio else 0.0

        profit_closed_fiat = self._fiat_converter.convert_amount(
            profit_closed_coin_sum, stake_currency,
            fiat_display_currency) if self._fiat_converter else 0

        profit_all_coin_sum = round(sum(profit_all_coin), 8)
        profit_all_ratio_mean = float(
            mean(profit_all_ratio) if profit_all_ratio else 0.0)
        profit_all_ratio_sum = sum(
            profit_all_ratio) if profit_all_ratio else 0.0
        profit_all_fiat = self._fiat_converter.convert_amount(
            profit_all_coin_sum, stake_currency,
            fiat_display_currency) if self._fiat_converter else 0

        first_date = trades[0].open_date if trades else None
        last_date = trades[-1].open_date if trades else None
        num = float(len(durations) or 1)
        return {
            'profit_closed_coin':
            profit_closed_coin_sum,
            'profit_closed_percent_mean':
            round(profit_closed_ratio_mean * 100, 2),
            'profit_closed_ratio_mean':
            profit_closed_ratio_mean,
            'profit_closed_percent_sum':
            round(profit_closed_ratio_sum * 100, 2),
            'profit_closed_ratio_sum':
            profit_closed_ratio_sum,
            'profit_closed_fiat':
            profit_closed_fiat,
            'profit_all_coin':
            profit_all_coin_sum,
            'profit_all_percent_mean':
            round(profit_all_ratio_mean * 100, 2),
            'profit_all_ratio_mean':
            profit_all_ratio_mean,
            'profit_all_percent_sum':
            round(profit_all_ratio_sum * 100, 2),
            'profit_all_ratio_sum':
            profit_all_ratio_sum,
            'profit_all_fiat':
            profit_all_fiat,
            'trade_count':
            len(trades),
            'closed_trade_count':
            len([t for t in trades if not t.is_open]),
            'first_trade_date':
            arrow.get(first_date).humanize() if first_date else '',
            'first_trade_timestamp':
            int(first_date.timestamp() * 1000) if first_date else 0,
            'latest_trade_date':
            arrow.get(last_date).humanize() if last_date else '',
            'latest_trade_timestamp':
            int(last_date.timestamp() * 1000) if last_date else 0,
            'avg_duration':
            str(timedelta(seconds=sum(durations) / num)).split('.')[0],
            'best_pair':
            best_pair[0] if best_pair else '',
            'best_rate':
            round(best_pair[1] * 100, 2) if best_pair else 0,
            'winning_trades':
            winning_trades,
            'losing_trades':
            losing_trades,
        }
예제 #10
0
    def stop_loss_reached(self,
                          current_rate: float,
                          trade: Trade,
                          current_time: datetime,
                          current_profit: float,
                          force_stoploss: float,
                          high: float = None) -> SellCheckTuple:
        """
        Based on current profit of the trade and configured (trailing) stoploss,
        decides to sell or not
        :param current_profit: current profit in percent
        """
        trailing_stop = self.config.get('trailing_stop', False)
        stop_loss_value = force_stoploss if force_stoploss else self.stoploss

        # Initiate stoploss with open_rate. Does nothing if stoploss is already set.
        trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)

        if trailing_stop:
            # trailing stoploss handling
            sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
            tsl_only_offset = self.config.get(
                'trailing_only_offset_is_reached', False)

            # Make sure current_profit is calculated using high for backtesting.
            high_profit = current_profit if not high else trade.calc_profit_percent(
                high)

            # Don't update stoploss if trailing_only_offset_is_reached is true.
            if not (tsl_only_offset and high_profit < sl_offset):
                # Specific handling for trailing_stop_positive
                if 'trailing_stop_positive' in self.config and high_profit > sl_offset:
                    # Ignore mypy error check in configuration that this is a float
                    stop_loss_value = self.config.get(
                        'trailing_stop_positive')  # type: ignore
                    logger.debug(
                        f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
                        f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%"
                    )

                trade.adjust_stop_loss(high or current_rate, stop_loss_value)

        # evaluate if the stoploss was hit if stoploss is not on exchange
        if ((self.stoploss is not None) and (trade.stop_loss >= current_rate)
                and (not self.order_types.get('stoploss_on_exchange'))):

            sell_type = SellType.STOP_LOSS

            # If initial stoploss is not the same as current one then it is trailing.
            if trade.initial_stop_loss != trade.stop_loss:
                sell_type = SellType.TRAILING_STOP_LOSS
                logger.debug(
                    f"{trade.pair} - HIT STOP: current price at {current_rate:.6f}, "
                    f"stoploss is {trade.stop_loss:.6f}, "
                    f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
                    f"trade opened at {trade.open_rate:.6f}")
                logger.debug(
                    f"{trade.pair} - Trailing stop saved "
                    f"{trade.stop_loss - trade.initial_stop_loss:.6f}")

            return SellCheckTuple(sell_flag=True, sell_type=sell_type)

        return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
예제 #11
0
def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        amount=5,
        open_rate=0.00001099,
        fee_open=fee.return_value,
        fee_close=fee.return_value,
        exchange='bittrex',
    )
    trade.open_order_id = 'something'
    trade.update(limit_buy_order)  # Buy @ 0.00001099

    # Get percent of profit with a custom rate (Higher than open rate)
    assert trade.calc_profit_ratio(rate=0.00001234) == 0.11723875

    # Get percent of profit with a custom rate (Lower than open rate)
    assert trade.calc_profit_ratio(rate=0.00000123) == -0.88863828

    # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
    trade.update(limit_sell_order)
    assert trade.calc_profit_ratio() == 0.06201058

    # Test with a custom fee rate on the close trade
    assert trade.calc_profit_ratio(fee=0.003) == 0.06147824
예제 #12
0
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
    """
    On this test we will buy and sell a crypto currency.

    Buy
    - Buy: 90.99181073 Crypto at 0.00001099 BTC
        (90.99181073*0.00001099 = 0.0009999 BTC)
    - Buying fee: 0.25%
    - Total cost of buy trade: 0.001002500 BTC
        ((90.99181073*0.00001099) + ((90.99181073*0.00001099)*0.0025))

    Sell
    - Sell: 90.99181073 Crypto at 0.00001173 BTC
        (90.99181073*0.00001173 = 0,00106733394 BTC)
    - Selling fee: 0.25%
    - Total cost of sell trade: 0.001064666 BTC
        ((90.99181073*0.00001173) - ((90.99181073*0.00001173)*0.0025))

    Profit/Loss: +0.000062166 BTC
        (Sell:0.001064666 - Buy:0.001002500)
    Profit/Loss percentage: 0.0620
        ((0.001064666/0.001002500)-1 = 6.20%)

    :param limit_buy_order:
    :param limit_sell_order:
    :return:
    """

    trade = Trade(
        id=2,
        pair='ETH/BTC',
        stake_amount=0.001,
        open_rate=0.01,
        amount=5,
        is_open=True,
        open_date=arrow.utcnow().datetime,
        fee_open=fee.return_value,
        fee_close=fee.return_value,
        exchange='bittrex',
    )
    assert trade.open_order_id is None
    assert trade.close_profit is None
    assert trade.close_date is None

    trade.open_order_id = 'something'
    trade.update(limit_buy_order)
    assert trade.open_order_id is None
    assert trade.open_rate == 0.00001099
    assert trade.close_profit is None
    assert trade.close_date is None
    assert log_has_re(
        r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
        r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).",
        caplog)

    caplog.clear()
    trade.open_order_id = 'something'
    trade.update(limit_sell_order)
    assert trade.open_order_id is None
    assert trade.close_rate == 0.00001173
    assert trade.close_profit == 0.06201058
    assert trade.close_date is not None
    assert log_has_re(
        r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
        r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).",
        caplog)
예제 #13
0
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        open_rate=0.01,
        amount=5,
        fee_open=fee.return_value,
        fee_close=fee.return_value,
        exchange='bittrex',
    )

    trade.open_order_id = 'something'
    trade.update(limit_buy_order)
    assert trade._calc_open_trade_value() == 0.0010024999999225068

    trade.update(limit_sell_order)
    assert trade.calc_close_trade_value() == 0.0010646656050132426

    # Profit in BTC
    assert trade.calc_profit() == 0.00006217

    # Profit in percent
    assert trade.calc_profit_ratio() == 0.06201058
예제 #14
0
def test_calc_profit(limit_buy_order, limit_sell_order, fee):
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        amount=5,
        open_rate=0.00001099,
        fee_open=fee.return_value,
        fee_close=fee.return_value,
        exchange='bittrex',
    )
    trade.open_order_id = 'something'
    trade.update(limit_buy_order)  # Buy @ 0.00001099

    # Custom closing rate and regular fee rate
    # Higher than open rate
    assert trade.calc_profit(rate=0.00001234) == 0.00011753
    # Lower than open rate
    assert trade.calc_profit(rate=0.00000123) == -0.00089086

    # Custom closing rate and custom fee rate
    # Higher than open rate
    assert trade.calc_profit(rate=0.00001234, fee=0.003) == 0.00011697
    # Lower than open rate
    assert trade.calc_profit(rate=0.00000123, fee=0.003) == -0.00089092

    # Test when we apply a Sell order. Sell higher than open rate @ 0.00001173
    trade.update(limit_sell_order)
    assert trade.calc_profit() == 0.00006217

    # Test with a custom fee rate on the close trade
    assert trade.calc_profit(fee=0.003) == 0.00006163
예제 #15
0
def test_fee_updated(fee):
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        fee_open=fee.return_value,
        open_date=arrow.utcnow().shift(hours=-2).datetime,
        amount=10,
        fee_close=fee.return_value,
        exchange='bittrex',
        open_rate=1,
        max_rate=1,
    )

    assert trade.fee_open_currency is None
    assert not trade.fee_updated('buy')
    assert not trade.fee_updated('sell')
    assert not trade.fee_updated('asdf')

    trade.update_fee(0.15, 'BTC', 0.0075, 'buy')
    assert trade.fee_updated('buy')
    assert not trade.fee_updated('sell')
    assert trade.fee_open_currency is not None
    assert trade.fee_close_currency is None

    trade.update_fee(0.15, 'ABC', 0.0075, 'sell')
    assert trade.fee_updated('buy')
    assert trade.fee_updated('sell')
    assert not trade.fee_updated('asfd')
예제 #16
0
    def stop_loss_reached(self, current_rate: float, trade: Trade,
                          current_time: datetime, current_profit: float,
                          force_stoploss: float, low: float = None,
                          high: float = None) -> SellCheckTuple:
        """
        Based on current profit of the trade and configured (trailing) stoploss,
        decides to sell or not
        :param current_profit: current profit as ratio
        :param low: Low value of this candle, only set in backtesting
        :param high: High value of this candle, only set in backtesting
        """
        stop_loss_value = force_stoploss if force_stoploss else self.stoploss

        # Initiate stoploss with open_rate. Does nothing if stoploss is already set.
        trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)

        if self.use_custom_stoploss and trade.stop_loss < (low or current_rate):
            stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None
                                                    )(pair=trade.pair, trade=trade,
                                                      current_time=current_time,
                                                      current_rate=current_rate,
                                                      current_profit=current_profit)
            # Sanity check - error cases will return None
            if stop_loss_value:
                # logger.info(f"{trade.pair} {stop_loss_value=} {current_profit=}")
                trade.adjust_stop_loss(current_rate, stop_loss_value)
            else:
                logger.warning("CustomStoploss function did not return valid stoploss")

        if self.trailing_stop and trade.stop_loss < (low or current_rate):
            # trailing stoploss handling
            sl_offset = self.trailing_stop_positive_offset

            # Make sure current_profit is calculated using high for backtesting.
            high_profit = current_profit if not high else trade.calc_profit_ratio(high)

            # Don't update stoploss if trailing_only_offset_is_reached is true.
            if not (self.trailing_only_offset_is_reached and high_profit < sl_offset):
                # Specific handling for trailing_stop_positive
                if self.trailing_stop_positive is not None and high_profit > sl_offset:
                    stop_loss_value = self.trailing_stop_positive
                    logger.debug(f"{trade.pair} - Using positive stoploss: {stop_loss_value} "
                                 f"offset: {sl_offset:.4g} profit: {current_profit:.2%}")

                trade.adjust_stop_loss(high or current_rate, stop_loss_value)

        # evaluate if the stoploss was hit if stoploss is not on exchange
        # in Dry-Run, this handles stoploss logic as well, as the logic will not be different to
        # regular stoploss handling.
        if ((trade.stop_loss >= (low or current_rate)) and
                (not self.order_types.get('stoploss_on_exchange') or self.config['dry_run'])):

            sell_type = SellType.STOP_LOSS

            # If initial stoploss is not the same as current one then it is trailing.
            if trade.initial_stop_loss != trade.stop_loss:
                sell_type = SellType.TRAILING_STOP_LOSS
                logger.debug(
                    f"{trade.pair} - HIT STOP: current price at {(low or current_rate):.6f}, "
                    f"stoploss is {trade.stop_loss:.6f}, "
                    f"initial stoploss was at {trade.initial_stop_loss:.6f}, "
                    f"trade opened at {trade.open_rate:.6f}")
                logger.debug(f"{trade.pair} - Trailing stop saved "
                             f"{trade.stop_loss - trade.initial_stop_loss:.6f}")

            return SellCheckTuple(sell_type=sell_type)

        return SellCheckTuple(sell_type=SellType.NONE)
예제 #17
0
    def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
                    sell: bool, low: float = None, high: float = None,
                    force_stoploss: float = 0) -> SellCheckTuple:
        """
        This function evaluates if one of the conditions required to trigger a sell
        has been reached, which can either be a stop-loss, ROI or sell-signal.
        :param low: Only used during backtesting to simulate stoploss
        :param high: Only used during backtesting, to simulate ROI
        :param force_stoploss: Externally provided stoploss
        :return: True if trade should be sold, False otherwise
        """
        current_rate = rate
        current_profit = trade.calc_profit_ratio(current_rate)

        trade.adjust_min_max_rates(high or current_rate, low or current_rate)

        stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
                                              current_time=date, current_profit=current_profit,
                                              force_stoploss=force_stoploss, low=low, high=high)

        # Set current rate to high for backtesting sell
        current_rate = high or rate
        current_profit = trade.calc_profit_ratio(current_rate)

        # if buy signal and ignore_roi is set, we don't need to evaluate min_roi.
        roi_reached = (not (buy and self.ignore_roi_if_buy_signal)
                       and self.min_roi_reached(trade=trade, current_profit=current_profit,
                                                current_time=date))

        sell_signal = SellType.NONE
        custom_reason = ''
        # use provided rate in backtesting, not high/low.
        current_rate = rate
        current_profit = trade.calc_profit_ratio(current_rate)

        if (self.sell_profit_only and current_profit <= self.sell_profit_offset):
            # sell_profit_only and profit doesn't reach the offset - ignore sell signal
            pass
        elif self.use_sell_signal and not buy:
            if sell:
                sell_signal = SellType.SELL_SIGNAL
            else:
                custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)(
                    pair=trade.pair, trade=trade, current_time=date, current_rate=current_rate,
                    current_profit=current_profit)
                if custom_reason:
                    sell_signal = SellType.CUSTOM_SELL
                    if isinstance(custom_reason, str):
                        if len(custom_reason) > CUSTOM_SELL_MAX_LENGTH:
                            logger.warning(f'Custom sell reason returned from custom_sell is too '
                                           f'long and was trimmed to {CUSTOM_SELL_MAX_LENGTH} '
                                           f'characters.')
                            custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH]
                    else:
                        custom_reason = None
            # TODO: return here if sell-signal should be favored over ROI

        # Start evaluations
        # Sequence:
        # ROI (if not stoploss)
        # Sell-signal
        # Stoploss
        if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS:
            logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI")
            return SellCheckTuple(sell_type=SellType.ROI)

        if sell_signal != SellType.NONE:
            logger.debug(f"{trade.pair} - Sell signal received. "
                         f"sell_type=SellType.{sell_signal.name}" +
                         (f", custom_reason={custom_reason}" if custom_reason else ""))
            return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason)

        if stoplossflag.sell_flag:

            logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.sell_type}")
            return stoplossflag

        # This one is noisy, commented out...
        # logger.debug(f"{trade.pair} - No sell signal.")
        return SellCheckTuple(sell_type=SellType.NONE)
예제 #18
0
    def __init__(self, config: Dict[str, Any]) -> None:

        LoggingMixin.show_output = False
        self.config = config

        # Reset keys for backtesting
        remove_credentials(self.config)
        self.strategylist: List[IStrategy] = []
        self.all_results: Dict[str, Dict] = {}

        self.exchange = ExchangeResolver.load_exchange(
            self.config['exchange']['name'], self.config)
        self.dataprovider = DataProvider(self.config, None)

        if self.config.get('strategy_list', None):
            for strat in list(self.config['strategy_list']):
                stratconf = deepcopy(self.config)
                stratconf['strategy'] = strat
                self.strategylist.append(
                    StrategyResolver.load_strategy(stratconf))
                validate_config_consistency(stratconf)

        else:
            # No strategy list specified, only one strategy
            self.strategylist.append(
                StrategyResolver.load_strategy(self.config))
            validate_config_consistency(self.config)

        if "timeframe" not in self.config:
            raise OperationalException(
                "Timeframe (ticker interval) needs to be set in either "
                "configuration or as cli argument `--timeframe 5m`")
        self.timeframe = str(self.config.get('timeframe'))
        self.timeframe_min = timeframe_to_minutes(self.timeframe)

        self.pairlists = PairListManager(self.exchange, self.config)
        if 'VolumePairList' in self.pairlists.name_list:
            raise OperationalException(
                "VolumePairList not allowed for backtesting.")
        if 'PerformanceFilter' in self.pairlists.name_list:
            raise OperationalException(
                "PerformanceFilter not allowed for backtesting.")

        if len(self.strategylist
               ) > 1 and 'PrecisionFilter' in self.pairlists.name_list:
            raise OperationalException(
                "PrecisionFilter not allowed for backtesting multiple strategies."
            )

        self.dataprovider.add_pairlisthandler(self.pairlists)
        self.pairlists.refresh_pairlist()

        if len(self.pairlists.whitelist) == 0:
            raise OperationalException("No pair in whitelist.")

        if config.get('fee', None) is not None:
            self.fee = config['fee']
        else:
            self.fee = self.exchange.get_fee(
                symbol=self.pairlists.whitelist[0])

        Trade.use_db = False
        Trade.reset_trades()
        PairLocks.timeframe = self.config['timeframe']
        PairLocks.use_db = False
        PairLocks.reset_locks()

        self.wallets = Wallets(self.config, self.exchange, log=False)

        # Get maximum required startup period
        self.required_startup = max(
            [strat.startup_candle_count for strat in self.strategylist])
예제 #19
0
def test_adjust_stop_loss(fee):
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        amount=5,
        fee_open=fee.return_value,
        fee_close=fee.return_value,
        exchange='bittrex',
        open_rate=1,
        max_rate=1,
    )

    trade.adjust_stop_loss(trade.open_rate, 0.05, True)
    assert trade.stop_loss == 0.95
    assert trade.stop_loss_pct == -0.05
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05

    # Get percent of profit with a lower rate
    trade.adjust_stop_loss(0.96, 0.05)
    assert trade.stop_loss == 0.95
    assert trade.stop_loss_pct == -0.05
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05

    # Get percent of profit with a custom rate (Higher than open rate)
    trade.adjust_stop_loss(1.3, -0.1)
    assert round(trade.stop_loss, 8) == 1.17
    assert trade.stop_loss_pct == -0.1
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05

    # current rate lower again ... should not change
    trade.adjust_stop_loss(1.2, 0.1)
    assert round(trade.stop_loss, 8) == 1.17
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05

    # current rate higher... should raise stoploss
    trade.adjust_stop_loss(1.4, 0.1)
    assert round(trade.stop_loss, 8) == 1.26
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05

    #  Initial is true but stop_loss set - so doesn't do anything
    trade.adjust_stop_loss(1.7, 0.1, True)
    assert round(trade.stop_loss, 8) == 1.26
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05
    assert trade.stop_loss_pct == -0.1
예제 #20
0
    def _rpc_trade_status(self,
                          trade_ids: List[int] = []) -> List[Dict[str, Any]]:
        """
        Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
        a remotely exposed function
        """
        # Fetch open trades
        if trade_ids:
            trades = Trade.get_trades(
                trade_filter=Trade.id.in_(trade_ids)).all()
        else:
            trades = Trade.get_open_trades()

        if not trades:
            raise RPCException('no active trade')
        else:
            results = []
            for trade in trades:
                order = None
                if trade.open_order_id:
                    order = self._freqtrade.exchange.fetch_order(
                        trade.open_order_id, trade.pair)
                # calculate profit and send message to user
                if trade.is_open:
                    try:
                        current_rate = self._freqtrade.exchange.get_sell_rate(
                            trade.pair, False)
                    except (ExchangeError, PricingError):
                        current_rate = NAN
                else:
                    current_rate = trade.close_rate
                current_profit = trade.calc_profit_ratio(current_rate)
                current_profit_abs = trade.calc_profit(current_rate)
                current_profit_fiat: Optional[float] = None
                # Calculate fiat profit
                if self._fiat_converter:
                    current_profit_fiat = self._fiat_converter.convert_amount(
                        current_profit_abs,
                        self._freqtrade.config['stake_currency'],
                        self._freqtrade.config['fiat_display_currency'])

                # Calculate guaranteed profit (in case of trailing stop)
                stoploss_entry_dist = trade.calc_profit(trade.stop_loss)
                stoploss_entry_dist_ratio = trade.calc_profit_ratio(
                    trade.stop_loss)
                # calculate distance to stoploss
                stoploss_current_dist = trade.stop_loss - current_rate
                stoploss_current_dist_ratio = stoploss_current_dist / current_rate

                trade_dict = trade.to_json()
                trade_dict.update(
                    dict(
                        base_currency=self._freqtrade.config['stake_currency'],
                        close_profit=trade.close_profit
                        if trade.close_profit is not None else None,
                        current_rate=current_rate,
                        current_profit=current_profit,  # Deprecated
                        current_profit_pct=round(current_profit * 100,
                                                 2),  # Deprecated
                        current_profit_abs=current_profit_abs,  # Deprecated
                        profit_ratio=current_profit,
                        profit_pct=round(current_profit * 100, 2),
                        profit_abs=current_profit_abs,
                        profit_fiat=current_profit_fiat,
                        stoploss_current_dist=stoploss_current_dist,
                        stoploss_current_dist_ratio=round(
                            stoploss_current_dist_ratio, 8),
                        stoploss_current_dist_pct=round(
                            stoploss_current_dist_ratio * 100, 2),
                        stoploss_entry_dist=stoploss_entry_dist,
                        stoploss_entry_dist_ratio=round(
                            stoploss_entry_dist_ratio, 8),
                        open_order='({} {} rem={:.8f})'.format(
                            order['type'], order['side'], order['remaining'])
                        if order else None,
                    ))
                results.append(trade_dict)
            return results
예제 #21
0
def test_api_performance(botclient, fee):
    ftbot, client = botclient
    patch_get_signal(ftbot, (True, False, None))

    trade = Trade(
        pair='LTC/ETH',
        amount=1,
        exchange='binance',
        stake_amount=1,
        open_rate=0.245441,
        open_order_id="123456",
        is_open=False,
        fee_close=fee.return_value,
        fee_open=fee.return_value,
        close_rate=0.265441,

    )
    trade.close_profit = trade.calc_profit_ratio()
    trade.close_profit_abs = trade.calc_profit()
    Trade.query.session.add(trade)

    trade = Trade(
        pair='XRP/ETH',
        amount=5,
        stake_amount=1,
        exchange='binance',
        open_rate=0.412,
        open_order_id="123456",
        is_open=False,
        fee_close=fee.return_value,
        fee_open=fee.return_value,
        close_rate=0.391
    )
    trade.close_profit = trade.calc_profit_ratio()
    trade.close_profit_abs = trade.calc_profit()

    Trade.query.session.add(trade)
    Trade.query.session.flush()

    rc = client_get(client, f"{BASE_URI}/performance")
    assert_response(rc)
    assert len(rc.json()) == 2
    assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279},
                         {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}]
예제 #22
0
    def should_sell(self,
                    trade: Trade,
                    rate: float,
                    date: datetime,
                    buy: bool,
                    sell: bool,
                    low: float = None,
                    high: float = None,
                    force_stoploss: float = 0) -> SellCheckTuple:
        """
        This function evaluates if one of the conditions required to trigger a sell
        has been reached, which can either be a stop-loss, ROI or sell-signal.
        :param low: Only used during backtesting to simulate stoploss
        :param high: Only used during backtesting, to simulate ROI
        :param force_stoploss: Externally provided stoploss
        :return: True if trade should be sold, False otherwise
        """
        # Set current rate to low for backtesting sell
        current_rate = low or rate
        current_profit = trade.calc_profit_percent(current_rate)

        trade.adjust_min_max_rates(high or current_rate)

        stoplossflag = self.stop_loss_reached(current_rate=current_rate,
                                              trade=trade,
                                              current_time=date,
                                              current_profit=current_profit,
                                              force_stoploss=force_stoploss,
                                              high=high)

        if stoplossflag.sell_flag:
            logger.debug(f"{trade.pair} - Stoploss hit. sell_flag=True, "
                         f"sell_type={stoplossflag.sell_type}")
            return stoplossflag

        # Set current rate to high for backtesting sell
        current_rate = high or rate
        current_profit = trade.calc_profit_percent(current_rate)
        experimental = self.config.get('experimental', {})

        if buy and experimental.get('ignore_roi_if_buy_signal', False):
            # This one is noisy, commented out
            # logger.debug(f"{trade.pair} - Buy signal still active. sell_flag=False")
            return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)

        # Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
        if self.min_roi_reached(trade=trade,
                                current_profit=current_profit,
                                current_time=date):
            logger.debug(
                f"{trade.pair} - Required profit reached. sell_flag=True, "
                f"sell_type=SellType.ROI")
            return SellCheckTuple(sell_flag=True, sell_type=SellType.ROI)

        if experimental.get('sell_profit_only', False):
            # This one is noisy, commented out
            # logger.debug(f"{trade.pair} - Checking if trade is profitable...")
            if trade.calc_profit(rate=rate) <= 0:
                # This one is noisy, commented out
                # logger.debug(f"{trade.pair} - Trade is not profitable. sell_flag=False")
                return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)

        if sell and not buy and experimental.get('use_sell_signal', False):
            logger.debug(
                f"{trade.pair} - Sell signal received. sell_flag=True, "
                f"sell_type=SellType.SELL_SIGNAL")
            return SellCheckTuple(sell_flag=True,
                                  sell_type=SellType.SELL_SIGNAL)

        # This one is noisy, commented out...
        # logger.debug(f"{trade.pair} - No sell signal. sell_flag=False")
        return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
예제 #23
0
def test_api_forcebuy(botclient, mocker, fee):
    ftbot, client = botclient

    rc = client_post(client, f"{BASE_URI}/forcebuy",
                     data='{"pair": "ETH/BTC"}')
    assert_response(rc, 502)
    assert rc.json == {"error": "Error querying _forcebuy: Forcebuy not enabled."}

    # enable forcebuy
    ftbot.config["forcebuy_enable"] = True

    fbuy_mock = MagicMock(return_value=None)
    mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
    rc = client_post(client, f"{BASE_URI}/forcebuy",
                     data='{"pair": "ETH/BTC"}')
    assert_response(rc)
    assert rc.json == {"status": "Error buying pair ETH/BTC."}

    # Test creating trae
    fbuy_mock = MagicMock(return_value=Trade(
        pair='ETH/ETH',
        amount=1,
        exchange='bittrex',
        stake_amount=1,
        open_rate=0.245441,
        open_order_id="123456",
        open_date=datetime.utcnow(),
        is_open=False,
        fee_close=fee.return_value,
        fee_open=fee.return_value,
        close_rate=0.265441,
    ))
    mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)

    rc = client_post(client, f"{BASE_URI}/forcebuy",
                     data='{"pair": "ETH/BTC"}')
    assert_response(rc)
    assert rc.json == {'amount': 1,
                       'trade_id': None,
                       'close_date': None,
                       'close_date_hum': None,
                       'close_timestamp': None,
                       'close_rate': 0.265441,
                       'open_date': ANY,
                       'open_date_hum': 'just now',
                       'open_timestamp': ANY,
                       'open_rate': 0.245441,
                       'pair': 'ETH/ETH',
                       'stake_amount': 1,
                       'stop_loss': None,
                       'stop_loss_abs': None,
                       'stop_loss_pct': None,
                       'stop_loss_ratio': None,
                       'stoploss_order_id': None,
                       'stoploss_last_update': None,
                       'stoploss_last_update_timestamp': None,
                       'initial_stop_loss': None,
                       'initial_stop_loss_abs': None,
                       'initial_stop_loss_pct': None,
                       'initial_stop_loss_ratio': None,
                       'close_profit': None,
                       'close_profit_abs': None,
                       'close_rate_requested': None,
                       'fee_close': 0.0025,
                       'fee_close_cost': None,
                       'fee_close_currency': None,
                       'fee_open': 0.0025,
                       'fee_open_cost': None,
                       'fee_open_currency': None,
                       'is_open': False,
                       'max_rate': None,
                       'min_rate': None,
                       'open_order_id': '123456',
                       'open_rate_requested': None,
                       'open_trade_price': 0.2460546025,
                       'sell_reason': None,
                       'sell_order_status': None,
                       'strategy': None,
                       'ticker_interval': None,
                       'timeframe': None,
                       'exchange': 'bittrex',
                       }
예제 #24
0
    def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
        """
        Check if trade is fulfilled in which case the stoploss
        on exchange should be added immediately if stoploss on exchange
        is enabled.
        """

        logger.debug('Handling stoploss on exchange %s ...', trade)

        stoploss_order = None

        try:
            # First we check if there is already a stoploss on exchange
            stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \
                if trade.stoploss_order_id else None
        except InvalidOrderException as exception:
            logger.warning('Unable to fetch stoploss order: %s', exception)

        # If trade open order id does not exist: buy order is fulfilled
        buy_order_fulfilled = not trade.open_order_id

        # Limit price threshold: As limit price should always be below price
        limit_price_pct = 0.99

        # If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
        if (buy_order_fulfilled and not stoploss_order):
            if self.edge:
                stoploss = self.edge.stoploss(pair=trade.pair)
            else:
                stoploss = self.strategy.stoploss

            stop_price = trade.open_rate * (1 + stoploss)

            # limit price should be less than stop price.
            limit_price = stop_price * limit_price_pct

            try:
                stoploss_order_id = self.exchange.stoploss_limit(
                    pair=trade.pair,
                    amount=trade.amount,
                    stop_price=stop_price,
                    rate=limit_price)['id']
                trade.stoploss_order_id = str(stoploss_order_id)
                trade.stoploss_last_update = datetime.now()
                return False

            except DependencyException as exception:
                logger.warning(
                    'Unable to place a stoploss order on exchange: %s',
                    exception)

        # If stoploss order is canceled for some reason we add it
        if stoploss_order and stoploss_order['status'] == 'canceled':
            try:
                stoploss_order_id = self.exchange.stoploss_limit(
                    pair=trade.pair,
                    amount=trade.amount,
                    stop_price=trade.stop_loss,
                    rate=trade.stop_loss * limit_price_pct)['id']
                trade.stoploss_order_id = str(stoploss_order_id)
                return False
            except DependencyException as exception:
                logger.warning(
                    'Stoploss order was cancelled, '
                    'but unable to recreate one: %s', exception)

        # We check if stoploss order is fulfilled
        if stoploss_order and stoploss_order['status'] == 'closed':
            trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
            trade.update(stoploss_order)
            self._notify_sell(trade)
            return True

        # Finally we check if stoploss on exchange should be moved up because of trailing.
        if stoploss_order and self.config.get('trailing_stop', False):
            # if trailing stoploss is enabled we check if stoploss value has changed
            # in which case we cancel stoploss order and put another one with new
            # value immediately
            self.handle_trailing_stoploss_on_exchange(trade, stoploss_order)

        return False
예제 #25
0
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
    mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
    mocker.patch.multiple(
        'freqtrade.exchange.Exchange',
        fetch_ticker=ticker,
        get_fee=fee,
    )

    freqtradebot = get_patched_freqtradebot(mocker, default_conf)
    patch_get_signal(freqtradebot)
    rpc = RPC(freqtradebot)

    freqtradebot.state = State.RUNNING
    with pytest.raises(RPCException, match=r'.*no active trade*'):
        rpc._rpc_trade_status()

    freqtradebot.enter_positions()
    trades = Trade.get_open_trades()
    trades[0].open_order_id = None
    freqtradebot.exit_positions(trades)

    results = rpc._rpc_trade_status()
    assert results[0] == {
        'trade_id': 1,
        'pair': 'ETH/BTC',
        'base_currency': 'BTC',
        'open_date': ANY,
        'open_timestamp': ANY,
        'is_open': ANY,
        'fee_open': ANY,
        'fee_open_cost': ANY,
        'fee_open_currency': ANY,
        'fee_close': fee.return_value,
        'fee_close_cost': ANY,
        'fee_close_currency': ANY,
        'open_rate_requested': ANY,
        'open_trade_value': 0.0010025,
        'close_rate_requested': ANY,
        'sell_reason': ANY,
        'sell_order_status': ANY,
        'min_rate': ANY,
        'max_rate': ANY,
        'strategy': ANY,
        'buy_tag': ANY,
        'timeframe': 5,
        'open_order_id': ANY,
        'close_date': None,
        'close_timestamp': None,
        'open_rate': 1.098e-05,
        'close_rate': None,
        'current_rate': 1.099e-05,
        'amount': 91.07468123,
        'amount_requested': 91.07468123,
        'stake_amount': 0.001,
        'trade_duration': None,
        'trade_duration_s': None,
        'close_profit': None,
        'close_profit_pct': None,
        'close_profit_abs': None,
        'current_profit': -0.00408133,
        'current_profit_pct': -0.41,
        'current_profit_abs': -4.09e-06,
        'profit_ratio': -0.00408133,
        'profit_pct': -0.41,
        'profit_abs': -4.09e-06,
        'profit_fiat': ANY,
        'stop_loss_abs': 9.882e-06,
        'stop_loss_pct': -10.0,
        'stop_loss_ratio': -0.1,
        'stoploss_order_id': None,
        'stoploss_last_update': ANY,
        'stoploss_last_update_timestamp': ANY,
        'initial_stop_loss_abs': 9.882e-06,
        'initial_stop_loss_pct': -10.0,
        'initial_stop_loss_ratio': -0.1,
        'stoploss_current_dist': -1.1080000000000002e-06,
        'stoploss_current_dist_ratio': -0.10081893,
        'stoploss_current_dist_pct': -10.08,
        'stoploss_entry_dist': -0.00010475,
        'stoploss_entry_dist_ratio': -0.10448878,
        'open_order': None,
        'exchange': 'binance',
    }

    mocker.patch(
        'freqtrade.exchange.Exchange.get_rate',
        MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
    results = rpc._rpc_trade_status()
    assert isnan(results[0]['current_profit'])
    assert isnan(results[0]['current_rate'])
    assert results[0] == {
        'trade_id': 1,
        'pair': 'ETH/BTC',
        'base_currency': 'BTC',
        'open_date': ANY,
        'open_timestamp': ANY,
        'is_open': ANY,
        'fee_open': ANY,
        'fee_open_cost': ANY,
        'fee_open_currency': ANY,
        'fee_close': fee.return_value,
        'fee_close_cost': ANY,
        'fee_close_currency': ANY,
        'open_rate_requested': ANY,
        'open_trade_value': ANY,
        'close_rate_requested': ANY,
        'sell_reason': ANY,
        'sell_order_status': ANY,
        'min_rate': ANY,
        'max_rate': ANY,
        'strategy': ANY,
        'buy_tag': ANY,
        'timeframe': ANY,
        'open_order_id': ANY,
        'close_date': None,
        'close_timestamp': None,
        'open_rate': 1.098e-05,
        'close_rate': None,
        'current_rate': ANY,
        'amount': 91.07468123,
        'amount_requested': 91.07468123,
        'trade_duration': ANY,
        'trade_duration_s': ANY,
        'stake_amount': 0.001,
        'close_profit': None,
        'close_profit_pct': None,
        'close_profit_abs': None,
        'current_profit': ANY,
        'current_profit_pct': ANY,
        'current_profit_abs': ANY,
        'profit_ratio': ANY,
        'profit_pct': ANY,
        'profit_abs': ANY,
        'profit_fiat': ANY,
        'stop_loss_abs': 9.882e-06,
        'stop_loss_pct': -10.0,
        'stop_loss_ratio': -0.1,
        'stoploss_order_id': None,
        'stoploss_last_update': ANY,
        'stoploss_last_update_timestamp': ANY,
        'initial_stop_loss_abs': 9.882e-06,
        'initial_stop_loss_pct': -10.0,
        'initial_stop_loss_ratio': -0.1,
        'stoploss_current_dist': ANY,
        'stoploss_current_dist_ratio': ANY,
        'stoploss_current_dist_pct': ANY,
        'stoploss_entry_dist': -0.00010475,
        'stoploss_entry_dist_ratio': -0.10448878,
        'open_order': None,
        'exchange': 'binance',
    }
예제 #26
0
def test_get_open(fee):

    create_mock_trades(fee)
    assert len(Trade.get_open_trades()) == 4
예제 #27
0
    def _rpc_trade_statistics(self, stake_currency: str,
                              fiat_display_currency: str) -> Dict[str, Any]:
        """ Returns cumulative profit statistics """
        trades = Trade.get_trades().order_by(Trade.id).all()

        profit_all_coin = []
        profit_all_ratio = []
        profit_closed_coin = []
        profit_closed_ratio = []
        durations = []

        for trade in trades:
            current_rate: float = 0.0

            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_ratio = trade.close_profit
                profit_closed_coin.append(trade.close_profit_abs)
                profit_closed_ratio.append(profit_ratio)
            else:
                # Get current rate
                try:
                    current_rate = self._freqtrade.get_sell_rate(
                        trade.pair, False)
                except DependencyException:
                    current_rate = NAN
                profit_ratio = trade.calc_profit_ratio(rate=current_rate)

            profit_all_coin.append(
                trade.calc_profit(rate=trade.close_rate or current_rate))
            profit_all_ratio.append(profit_ratio)

        best_pair = Trade.get_best_pair()

        if not best_pair:
            raise RPCException('no closed trade')

        bp_pair, bp_rate = best_pair

        # Prepare data to display
        profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
        profit_closed_percent = (round(mean(profit_closed_ratio) *
                                       100, 2) if profit_closed_ratio else 0.0)
        profit_closed_fiat = self._fiat_converter.convert_amount(
            profit_closed_coin_sum, stake_currency,
            fiat_display_currency) if self._fiat_converter else 0

        profit_all_coin_sum = round(sum(profit_all_coin), 8)
        profit_all_percent = round(mean(profit_all_ratio) *
                                   100, 2) if profit_all_ratio else 0.0
        profit_all_fiat = self._fiat_converter.convert_amount(
            profit_all_coin_sum, stake_currency,
            fiat_display_currency) if self._fiat_converter else 0

        num = float(len(durations) or 1)
        return {
            'profit_closed_coin':
            profit_closed_coin_sum,
            'profit_closed_percent':
            profit_closed_percent,
            'profit_closed_fiat':
            profit_closed_fiat,
            'profit_all_coin':
            profit_all_coin_sum,
            '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),
        }
예제 #28
0
def test_api_status(botclient, mocker, ticker, fee, markets):
    ftbot, client = botclient
    patch_get_signal(ftbot, (True, False))
    mocker.patch.multiple(
        'freqtrade.exchange.Exchange',
        get_balances=MagicMock(return_value=ticker),
        fetch_ticker=ticker,
        get_fee=fee,
        markets=PropertyMock(return_value=markets)
    )

    rc = client_get(client, f"{BASE_URI}/status")
    assert_response(rc, 200)
    assert rc.json == []

    ftbot.enter_positions()
    trades = Trade.get_open_trades()
    trades[0].open_order_id = None
    ftbot.exit_positions(trades)

    rc = client_get(client, f"{BASE_URI}/status")
    assert_response(rc)
    assert len(rc.json) == 1
    assert rc.json == [{'amount': 91.07468124,
                        'base_currency': 'BTC',
                        'close_date': None,
                        'close_date_hum': None,
                        'close_timestamp': None,
                        'close_profit': None,
                        'close_profit_pct': None,
                        'close_profit_abs': None,
                        'close_rate': None,
                        'current_profit': -0.00408133,
                        'current_profit_pct': -0.41,
                        'current_profit_abs': -4.09e-06,
                        'current_rate': 1.099e-05,
                        'open_date': ANY,
                        'open_date_hum': 'just now',
                        'open_timestamp': ANY,
                        'open_order': None,
                        'open_rate': 1.098e-05,
                        'pair': 'ETH/BTC',
                        'stake_amount': 0.001,
                        'stop_loss': 9.882e-06,
                        'stop_loss_abs': 9.882e-06,
                        'stop_loss_pct': -10.0,
                        'stop_loss_ratio': -0.1,
                        'stoploss_order_id': None,
                        'stoploss_last_update': ANY,
                        'stoploss_last_update_timestamp': ANY,
                        'initial_stop_loss': 9.882e-06,
                        'initial_stop_loss_abs': 9.882e-06,
                        'initial_stop_loss_pct': -10.0,
                        'initial_stop_loss_ratio': -0.1,
                        'stoploss_current_dist': -1.1080000000000002e-06,
                        'stoploss_current_dist_ratio': -0.10081893,
                        'stoploss_entry_dist': -0.00010475,
                        'stoploss_entry_dist_ratio': -0.10448878,
                        'trade_id': 1,
                        'close_rate_requested': None,
                        'current_rate': 1.099e-05,
                        'fee_close': 0.0025,
                        'fee_close_cost': None,
                        'fee_close_currency': None,
                        'fee_open': 0.0025,
                        'fee_open_cost': None,
                        'fee_open_currency': None,
                        'open_date': ANY,
                        'is_open': True,
                        'max_rate': 1.099e-05,
                        'min_rate': 1.098e-05,
                        'open_order_id': None,
                        'open_rate_requested': 1.098e-05,
                        'open_trade_price': 0.0010025,
                        'sell_reason': None,
                        'sell_order_status': None,
                        'strategy': 'DefaultStrategy',
                        'ticker_interval': 5,
                        'timeframe': 5,
                        'exchange': 'bittrex',
                        }]
예제 #29
0
def test_to_json(default_conf, fee):

    # Simulate dry_run entries
    trade = Trade(pair='ETH/BTC',
                  stake_amount=0.001,
                  amount=123.0,
                  amount_requested=123.0,
                  fee_open=fee.return_value,
                  fee_close=fee.return_value,
                  open_date=arrow.utcnow().shift(hours=-2).datetime,
                  open_rate=0.123,
                  exchange='bittrex',
                  open_order_id='dry_run_buy_12345')
    result = trade.to_json()
    assert isinstance(result, dict)

    assert result == {
        'trade_id': None,
        'pair': 'ETH/BTC',
        'is_open': None,
        'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
        'open_timestamp': int(trade.open_date.timestamp() * 1000),
        'open_order_id': 'dry_run_buy_12345',
        'close_date': None,
        'close_timestamp': None,
        'open_rate': 0.123,
        'open_rate_requested': None,
        'open_trade_value': 15.1668225,
        'fee_close': 0.0025,
        'fee_close_cost': None,
        'fee_close_currency': None,
        'fee_open': 0.0025,
        'fee_open_cost': None,
        'fee_open_currency': None,
        'close_rate': None,
        'close_rate_requested': None,
        'amount': 123.0,
        'amount_requested': 123.0,
        'stake_amount': 0.001,
        'trade_duration': None,
        'trade_duration_s': None,
        'close_profit': None,
        'close_profit_pct': None,
        'close_profit_abs': None,
        'profit_ratio': None,
        'profit_pct': None,
        'profit_abs': None,
        'sell_reason': None,
        'sell_order_status': None,
        'stop_loss_abs': None,
        'stop_loss_ratio': None,
        'stop_loss_pct': None,
        'stoploss_order_id': None,
        'stoploss_last_update': None,
        'stoploss_last_update_timestamp': None,
        'initial_stop_loss_abs': None,
        'initial_stop_loss_pct': None,
        'initial_stop_loss_ratio': None,
        'min_rate': None,
        'max_rate': None,
        'strategy': None,
        'timeframe': None,
        'exchange': 'bittrex',
    }

    # Simulate dry_run entries
    trade = Trade(
        pair='XRP/BTC',
        stake_amount=0.001,
        amount=100.0,
        amount_requested=101.0,
        fee_open=fee.return_value,
        fee_close=fee.return_value,
        open_date=arrow.utcnow().shift(hours=-2).datetime,
        close_date=arrow.utcnow().shift(hours=-1).datetime,
        open_rate=0.123,
        close_rate=0.125,
        exchange='bittrex',
    )
    result = trade.to_json()
    assert isinstance(result, dict)

    assert result == {
        'trade_id': None,
        'pair': 'XRP/BTC',
        'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
        'open_timestamp': int(trade.open_date.timestamp() * 1000),
        'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"),
        'close_timestamp': int(trade.close_date.timestamp() * 1000),
        'open_rate': 0.123,
        'close_rate': 0.125,
        'amount': 100.0,
        'amount_requested': 101.0,
        'stake_amount': 0.001,
        'trade_duration': 60,
        'trade_duration_s': 3600,
        'stop_loss_abs': None,
        'stop_loss_pct': None,
        'stop_loss_ratio': None,
        'stoploss_order_id': None,
        'stoploss_last_update': None,
        'stoploss_last_update_timestamp': None,
        'initial_stop_loss_abs': None,
        'initial_stop_loss_pct': None,
        'initial_stop_loss_ratio': None,
        'close_profit': None,
        'close_profit_pct': None,
        'close_profit_abs': None,
        'profit_ratio': None,
        'profit_pct': None,
        'profit_abs': None,
        'close_rate_requested': None,
        'fee_close': 0.0025,
        'fee_close_cost': None,
        'fee_close_currency': None,
        'fee_open': 0.0025,
        'fee_open_cost': None,
        'fee_open_currency': None,
        'is_open': None,
        'max_rate': None,
        'min_rate': None,
        'open_order_id': None,
        'open_rate_requested': None,
        'open_trade_value': 12.33075,
        'sell_reason': None,
        'sell_order_status': None,
        'strategy': None,
        'timeframe': None,
        'exchange': 'bittrex',
    }
예제 #30
0
    def populate_trades(self, pair: str) -> dict:
        # Initialize the trades dict if it doesn't exist, persist it otherwise
        if not pair in self.custom_trade_info:
            self.custom_trade_info[pair] = {}

        # init the temp dicts and set the trade stuff to false
        trade_data = {}
        trade_data['active_trade'] = trade_data['other_trades'] = trade_data[
            'biggest_loser'] = False
        self.custom_trade_info['meta'] = {}

        # active trade stuff only works in live and dry, not backtest
        if self.config['runmode'].value in ('live', 'dry_run'):

            # find out if we have an open trade for this pair
            active_trade = Trade.get_trades([
                Trade.pair == pair,
                Trade.is_open.is_(True),
            ]).all()

            # if so, get some information
            if active_trade:
                # get current price and update the min/max rate
                current_rate = self.get_current_price(pair, True)
                active_trade[0].adjust_min_max_rates(current_rate)

                # get how long the trade has been open in minutes and candles
                present = arrow.utcnow()
                trade_start = arrow.get(active_trade[0].open_date)
                open_minutes = (present -
                                trade_start).total_seconds() // 60  # floor

                # set up the things we use in the strategy
                trade_data['active_trade'] = True
                trade_data['current_profit'] = active_trade[
                    0].calc_profit_ratio(current_rate)
                trade_data['peak_profit'] = max(
                    0, active_trade[0].calc_profit_ratio(
                        active_trade[0].max_rate))
                trade_data['open_minutes']: int = open_minutes
                trade_data['open_candles']: int = (
                    open_minutes // active_trade[0].timeframe)  # floor
            else:
                trade_data['current_profit'] = trade_data['peak_profit'] = 0.0
                trade_data['open_minutes'] = trade_data['open_candles'] = 0

            # if there are open trades not including the current pair, get some information
            # future reference, for *all* open trades: open_trades = Trade.get_open_trades()
            other_trades = Trade.get_trades([
                Trade.pair != pair,
                Trade.is_open.is_(True),
            ]).all()

            if other_trades:
                trade_data['other_trades'] = True
                other_profit = tuple(
                    trade.calc_profit_ratio(
                        self.get_current_price(trade.pair, False))
                    for trade in other_trades)
                trade_data['avg_other_profit'] = mean(other_profit)
                # find which of our trades is the biggest loser
                if trade_data['current_profit'] < min(other_profit):
                    trade_data['biggest_loser'] = True
            else:
                trade_data['avg_other_profit'] = 0

            # get the number of free trade slots, storing in every pairs dict due to laziness
            open_trades = len(Trade.get_open_trades())
            trade_data['free_slots'] = max(
                0, self.config['max_open_trades'] - open_trades)

        return trade_data
예제 #31
0
def test_stoploss_reinitialization(default_conf, fee):
    init_db(default_conf['db_url'])
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        fee_open=fee.return_value,
        open_date=arrow.utcnow().shift(hours=-2).datetime,
        amount=10,
        fee_close=fee.return_value,
        exchange='bittrex',
        open_rate=1,
        max_rate=1,
    )

    trade.adjust_stop_loss(trade.open_rate, 0.05, True)
    assert trade.stop_loss == 0.95
    assert trade.stop_loss_pct == -0.05
    assert trade.initial_stop_loss == 0.95
    assert trade.initial_stop_loss_pct == -0.05
    Trade.query.session.add(trade)

    # Lower stoploss
    Trade.stoploss_reinitialization(0.06)

    trades = Trade.get_open_trades()
    assert len(trades) == 1
    trade_adj = trades[0]
    assert trade_adj.stop_loss == 0.94
    assert trade_adj.stop_loss_pct == -0.06
    assert trade_adj.initial_stop_loss == 0.94
    assert trade_adj.initial_stop_loss_pct == -0.06

    # Raise stoploss
    Trade.stoploss_reinitialization(0.04)

    trades = Trade.get_open_trades()
    assert len(trades) == 1
    trade_adj = trades[0]
    assert trade_adj.stop_loss == 0.96
    assert trade_adj.stop_loss_pct == -0.04
    assert trade_adj.initial_stop_loss == 0.96
    assert trade_adj.initial_stop_loss_pct == -0.04

    # Trailing stoploss (move stoplos up a bit)
    trade.adjust_stop_loss(1.02, 0.04)
    assert trade_adj.stop_loss == 0.9792
    assert trade_adj.initial_stop_loss == 0.96

    Trade.stoploss_reinitialization(0.04)

    trades = Trade.get_open_trades()
    assert len(trades) == 1
    trade_adj = trades[0]
    # Stoploss should not change in this case.
    assert trade_adj.stop_loss == 0.9792
    assert trade_adj.stop_loss_pct == -0.04
    assert trade_adj.initial_stop_loss == 0.96
    assert trade_adj.initial_stop_loss_pct == -0.04
예제 #32
0
def backtest(stake_amount: float,
             processed: Dict[str, DataFrame],
             max_open_trades: int = 0,
             realistic: bool = True,
             sell_profit_only: bool = False,
             stoploss: int = -1.00,
             use_sell_signal: bool = False) -> DataFrame:
    """
    Implements backtesting functionality
    :param stake_amount: btc amount to use for each trade
    :param processed: a processed dictionary with format {pair, data}
    :param max_open_trades: maximum number of concurrent trades (default: 0, disabled)
    :param realistic: do we try to simulate realistic trades? (default: True)
    :return: DataFrame
    """
    trades = []
    trade_count_lock: dict = {}
    exchange._API = Bittrex({'key': '', 'secret': ''})
    for pair, pair_data in processed.items():
        pair_data['buy'], pair_data['sell'] = 0, 0
        ticker = populate_sell_trend(populate_buy_trend(pair_data))
        # for each buy point
        lock_pair_until = None
        buy_subset = ticker[ticker.buy == 1][[
            'buy', 'open', 'close', 'date', 'sell'
        ]]
        for row in buy_subset.itertuples(index=True):
            if realistic:
                if lock_pair_until is not None and row.Index <= lock_pair_until:
                    continue
            if max_open_trades > 0:
                # Check if max_open_trades has already been reached for the given date
                if not trade_count_lock.get(row.date, 0) < max_open_trades:
                    continue

            if max_open_trades > 0:
                # Increase lock
                trade_count_lock[row.date] = trade_count_lock.get(row.date,
                                                                  0) + 1

            trade = Trade(open_rate=row.close,
                          open_date=row.date,
                          stake_amount=stake_amount,
                          amount=stake_amount / row.open,
                          fee=exchange.get_fee())

            # calculate win/lose forwards from buy point
            sell_subset = ticker[row.Index + 1:][['close', 'date', 'sell']]
            for row2 in sell_subset.itertuples(index=True):
                if max_open_trades > 0:
                    # Increase trade_count_lock for every iteration
                    trade_count_lock[row2.date] = trade_count_lock.get(
                        row2.date, 0) + 1

                current_profit_percent = trade.calc_profit_percent(
                    rate=row2.close)
                if (sell_profit_only and current_profit_percent < 0):
                    continue
                if min_roi_reached(trade, row2.close, row2.date) or \
                    (row2.sell == 1 and use_sell_signal) or \
                        current_profit_percent <= stoploss:
                    current_profit_btc = trade.calc_profit(rate=row2.close)
                    lock_pair_until = row2.Index

                    trades.append(
                        (pair, current_profit_percent, current_profit_btc,
                         row2.Index - row.Index, current_profit_btc > 0,
                         current_profit_btc < 0))
                    break
    labels = [
        'currency', 'profit_percent', 'profit_BTC', 'duration', 'profit',
        'loss'
    ]
    return DataFrame.from_records(trades, columns=labels)
예제 #33
0
def test_update_fee(fee):
    trade = Trade(
        pair='ETH/BTC',
        stake_amount=0.001,
        fee_open=fee.return_value,
        open_date=arrow.utcnow().shift(hours=-2).datetime,
        amount=10,
        fee_close=fee.return_value,
        exchange='bittrex',
        open_rate=1,
        max_rate=1,
    )
    fee_cost = 0.15
    fee_currency = 'BTC'
    fee_rate = 0.0075
    assert trade.fee_open_currency is None
    assert not trade.fee_updated('buy')
    assert not trade.fee_updated('sell')

    trade.update_fee(fee_cost, fee_currency, fee_rate, 'buy')
    assert trade.fee_updated('buy')
    assert not trade.fee_updated('sell')
    assert trade.fee_open_currency == fee_currency
    assert trade.fee_open_cost == fee_cost
    assert trade.fee_open == fee_rate
    # Setting buy rate should "guess" close rate
    assert trade.fee_close == fee_rate
    assert trade.fee_close_currency is None
    assert trade.fee_close_cost is None

    fee_rate = 0.0076
    trade.update_fee(fee_cost, fee_currency, fee_rate, 'sell')
    assert trade.fee_updated('buy')
    assert trade.fee_updated('sell')
    assert trade.fee_close == 0.0076
    assert trade.fee_close_cost == fee_cost
    assert trade.fee_close == fee_rate
예제 #34
0
    def execute_buy(self,
                    pair: str,
                    stake_amount: float,
                    price: Optional[float] = None) -> bool:
        """
        Executes a limit buy for the given pair
        :param pair: pair for which we want to create a LIMIT_BUY
        :return: None
        """
        pair_s = pair.replace('_', '/')
        pair_url = self.exchange.get_pair_detail_url(pair)
        stake_currency = self.config['stake_currency']
        fiat_currency = self.config.get('fiat_display_currency', None)
        time_in_force = self.strategy.order_time_in_force['buy']

        if price:
            buy_limit_requested = price
        else:
            # Calculate amount
            buy_limit_requested = self.get_target_bid(
                pair, self.exchange.get_ticker(pair))

        min_stake_amount = self._get_min_pair_stake_amount(
            pair_s, buy_limit_requested)
        if min_stake_amount is not None and min_stake_amount > stake_amount:
            logger.warning(
                f'Can\'t open a new trade for {pair_s}: stake amount'
                f' is too small ({stake_amount} < {min_stake_amount})')
            return False

        amount = stake_amount / buy_limit_requested

        order = self.exchange.buy(pair=pair,
                                  ordertype=self.strategy.order_types['buy'],
                                  amount=amount,
                                  rate=buy_limit_requested,
                                  time_in_force=time_in_force)
        order_id = order['id']
        order_status = order.get('status', None)

        # we assume the order is executed at the price requested
        buy_limit_filled_price = buy_limit_requested

        if order_status == 'expired' or order_status == 'rejected':
            order_type = self.strategy.order_types['buy']
            order_tif = self.strategy.order_time_in_force['buy']

            # return false if the order is not filled
            if float(order['filled']) == 0:
                logger.warning(
                    'Buy %s order with time in force %s for %s is %s by %s.'
                    ' zero amount is fulfilled.', order_tif, order_type,
                    pair_s, order_status, self.exchange.name)
                return False
            else:
                # the order is partially fulfilled
                # in case of IOC orders we can check immediately
                # if the order is fulfilled fully or partially
                logger.warning(
                    'Buy %s order with time in force %s for %s is %s by %s.'
                    ' %s amount fulfilled out of %s (%s remaining which is canceled).',
                    order_tif, order_type, pair_s, order_status,
                    self.exchange.name, order['filled'], order['amount'],
                    order['remaining'])
                stake_amount = order['cost']
                amount = order['amount']
                buy_limit_filled_price = order['price']
                order_id = None

        # in case of FOK the order may be filled immediately and fully
        elif order_status == 'closed':
            stake_amount = order['cost']
            amount = order['amount']
            buy_limit_filled_price = order['price']
            order_id = None

        self.rpc.send_msg({
            'type': RPCMessageType.BUY_NOTIFICATION,
            'exchange': self.exchange.name.capitalize(),
            'pair': pair_s,
            'market_url': pair_url,
            'limit': buy_limit_filled_price,
            'stake_amount': stake_amount,
            'stake_currency': stake_currency,
            'fiat_currency': fiat_currency
        })

        # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
        fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
        trade = Trade(pair=pair,
                      stake_amount=stake_amount,
                      amount=amount,
                      fee_open=fee,
                      fee_close=fee,
                      open_rate=buy_limit_filled_price,
                      open_rate_requested=buy_limit_requested,
                      open_date=datetime.utcnow(),
                      exchange=self.exchange.id,
                      open_order_id=order_id,
                      strategy=self.strategy.get_strategy_name(),
                      ticker_interval=constants.TICKER_INTERVAL_MINUTES[
                          self.config['ticker_interval']])

        Trade.session.add(trade)
        Trade.session.flush()

        # Updating wallets
        self.wallets.update()

        return True
예제 #35
0
    def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
        """
        Check if trade is fulfilled in which case the stoploss
        on exchange should be added immediately if stoploss on exchange
        is enabled.
        """

        logger.debug('Handling stoploss on exchange %s ...', trade)

        stoploss_order = None

        try:
            # First we check if there is already a stoploss on exchange
            stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \
                if trade.stoploss_order_id else None
        except InvalidOrderException as exception:
            logger.warning('Unable to fetch stoploss order: %s', exception)

        # If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
        if (not trade.open_order_id and not stoploss_order):

            stoploss = self.edge.stoploss(
                pair=trade.pair) if self.edge else self.strategy.stoploss

            stop_price = trade.open_rate * (1 + stoploss)

            if self.create_stoploss_order(trade=trade,
                                          stop_price=stop_price,
                                          rate=stop_price):
                trade.stoploss_last_update = datetime.now()
                return False

        # If stoploss order is canceled for some reason we add it
        if stoploss_order and stoploss_order['status'] == 'canceled':
            if self.create_stoploss_order(trade=trade,
                                          stop_price=trade.stop_loss,
                                          rate=trade.stop_loss):
                return False
            else:
                trade.stoploss_order_id = None
                logger.warning(
                    'Stoploss order was cancelled, but unable to recreate one.'
                )

        # We check if stoploss order is fulfilled
        if stoploss_order and stoploss_order['status'] == 'closed':
            trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
            trade.update(stoploss_order)
            # Lock pair for one candle to prevent immediate rebuys
            self.strategy.lock_pair(
                trade.pair,
                timeframe_to_next_date(self.config['ticker_interval']))
            self._notify_sell(trade, "stoploss")
            return True

        # Finally we check if stoploss on exchange should be moved up because of trailing.
        if stoploss_order and self.config.get('trailing_stop', False):
            # if trailing stoploss is enabled we check if stoploss value has changed
            # in which case we cancel stoploss order and put another one with new
            # value immediately
            self.handle_trailing_stoploss_on_exchange(trade, stoploss_order)

        return False