diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d52165e0a..603b0631f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -20,7 +20,7 @@ from freqtrade.exchange import timeframe_to_minutes from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver -from freqtrade.state import State +from freqtrade.state import State, RunMode from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.wallets import Wallets @@ -75,6 +75,12 @@ class FreqtradeBot(object): persistence.init(self.config.get('db_url', None), clean_open_orders=self.config.get('dry_run', False)) + # Stoploss on exchange does not make sense, therefore we need to disable that. + if (self.dataprovider.runmode == RunMode.DRY_RUN and + self.strategy.order_types.get('stoploss_on_exchange', False)): + logger.info("Disabling stoploss_on_exchange during dry-run.") + self.strategy.order_types['stoploss_on_exchange'] = False + config['order_types']['stoploss_on_exchange'] = False # Set initial bot state from config initial_state = self.config.get('initial_state') self.state = State[initial_state.upper()] if initial_state else State.STOPPED diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f77e0eddb..7b811cadc 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -10,7 +10,7 @@ from typing import Dict, Any, List, Optional import arrow import sqlalchemy as sql -from numpy import mean, nan_to_num, NAN +from numpy import mean, NAN from pandas import DataFrame from freqtrade import TemporaryError, DependencyException @@ -195,9 +195,9 @@ class RPC(object): trades = Trade.query.order_by(Trade.id).all() profit_all_coin = [] - profit_all_percent = [] + profit_all_perc = [] profit_closed_coin = [] - profit_closed_percent = [] + profit_closed_perc = [] durations = [] for trade in trades: @@ -211,7 +211,7 @@ class RPC(object): if not trade.is_open: profit_percent = trade.calc_profit_percent() profit_closed_coin.append(trade.calc_profit()) - profit_closed_percent.append(profit_percent) + profit_closed_perc.append(profit_percent) else: # Get current rate try: @@ -223,7 +223,7 @@ class RPC(object): profit_all_coin.append( trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)) ) - profit_all_percent.append(profit_percent) + profit_all_perc.append(profit_percent) best_pair = Trade.session.query( Trade.pair, sql.func.sum(Trade.close_profit).label('profit_sum') @@ -238,7 +238,8 @@ class RPC(object): # Prepare data to display profit_closed_coin_sum = round(sum(profit_closed_coin), 8) - profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2) + profit_closed_percent = (round(mean(profit_closed_perc) * 100, 2) if profit_closed_perc + else 0.0) profit_closed_fiat = self._fiat_converter.convert_amount( profit_closed_coin_sum, stake_currency, @@ -246,7 +247,7 @@ class RPC(object): ) if self._fiat_converter else 0 profit_all_coin_sum = round(sum(profit_all_coin), 8) - profit_all_percent = round(nan_to_num(mean(profit_all_percent)) * 100, 2) + profit_all_percent = round(mean(profit_all_perc) * 100, 2) if profit_all_perc else 0.0 profit_all_fiat = self._fiat_converter.convert_amount( profit_all_coin_sum, stake_currency, diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index fdccad02f..df9513cdb 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -10,6 +10,7 @@ from unittest.mock import MagicMock, PropertyMock import arrow import pytest +import numpy as np from telegram import Chat, Message, Update from freqtrade import constants, persistence @@ -25,6 +26,10 @@ from freqtrade.worker import Worker logging.getLogger('').setLevel(logging.INFO) +# Do not mask numpy errors as warnings that no one read, raise the exсeption +np.seterr(all='raise') + + def log_has(line, logs): # caplog mocker returns log as a tuple: ('freqtrade.something', logging.WARNING, 'foobar') # and we want to match line against foobar in the tuple diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index cf9a4fa29..4e649250a 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -16,7 +16,7 @@ from freqtrade.data.dataprovider import DataProvider from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType -from freqtrade.state import State +from freqtrade.state import State, RunMode from freqtrade.strategy.interface import SellCheckTuple, SellType from freqtrade.tests.conftest import (get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, @@ -130,7 +130,77 @@ def test_throttle_with_assets(mocker, default_conf) -> None: assert result == -1 -def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None: +def test_order_dict_dry_run(default_conf, mocker, caplog) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2) + ) + conf = default_conf.copy() + conf['runmode'] = RunMode.DRY_RUN + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True, + } + + freqtrade = FreqtradeBot(conf) + assert log_has("Disabling stoploss_on_exchange during dry-run.", caplog) + assert not freqtrade.strategy.order_types['stoploss_on_exchange'] + + caplog.clear() + # is left untouched + conf = default_conf.copy() + conf['runmode'] = RunMode.DRY_RUN + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': False, + } + freqtrade = FreqtradeBot(conf) + assert not freqtrade.strategy.order_types['stoploss_on_exchange'] + assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog) + + +def test_order_dict_live(default_conf, mocker, caplog) -> None: + patch_RPCManager(mocker) + patch_exchange(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2) + ) + conf = default_conf.copy() + conf['runmode'] = RunMode.LIVE + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': True, + } + + freqtrade = FreqtradeBot(conf) + assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog) + assert freqtrade.strategy.order_types['stoploss_on_exchange'] + + caplog.clear() + # is left untouched + conf = default_conf.copy() + conf['runmode'] = RunMode.LIVE + conf['order_types'] = { + 'buy': 'market', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': False, + } + freqtrade = FreqtradeBot(conf) + assert not freqtrade.strategy.order_types['stoploss_on_exchange'] + assert not log_has_re(".*stoploss_on_exchange .* dry-run", caplog) + + +def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) mocker.patch.multiple(